package workspace

import (
	"context"
	"testing"

	"github.com/google/uuid"
	"github.com/stretchr/testify/require"

	"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
)

func requireCode(t *testing.T, err error, want errs.Code) {
	t.Helper()
	ae, ok := errs.As(err)
	require.True(t, ok, "expected *errs.AppError, got %T", err)
	require.Equal(t, want, ae.Code)
}

func newWtSvc(t *testing.T) (*worktreeService, *fakeRepo, *fakePA, *fakeGit, uuid.UUID) {
	t.Helper()
	pa := &fakePA{pid: uuid.New(), canRead: true, canWrite: true}
	gitr := &fakeGit{}
	repo := newFakeRepo()
	svc := NewWorktreeService(repo, noopAudit{}, gitr, pa, "/data").(*worktreeService)
	wsID := uuid.New()
	repo.wss[wsID] = &Workspace{ID: wsID, ProjectID: pa.pid, MainPath: "/data/main", DefaultBranch: "main"}
	return svc, repo, pa, gitr, wsID
}

func TestWorktreeService_CreateAndList(t *testing.T) {
	t.Parallel()
	svc, _, _, _, wsID := newWtSvc(t)
	wt, err := svc.Create(context.Background(), Caller{UserID: uuid.New()}, wsID, CreateWorktreeInput{Branch: "feat-x"})
	require.NoError(t, err)
	require.Equal(t, "feat-x", wt.Branch)
	require.Equal(t, WorktreeStatusIdle, wt.Status)
}

func TestWorktreeService_AcquireIdempotent(t *testing.T) {
	t.Parallel()
	svc, _, _, _, wsID := newWtSvc(t)
	caller := Caller{UserID: uuid.New()}
	wt, err := svc.Create(context.Background(), caller, wsID, CreateWorktreeInput{Branch: "feat-y"})
	require.NoError(t, err)

	a1, err := svc.Acquire(context.Background(), caller, wt.ID)
	require.NoError(t, err)
	require.Equal(t, WorktreeStatusActive, a1.Status)
	a2, err := svc.Acquire(context.Background(), caller, wt.ID) // idempotent
	require.NoError(t, err)
	require.Equal(t, a1.ID, a2.ID)
}

func TestWorktreeService_AcquireConflict(t *testing.T) {
	t.Parallel()
	svc, _, _, _, wsID := newWtSvc(t)
	a := Caller{UserID: uuid.New()}
	b := Caller{UserID: uuid.New()}
	wt, err := svc.Create(context.Background(), a, wsID, CreateWorktreeInput{Branch: "feat-z"})
	require.NoError(t, err)
	_, err = svc.Acquire(context.Background(), a, wt.ID)
	require.NoError(t, err)
	_, err = svc.Acquire(context.Background(), b, wt.ID)
	require.Error(t, err)
}

func TestWorktreeService_Release(t *testing.T) {
	t.Parallel()
	svc, _, _, _, wsID := newWtSvc(t)
	caller := Caller{UserID: uuid.New()}
	wt, err := svc.Create(context.Background(), caller, wsID, CreateWorktreeInput{Branch: "feat-r"})
	require.NoError(t, err)
	_, err = svc.Acquire(context.Background(), caller, wt.ID)
	require.NoError(t, err)
	_, err = svc.Release(context.Background(), caller, wt.ID)
	require.NoError(t, err)
	got, _ := svc.repo.GetWorktreeByID(context.Background(), wt.ID)
	require.Equal(t, WorktreeStatusIdle, got.Status)
}

func TestWorktreeService_Delete_BlockedWhenActive(t *testing.T) {
	t.Parallel()
	svc, _, _, _, wsID := newWtSvc(t)
	caller := Caller{UserID: uuid.New()}
	wt, err := svc.Create(context.Background(), caller, wsID, CreateWorktreeInput{Branch: "feat-d"})
	require.NoError(t, err)
	_, err = svc.Acquire(context.Background(), caller, wt.ID)
	require.NoError(t, err)
	err = svc.Delete(context.Background(), caller, wt.ID)
	require.Error(t, err)
}

// Bug #5: Delete 的 check-and-transition 必须在锁下原子完成。即使 worktree 处于
// active（被持有），Delete 也必须在事务内（GetWorktreeByIDForUpdate）发现并拒绝，
// 返回 CodeWorktreeAlreadyActive，且不触碰磁盘 / 不删行。
func TestWorktreeService_Delete_AtomicGuard_RefusesActive(t *testing.T) {
	t.Parallel()
	svc, repo, _, gitr, wsID := newWtSvc(t)
	caller := Caller{UserID: uuid.New()}
	wt, err := svc.Create(context.Background(), caller, wsID, CreateWorktreeInput{Branch: "feat-g"})
	require.NoError(t, err)
	_, err = svc.Acquire(context.Background(), caller, wt.ID)
	require.NoError(t, err)

	err = svc.Delete(context.Background(), caller, wt.ID)
	require.Error(t, err)
	requireCode(t, err, errs.CodeWorktreeAlreadyActive)
	// 磁盘未被触碰，行仍存在且保持 active。
	require.Empty(t, gitr.removed)
	got, err := repo.GetWorktreeByID(context.Background(), wt.ID)
	require.NoError(t, err)
	require.Equal(t, WorktreeStatusActive, got.Status)
}

// Bug #6: Acquire 必须做 project 鉴权。无 write 权限的 caller 不得锁定 worktree。
func TestWorktreeService_Acquire_DeniedWithoutWriteAccess(t *testing.T) {
	t.Parallel()
	svc, _, pa, _, wsID := newWtSvc(t)
	caller := Caller{UserID: uuid.New()}
	wt, err := svc.Create(context.Background(), caller, wsID, CreateWorktreeInput{Branch: "feat-a"})
	require.NoError(t, err)

	pa.canWrite = false
	_, err = svc.Acquire(context.Background(), caller, wt.ID)
	require.Error(t, err)
	requireCode(t, err, errs.CodeForbidden)
}

// Bug #6: Release 同样必须做 project 鉴权（在 holder-match 之前）。
func TestWorktreeService_Release_DeniedWithoutWriteAccess(t *testing.T) {
	t.Parallel()
	svc, _, pa, _, wsID := newWtSvc(t)
	caller := Caller{UserID: uuid.New()}
	wt, err := svc.Create(context.Background(), caller, wsID, CreateWorktreeInput{Branch: "feat-rr"})
	require.NoError(t, err)
	_, err = svc.Acquire(context.Background(), caller, wt.ID)
	require.NoError(t, err)

	pa.canWrite = false
	_, err = svc.Release(context.Background(), caller, wt.ID)
	require.Error(t, err)
	requireCode(t, err, errs.CodeForbidden)
}
