package kb

import (
	"context"
	"testing"

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

type servicePublicationApprovalGateway struct {
	authority  kbgovernance.PublicationAuthority
	approverID string
}

func (gateway servicePublicationApprovalGateway) SubmitPublication(ctx context.Context, command kbgovernance.SubmitPublicationCommand) (kbgovernance.PublicationState, error) {
	return gateway.authority.RecordPublicationApproval(ctx, kbgovernance.RecordPublicationApprovalCommand{
		RequestID: "kbpub_gateway", ApprovalID: "appr_publication", ApprovalVersion: 1,
		Prepared: command.Prepared,
	})
}

func (gateway servicePublicationApprovalGateway) DecidePublication(ctx context.Context, command kbgovernance.DecidePublicationCommand) (kbgovernance.PublicationState, error) {
	return gateway.authority.ResolvePublicationApproval(ctx, kbgovernance.ResolvePublicationApprovalCommand{
		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 servicePublicationApprovalGateway) WithdrawPublication(ctx context.Context, command kbgovernance.WithdrawPublicationCommand) (kbgovernance.PublicationState, error) {
	return gateway.authority.ResolvePublicationApproval(ctx, kbgovernance.ResolvePublicationApprovalCommand{
		WorkspaceID: command.WorkspaceID, ApprovalID: command.ApprovalID,
		ApprovalVersion: command.ExpectedVersion + 1, ActorPrincipalID: command.ActorPrincipalID,
		ClientDecisionID: command.ClientRequestID, Decision: kbgovernance.DecisionWithdrawn, Now: command.Now,
	})
}

func (gateway servicePublicationApprovalGateway) InspectPublication(_ context.Context, command kbgovernance.InspectPublicationApprovalCommand) (kbgovernance.PublicationApprovalReview, error) {
	approverID := gateway.approverID
	if approverID == "" {
		approverID = "owner"
	}
	return kbgovernance.PublicationApprovalReview{
		Approvers:     []kbgovernance.PublicationApprover{{PrincipalID: approverID, DisplayName: "Workspace reviewer"}},
		ActorAssigned: command.ActorPrincipalID == approverID, CanDecide: command.ActorPrincipalID == approverID,
	}, nil
}

func seedReadyMemberPublication(t *testing.T) (*KBService, storekb.KnowledgeBase) {
	t.Helper()
	svc, base, _ := seedDirectWikiGenerationSetup(t, &fakeKBWikiGenerator{})
	now := svc.now()
	_, err := svc.store.UpsertKnowledgeBaseGrant(t.Context(), storekb.KnowledgeBaseGrant{
		ID: "grant_member_publication", WorkspaceID: "ws_1", KnowledgeBaseID: base.ID,
		SubjectType: "user", SubjectID: "member", Role: "manage", RepositoryAccessProfile: "read",
		CreatedBy: "owner", UpdatedBy: "owner", CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)
	for _, kind := range []string{"wiki", "dashboard"} {
		_, err = svc.store.UpsertKnowledgeBaseSetupSourceArtifact(t.Context(), storekb.KnowledgeBaseSetupSourceArtifact{
			ID: "artifact_" + kind, WorkspaceID: "ws_1", KnowledgeBaseID: base.ID, SourceID: "src_1",
			Kind: kind, State: "skipped", UpdatedBy: "member", CreatedAt: now, UpdatedAt: now,
		})
		require.NoError(t, err)
	}
	svc.principalWorkspaceAccessVerifier = fakePrincipalWorkspaceAccessVerifier{active: true}
	svc.ConfigurePublicationApprovalGateway(servicePublicationApprovalGateway{authority: svc})
	return svc, base
}

func TestPublicationApprovalRequiresOwnerOrManagerAccess(t *testing.T) {
	svc, base := seedReadyMemberPublication(t)
	now := svc.now()
	_, err := svc.store.UpsertKnowledgeBaseGrant(t.Context(), storekb.KnowledgeBaseGrant{
		ID: "grant_member_publication", 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)
	setup, err := svc.GetKnowledgeBaseSetup(t.Context(), "ws_1", "member", base.ID)
	require.NoError(t, err)
	require.False(t, setup.CanRequestPublication)

	_, err = svc.RequestPublicationApproval(t.Context(), "ws_1", "member", base.ID, setup.Session.WorkingRevision, "publish-request-editor")
	require.Error(t, err)
}

func TestPublicationApprovalOwnsReviewPageAndPublishesExactRevision(t *testing.T) {
	svc, base := seedReadyMemberPublication(t)
	setup, err := svc.GetKnowledgeBaseSetup(t.Context(), "ws_1", "member", base.ID)
	require.NoError(t, err)
	require.Equal(t, "ready", setup.StepSummary["review"])
	require.True(t, setup.CanRequestPublication)

	pending, err := svc.RequestPublicationApproval(t.Context(), "ws_1", "member", base.ID, setup.Session.WorkingRevision, "publish-request-1")
	require.NoError(t, err)
	require.NotNil(t, pending.PublicationRequest)
	require.Equal(t, storekb.PublicationRequestStatusPendingApproval, pending.PublicationRequest.Status)
	require.True(t, pending.CanWithdrawPublication)
	require.False(t, pending.CanRequestPublication)

	adminView, err := svc.GetKnowledgeBaseSetup(t.Context(), "ws_1", "owner", base.ID)
	require.NoError(t, err)
	require.True(t, adminView.CanReviewPublication)
	require.Equal(t, []kbgovernance.PublicationApprover{{PrincipalID: "owner", DisplayName: "Workspace reviewer"}}, adminView.PublicationApprovers)
	published, err := svc.DecidePublicationApproval(t.Context(), "ws_1", "owner", base.ID, "appr_publication", 1, "publish-decision-1", kbgovernance.DecisionApproved, "")
	require.NoError(t, err)
	require.Equal(t, storekb.PublicationRequestStatusPublished, published.PublicationRequest.Status)

	updated, err := svc.GetKnowledgeBase(t.Context(), "ws_1", "owner", base.ID)
	require.NoError(t, err)
	require.Equal(t, KBPublicationOfficial, updated.PublicationState)
	require.Equal(t, setup.Session.WorkingRevision, updated.PublishedRevision)
	require.Empty(t, updated.WorkingRevision)

	officialEdit, err := svc.StartKnowledgeBaseOfficialEdit(t.Context(), "ws_1", "owner", base.ID)
	require.NoError(t, err)
	require.Equal(t, KBSetupModeEditOfficial, officialEdit.Session.Mode)
	require.Equal(t, updated.PublishedRevision, officialEdit.Session.PublishedRevision)
	require.NotEqual(t, updated.PublishedRevision, officialEdit.Session.WorkingRevision)
}

func TestPublicationRejectionRequiresReasonAndReturnsDraftToEditable(t *testing.T) {
	svc, base := seedReadyMemberPublication(t)
	setup, err := svc.GetKnowledgeBaseSetup(t.Context(), "ws_1", "member", base.ID)
	require.NoError(t, err)
	pending, err := svc.RequestPublicationApproval(t.Context(), "ws_1", "member", base.ID, setup.Session.WorkingRevision, "publish-request-reject")
	require.NoError(t, err)

	_, err = svc.DecidePublicationApproval(t.Context(), "ws_1", "owner", base.ID, pending.PublicationRequest.ApprovalID, 1, "reject-empty", kbgovernance.DecisionRejected, "")
	require.Error(t, err)
	rejected, err := svc.DecidePublicationApproval(t.Context(), "ws_1", "owner", base.ID, pending.PublicationRequest.ApprovalID, 1, "reject-valid", kbgovernance.DecisionRejected, "Sources need revision")
	require.NoError(t, err)
	require.Equal(t, storekb.PublicationRequestStatusRejected, rejected.PublicationRequest.Status)
	require.Equal(t, "Sources need revision", rejected.PublicationRequest.DecisionReason)
	require.True(t, rejected.CanRequestPublication)
}

func TestPublicationApprovalBecomesStaleWhenApplicantLosesWorkspaceAccess(t *testing.T) {
	svc, base := seedReadyMemberPublication(t)
	setup, err := svc.GetKnowledgeBaseSetup(t.Context(), "ws_1", "member", base.ID)
	require.NoError(t, err)
	pending, err := svc.RequestPublicationApproval(t.Context(), "ws_1", "member", base.ID, setup.Session.WorkingRevision, "publish-request-revoked")
	require.NoError(t, err)

	svc.principalWorkspaceAccessVerifier = fakePrincipalWorkspaceAccessVerifier{active: false}
	stale, err := svc.DecidePublicationApproval(t.Context(), "ws_1", "owner", base.ID, pending.PublicationRequest.ApprovalID, 1, "approve-revoked", kbgovernance.DecisionApproved, "")
	require.NoError(t, err)
	require.Equal(t, storekb.PublicationRequestStatusStale, stale.PublicationRequest.Status)
	require.Equal(t, "authorization_changed", stale.PublicationRequest.DecisionReason)

	unchanged, err := svc.GetKnowledgeBase(t.Context(), "ws_1", "owner", base.ID)
	require.NoError(t, err)
	require.Equal(t, KBPublicationDraft, unchanged.PublicationState)
}

func TestAssignedWorkspaceAdminReviewsFrozenPublicationWithImplicitKBManagement(t *testing.T) {
	svc, base := seedReadyMemberPublication(t)
	setup, err := svc.GetKnowledgeBaseSetup(t.Context(), "ws_1", "member", base.ID)
	require.NoError(t, err)
	pending, err := svc.RequestPublicationApproval(t.Context(), "ws_1", "member", base.ID, setup.Session.WorkingRevision, "publish-scoped-review")
	require.NoError(t, err)

	svc.workspaceGovernanceAuthorizer = fakeWorkspaceGovernanceAuthorizer{authorize: func(_ context.Context, _, principalID string) (bool, error) {
		return principalID == "reviewer", nil
	}}
	svc.ConfigurePublicationApprovalGateway(servicePublicationApprovalGateway{authority: svc, approverID: "reviewer"})
	reviewerBase, err := svc.GetKnowledgeBase(t.Context(), "ws_1", "reviewer", base.ID)
	require.NoError(t, err)
	require.Equal(t, "manage", reviewerBase.Role)
	_, err = svc.GetPublicationReview(t.Context(), "ws_1", "unassigned", pending.PublicationRequest.ApprovalID)
	require.Error(t, err)
	applicantReview, err := svc.GetPublicationReview(t.Context(), "ws_1", "member", pending.PublicationRequest.ApprovalID)
	require.NoError(t, err)
	require.False(t, applicantReview.CanDecide)

	review, err := svc.GetPublicationReview(t.Context(), "ws_1", "reviewer", pending.PublicationRequest.ApprovalID)
	require.NoError(t, err)
	require.Equal(t, base.ID, review.KnowledgeBaseID)
	require.Equal(t, "Product docs", review.Name)
	require.Len(t, review.Sources, 1)
	require.True(t, review.CanDecide)
	require.Equal(t, "reviewer", review.Approvers[0].PrincipalID)

	resolved, err := svc.DecidePublicationReview(t.Context(), "ws_1", "reviewer", pending.PublicationRequest.ApprovalID, 1, "scoped-approve", kbgovernance.DecisionApproved, "")
	require.NoError(t, err)
	require.Equal(t, storekb.PublicationRequestStatusPublished, resolved.Request.Status)
}
