package mcp

import (
	"context"
	"testing"
	"time"

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

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

// fakeChangeReqSvc implements changerequest.Service.
type fakeChangeReqSvc struct {
	items     []*changerequest.ChangeRequest
	mergeErr  error
	syncState changerequest.CIState
}

func (f *fakeChangeReqSvc) Create(_ context.Context, c changerequest.Caller, wsID uuid.UUID, in changerequest.CreateInput) (*changerequest.ChangeRequest, error) {
	ext := int64(len(f.items) + 1)
	cr := &changerequest.ChangeRequest{
		ID: uuid.New(), ProjectID: testPID, WorkspaceID: wsID, Number: len(f.items) + 1,
		Title: in.Title, SourceBranch: in.SourceBranch, TargetBranch: firstNonEmpty(in.TargetBranch, "main"),
		State: changerequest.StateOpen, ReviewVerdict: changerequest.VerdictPending, CIState: changerequest.CIUnknown,
		Provider: "gitea", ExternalID: &ext, ExternalURL: "http://h/pr", CreatedBy: c.UserID,
	}
	f.items = append(f.items, cr)
	return cr, nil
}
func (f *fakeChangeReqSvc) Get(_ context.Context, _ changerequest.Caller, id uuid.UUID) (*changerequest.ChangeRequest, error) {
	for _, cr := range f.items {
		if cr.ID == id {
			return cr, nil
		}
	}
	return nil, errs.New(errs.CodeNotFound, "not found")
}
func (f *fakeChangeReqSvc) GetByNumber(_ context.Context, _ changerequest.Caller, _ string, n int) (*changerequest.ChangeRequest, error) {
	for _, cr := range f.items {
		if cr.Number == n {
			return cr, nil
		}
	}
	return nil, errs.New(errs.CodeNotFound, "not found")
}
func (f *fakeChangeReqSvc) ListByWorkspace(_ context.Context, _ changerequest.Caller, wsID uuid.UUID) ([]*changerequest.ChangeRequest, error) {
	out := make([]*changerequest.ChangeRequest, 0, len(f.items))
	for _, cr := range f.items {
		if cr.WorkspaceID == wsID {
			out = append(out, cr)
		}
	}
	return out, nil
}
func (f *fakeChangeReqSvc) Review(_ context.Context, _ changerequest.Caller, id uuid.UUID, v changerequest.Verdict, _ string) (*changerequest.ChangeRequest, error) {
	cr, err := f.Get(context.Background(), changerequest.Caller{}, id)
	if err != nil {
		return nil, err
	}
	cr.ReviewVerdict = v
	return cr, nil
}
func (f *fakeChangeReqSvc) Merge(_ context.Context, _ changerequest.Caller, id uuid.UUID, _ string) (*changerequest.ChangeRequest, error) {
	if f.mergeErr != nil {
		return nil, f.mergeErr
	}
	cr, err := f.Get(context.Background(), changerequest.Caller{}, id)
	if err != nil {
		return nil, err
	}
	cr.State = changerequest.StateMerged
	cr.MergeCommitSHA = "merged-sha"
	return cr, nil
}
func (f *fakeChangeReqSvc) SyncStatus(_ context.Context, _ changerequest.Caller, id uuid.UUID) (*changerequest.ChangeRequest, error) {
	cr, err := f.Get(context.Background(), changerequest.Caller{}, id)
	if err != nil {
		return nil, err
	}
	if f.syncState != "" {
		cr.CIState = f.syncState
	}
	return cr, nil
}

func firstNonEmpty(a, b string) string {
	if a != "" {
		return a
	}
	return b
}

// crTestDeps mirrors wsTestDeps but with a change-request service attached.
func crTestDeps() (ServerDeps, *fakeChangeReqSvc) {
	deps := testDeps()
	deps.Workspaces = &fakeWorkspaceSvc{items: []*workspace.Workspace{{
		ID: testWSID, ProjectID: testPID, Slug: "ws-1", Name: "Workspace 1",
		DefaultBranch: "main", SyncStatus: workspace.SyncStatusIdle, CreatedAt: time.Now(),
	}}}
	deps.Worktrees = &fakeWorktreeSvc{}
	deps.GitOps = &fakeGitOpsSvc{}
	crSvc := &fakeChangeReqSvc{}
	deps.ChangeReqs = crSvc
	return deps, crSvc
}

func TestCreatePullRequest_HappyPath(t *testing.T) {
	deps, crSvc := crTestDeps()
	result, err := callTool(ctxWithAuth(Scope{}), deps, "create_pull_request", map[string]any{
		"workspace_id": testWSID.String(), "source_branch": "feature", "title": "Add X",
	})
	require.NoError(t, err)
	require.False(t, result.IsError)
	var out changeRequestOut
	unmarshalStructured(t, result, &out)
	assert.True(t, out.OK)
	assert.Equal(t, "feature", out.ChangeRequest.SourceBranch)
	require.Len(t, crSvc.items, 1)
}

func TestCreatePullRequest_ScopeDenied(t *testing.T) {
	deps, _ := crTestDeps()
	result, err := callTool(ctxWithAuth(Scope{Tools: []string{"git_diff"}}), deps, "create_pull_request", map[string]any{
		"workspace_id": testWSID.String(), "source_branch": "feature", "title": "X",
	})
	require.NoError(t, err)
	assert.True(t, result.IsError, "token scope without create_pull_request must deny")
}

func TestGetPRStatus(t *testing.T) {
	deps, crSvc := crTestDeps()
	crSvc.syncState = changerequest.CISuccess
	// seed via create
	_, err := callTool(ctxWithAuth(Scope{}), deps, "create_pull_request", map[string]any{
		"workspace_id": testWSID.String(), "source_branch": "feature", "title": "X",
	})
	require.NoError(t, err)

	result, err := callTool(ctxWithAuth(Scope{}), deps, "get_pr_status", map[string]any{
		"workspace_id": testWSID.String(), "number": 1,
	})
	require.NoError(t, err)
	require.False(t, result.IsError)
	var out changeRequestOut
	unmarshalStructured(t, result, &out)
	assert.Equal(t, string(changerequest.CISuccess), out.ChangeRequest.CIState)
}

func TestMergePullRequest_GateError(t *testing.T) {
	deps, crSvc := crTestDeps()
	crSvc.mergeErr = errs.New(errs.CodeFailedPrecondition, "merge blocked: review not approved")
	_, err := callTool(ctxWithAuth(Scope{}), deps, "create_pull_request", map[string]any{
		"workspace_id": testWSID.String(), "source_branch": "feature", "title": "X",
	})
	require.NoError(t, err)

	result, err := callTool(ctxWithAuth(Scope{}), deps, "merge_pull_request", map[string]any{
		"workspace_id": testWSID.String(), "number": 1,
	})
	require.NoError(t, err)
	assert.True(t, result.IsError, "gated merge must surface as an MCP error")
}

func TestMergePullRequest_Allowed(t *testing.T) {
	deps, _ := crTestDeps()
	_, err := callTool(ctxWithAuth(Scope{}), deps, "create_pull_request", map[string]any{
		"workspace_id": testWSID.String(), "source_branch": "feature", "title": "X",
	})
	require.NoError(t, err)

	result, err := callTool(ctxWithAuth(Scope{}), deps, "merge_pull_request", map[string]any{
		"workspace_id": testWSID.String(), "number": 1, "method": "squash",
	})
	require.NoError(t, err)
	require.False(t, result.IsError)
	var out changeRequestOut
	unmarshalStructured(t, result, &out)
	assert.Equal(t, string(changerequest.StateMerged), out.ChangeRequest.State)
}

func TestListPullRequests(t *testing.T) {
	deps, _ := crTestDeps()
	_, err := callTool(ctxWithAuth(Scope{}), deps, "create_pull_request", map[string]any{
		"workspace_id": testWSID.String(), "source_branch": "feature", "title": "X",
	})
	require.NoError(t, err)

	result, err := callTool(ctxWithAuth(Scope{}), deps, "list_pull_requests", map[string]any{
		"workspace_id": testWSID.String(),
	})
	require.NoError(t, err)
	var out listPRsOut
	unmarshalStructured(t, result, &out)
	require.Len(t, out.ChangeRequests, 1)
}
