package kb

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/kbgovernance"
	storekb "github.com/tidbcloud/AgentTeam/internal/store/kb"
)

type serviceWikiApprovalGateway struct {
	authority kbgovernance.WikiGenerationAuthority
}

func (gateway serviceWikiApprovalGateway) SubmitWikiGeneration(ctx context.Context, command kbgovernance.SubmitWikiGenerationCommand) (kbgovernance.WikiGenerationState, error) {
	return gateway.authority.RecordWikiGenerationApproval(ctx, kbgovernance.RecordWikiApprovalCommand{
		RequestID: "kbgr_gateway", ApprovalID: "appr_gateway", ApprovalVersion: 1,
		Prepared: command.Prepared,
	})
}

func (gateway serviceWikiApprovalGateway) DecideWikiGeneration(ctx context.Context, command kbgovernance.DecideWikiGenerationCommand) (kbgovernance.WikiGenerationState, error) {
	return gateway.authority.ResolveWikiGenerationApproval(ctx, kbgovernance.ResolveWikiApprovalCommand{
		WorkspaceID: command.WorkspaceID, ApprovalID: command.ApprovalID,
		ApprovalVersion: command.ExpectedVersion + 1, ActorPrincipalID: command.ActorPrincipalID,
		ClientDecisionID: command.ClientDecisionID, Decision: command.Decision, Reason: command.Reason, Now: command.Now,
	})
}

func (gateway serviceWikiApprovalGateway) WithdrawWikiGeneration(ctx context.Context, command kbgovernance.WithdrawWikiGenerationCommand) (kbgovernance.WikiGenerationState, error) {
	return gateway.authority.ResolveWikiGenerationApproval(ctx, kbgovernance.ResolveWikiApprovalCommand{
		WorkspaceID: command.WorkspaceID, ApprovalID: command.ApprovalID,
		ApprovalVersion: command.ExpectedVersion + 1, ActorPrincipalID: command.ActorPrincipalID,
		ClientDecisionID: command.ClientRequestID, Decision: kbgovernance.DecisionWithdrawn, Now: command.Now,
	})
}

func prepareMemberWikiGeneration(t *testing.T, svc *KBService, base storekb.KnowledgeBase, revision storekb.SourceRevision) kbgovernance.PreparedWikiGeneration {
	t.Helper()
	now := svc.now()
	_, err := svc.store.UpsertKnowledgeBaseGrant(t.Context(), storekb.KnowledgeBaseGrant{
		ID: "grant_member", WorkspaceID: "ws_1", KnowledgeBaseID: base.ID,
		SubjectType: "user", SubjectID: "member", Role: "edit",
		RepositoryAccessProfile: "read", CreatedBy: "owner", UpdatedBy: "owner",
		CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)
	svc.principalWorkspaceAccessVerifier = fakePrincipalWorkspaceAccessVerifier{active: true}
	prepared, err := svc.PrepareWikiGeneration(t.Context(), kbgovernance.PrepareWikiGenerationCommand{
		WorkspaceID: "ws_1", KnowledgeBaseID: base.ID, SourceID: "src_1",
		SourceRevision: revision.ID, Scope: "full", Language: "en",
		Reason: "Document the repository", RequesterID: "member",
		IdempotencyKey: "member-request-1", CorrelationID: "trace-1",
	})
	require.NoError(t, err)
	return prepared
}

func recordMemberWikiApproval(t *testing.T, svc *KBService, prepared kbgovernance.PreparedWikiGeneration) kbgovernance.WikiGenerationState {
	t.Helper()
	state, err := svc.RecordWikiGenerationApproval(t.Context(), kbgovernance.RecordWikiApprovalCommand{
		RequestID: "kbgr_member", ApprovalID: "appr_member", ApprovalVersion: 1,
		Prepared: prepared, Now: svc.now(),
	})
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusPendingApproval, state.Status)
	require.Equal(t, int64(1), state.ApprovalVersion)
	return state
}

func TestMemberWikiApprovalGatewayOwnsSetupPageLifecycle(t *testing.T) {
	svc, base, revision := seedDirectWikiGenerationSetup(t, &fakeKBWikiGenerator{})
	prepareMemberWikiGeneration(t, svc, base, revision)
	svc.ConfigureWikiGenerationApprovalGateway(serviceWikiApprovalGateway{authority: svc})

	memberSetup, err := svc.GetKnowledgeBaseSetup(t.Context(), "ws_1", "member", base.ID)
	require.NoError(t, err)
	require.True(t, memberSetup.CanRequestWikiGeneration)
	require.False(t, memberSetup.CanDirectWikiGeneration)

	pending, err := svc.RequestWikiGenerationApproval(t.Context(), "ws_1", "member", base.ID, "src_1", KBWikiGenerationMutation{
		SourceRevision: revision.ID, Scope: "full", Language: "en",
		Reason: "Document the repository", IdempotencyKey: "gateway-request-1",
	})
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusPendingApproval, pending.Sources[0].WikiState)
	require.Equal(t, "appr_gateway", pending.Sources[0].WikiRequest.ApprovalID)
	require.Equal(t, int64(1), pending.Sources[0].WikiRequest.ApprovalVersion)

	queued, err := svc.DecideWikiGenerationApproval(t.Context(), "ws_1", "owner", base.ID, "src_1", "appr_gateway", 1, "decision-gateway", kbgovernance.DecisionApproved, "")
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusQueued, queued.Sources[0].WikiState)
}

func TestMemberWikiApprovalQueuesAndRunsExactFrozenRevision(t *testing.T) {
	generator := &fakeKBWikiGenerator{output: KBWikiGenerationOutput{
		InsightsCommitSHA: "insights-commit", PublishedWikiCommit: "wiki-commit",
	}}
	svc, base, revision := seedDirectWikiGenerationSetup(t, generator)
	prepared := prepareMemberWikiGeneration(t, svc, base, revision)
	require.Equal(t, "member", prepared.Payload.RequesterID)
	require.Equal(t, revision.ID, prepared.Payload.SourceRevision)
	require.Equal(t, revision.SourceCommitSHA, prepared.Payload.SourceCommitSHA)
	require.NotEmpty(t, prepared.PayloadDigest)

	pending := recordMemberWikiApproval(t, svc, prepared)
	decision := kbgovernance.ResolveWikiApprovalCommand{
		WorkspaceID: "ws_1", ApprovalID: pending.ApprovalID, ApprovalVersion: 2,
		ActorPrincipalID: "owner", ClientDecisionID: "decision-1",
		Decision: kbgovernance.DecisionApproved, Now: svc.now().Add(time.Minute),
	}
	resolved, err := svc.ResolveWikiGenerationApproval(t.Context(), decision)
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusQueued, resolved.Status)
	require.Equal(t, int64(2), resolved.ApprovalVersion)

	claimed, err := svc.RunPendingWikiGenerationJobs(t.Context(), WikiGenerationRunOptions{
		Limit: 1, LeaseOwner: "wiki-worker", LeaseDuration: time.Hour,
	})
	require.NoError(t, err)
	require.Equal(t, 1, claimed)
	require.Len(t, generator.inputs, 1)
	require.Equal(t, revision.ID, generator.inputs[0].SourceRevision)
	require.Equal(t, revision.SourceCommitSHA, generator.inputs[0].SourceCommitSHA)
	request, err := svc.store.GetGenerationRequestByApproval(t.Context(), "ws_1", pending.ApprovalID)
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusReady, request.Status)
	svc.workspaceGovernanceAuthorizer = fakeWorkspaceGovernanceAuthorizer{}
	replayed, err := svc.ResolveWikiGenerationApproval(t.Context(), decision)
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusReady, replayed.Status)
}

func TestWikiApprovalPreparationSeparatesAdminDirectPathAndRejectsTampering(t *testing.T) {
	svc, base, revision := seedDirectWikiGenerationSetup(t, &fakeKBWikiGenerator{})
	_, err := svc.PrepareWikiGeneration(t.Context(), kbgovernance.PrepareWikiGenerationCommand{
		WorkspaceID: "ws_1", KnowledgeBaseID: base.ID, SourceID: "src_1",
		SourceRevision: revision.ID, RequesterID: "owner", IdempotencyKey: "admin-request",
	})
	require.Error(t, err)
	require.Equal(t, "kb-wiki-admin-direct-required", Code(err))

	prepared := prepareMemberWikiGeneration(t, svc, base, revision)
	tampered := prepared
	tampered.PayloadJSON = append([]byte(nil), prepared.PayloadJSON...)
	tampered.PayloadJSON[len(tampered.PayloadJSON)-1] = ' '
	_, err = svc.RecordWikiGenerationApproval(t.Context(), kbgovernance.RecordWikiApprovalCommand{
		RequestID: "kbgr_tampered", ApprovalID: "appr_tampered", ApprovalVersion: 1,
		Prepared: tampered,
	})
	require.Error(t, err)
	require.Equal(t, "kb-wiki-approval-payload-mismatch", Code(err))
}

func TestWikiApprovalDecisionAuthorizationAndWithdrawal(t *testing.T) {
	for _, test := range []struct {
		name, actor, decision, reason string
		wantForbidden                 bool
	}{
		{name: "member cannot approve", actor: "member", decision: kbgovernance.DecisionApproved, wantForbidden: true},
		{name: "member cannot reject", actor: "member", decision: kbgovernance.DecisionRejected, reason: "not ready", wantForbidden: true},
		{name: "other principal cannot withdraw", actor: "other", decision: kbgovernance.DecisionWithdrawn, wantForbidden: true},
		{name: "applicant can withdraw", actor: "member", decision: kbgovernance.DecisionWithdrawn},
	} {
		t.Run(test.name, func(t *testing.T) {
			svc, base, revision := seedDirectWikiGenerationSetup(t, &fakeKBWikiGenerator{})
			prepared := prepareMemberWikiGeneration(t, svc, base, revision)
			pending := recordMemberWikiApproval(t, svc, prepared)
			state, err := svc.ResolveWikiGenerationApproval(t.Context(), kbgovernance.ResolveWikiApprovalCommand{
				WorkspaceID: "ws_1", ApprovalID: pending.ApprovalID, ApprovalVersion: 2,
				ActorPrincipalID: test.actor, ClientDecisionID: "decision-1",
				Decision: test.decision, Reason: test.reason,
			})
			if test.wantForbidden {
				require.ErrorIs(t, err, ErrForbidden)
				return
			}
			require.NoError(t, err)
			require.Equal(t, storekb.GenerationRequestStatusWithdrawn, state.Status)
		})
	}
}

func TestWikiApprovalRevisionChangeBecomesStaleWithoutJob(t *testing.T) {
	svc, base, revision := seedDirectWikiGenerationSetup(t, &fakeKBWikiGenerator{})
	prepared := prepareMemberWikiGeneration(t, svc, base, revision)
	pending := recordMemberWikiApproval(t, svc, prepared)
	now := svc.now().Add(time.Minute)
	_, _, err := svc.store.AdvanceSourceRevision(t.Context(), storekb.AdvanceSourceRevisionParams{
		ExpectedCurrentSourceCommitSHA: revision.SourceCommitSHA, EnforceExpectedCurrent: true,
		Revision: storekb.SourceRevision{
			ID: "rev_2", WorkspaceID: "ws_1", KnowledgeBaseID: base.ID,
			SourceID: "src_1", SourceCommitSHA: "changed-commit",
			FreshnessStatus: "current", CreatedAt: now, UpdatedAt: now,
		},
	})
	require.NoError(t, err)

	state, err := svc.ResolveWikiGenerationApproval(t.Context(), kbgovernance.ResolveWikiApprovalCommand{
		WorkspaceID: "ws_1", ApprovalID: pending.ApprovalID, ApprovalVersion: 2,
		ActorPrincipalID: "owner", ClientDecisionID: "decision-stale",
		Decision: kbgovernance.DecisionApproved, Now: now.Add(time.Minute),
	})
	require.NoError(t, err)
	require.True(t, state.Stale)
	require.Equal(t, storekb.GenerationRequestStatusStale, state.Status)
	jobs, err := svc.store.ListSourceJobs(t.Context(), "ws_1", base.ID, "src_1", KBJobTypeGenerateWiki, 1, 10)
	require.NoError(t, err)
	require.Empty(t, jobs)
}

func TestWikiApprovalRejectsInvalidDecisionBeforeMutation(t *testing.T) {
	svc, base, revision := seedDirectWikiGenerationSetup(t, &fakeKBWikiGenerator{})
	prepared := prepareMemberWikiGeneration(t, svc, base, revision)
	pending := recordMemberWikiApproval(t, svc, prepared)

	for _, command := range []kbgovernance.ResolveWikiApprovalCommand{
		{
			WorkspaceID: "ws_1", ApprovalID: pending.ApprovalID, ApprovalVersion: 2,
			ActorPrincipalID: "owner", ClientDecisionID: "unknown-decision",
			Decision: "maybe",
		},
		{
			WorkspaceID: "ws_1", ApprovalID: pending.ApprovalID, ApprovalVersion: 2,
			ActorPrincipalID: "owner", ClientDecisionID: "empty-reason",
			Decision: kbgovernance.DecisionRejected,
		},
	} {
		_, err := svc.ResolveWikiGenerationApproval(t.Context(), command)
		require.ErrorIs(t, err, ErrInvalidArgument)
	}
	stored, err := svc.store.GetGenerationRequestByApproval(t.Context(), "ws_1", pending.ApprovalID)
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusPendingApproval, stored.Status)
}
