package kb

import (
	"context"
	"errors"
	"testing"
	"time"

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

type fakeKBWikiGenerator struct {
	inputs []KBWikiGenerationInput
	output KBWikiGenerationOutput
	err    error
}

func (generator *fakeKBWikiGenerator) Generate(_ context.Context, input KBWikiGenerationInput) (KBWikiGenerationOutput, error) {
	generator.inputs = append(generator.inputs, input)
	return generator.output, generator.err
}

type fakeWikiGenerationTerminalGateway struct {
	commands []kbgovernance.NotifyWikiGenerationTerminalCommand
	err      error
}

func (gateway *fakeWikiGenerationTerminalGateway) NotifyWikiGenerationTerminal(_ context.Context, command kbgovernance.NotifyWikiGenerationTerminalCommand) error {
	gateway.commands = append(gateway.commands, command)
	return gateway.err
}

func seedDirectWikiGenerationSetup(t *testing.T, generator KBWikiGenerator) (*KBService, storekb.KnowledgeBase, storekb.SourceRevision) {
	t.Helper()
	svc := newKBServiceForTest(t)
	svc.wikiGenerator = generator
	base, _, err := svc.CreateKnowledgeBaseSetup(t.Context(), "ws_1", "owner", KBMutation{Name: "Product docs"})
	require.NoError(t, err)
	now := svc.now()
	_, err = svc.store.CreateSource(t.Context(), storekb.Source{
		ID: "src_1", WorkspaceID: "ws_1", KnowledgeBaseID: base.ID,
		Name: "Repository", SourceType: "file_repository", Status: "ready",
		SyncStatus: "ready", IndexStatus: "ready", Config: "{}",
		CreatedBy: "owner", UpdatedBy: "owner", CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)
	revision, err := svc.store.CreateSourceRevision(t.Context(), storekb.SourceRevision{
		ID: "rev_1", WorkspaceID: "ws_1", KnowledgeBaseID: base.ID,
		SourceID: "src_1", SourceCommitSHA: "source-commit", IsCurrent: true,
		FreshnessStatus: "current", CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)
	return svc, base, revision
}

func TestDirectWikiGenerationQueuesRunsAndPublishesExactRevision(t *testing.T) {
	generator := &fakeKBWikiGenerator{output: KBWikiGenerationOutput{
		InsightsCommitSHA: "insights-commit", PublishedWikiCommit: "wiki-commit",
	}}
	svc, base, revision := seedDirectWikiGenerationSetup(t, generator)

	initial, err := svc.GetKnowledgeBaseSetup(t.Context(), "ws_1", "owner", base.ID)
	require.NoError(t, err)
	require.True(t, initial.CanDirectWikiGeneration)
	require.Equal(t, revision.ID, initial.Sources[0].SourceRevision)

	queued, err := svc.RequestDirectWikiGeneration(t.Context(), "ws_1", "owner", base.ID, "src_1", KBWikiGenerationMutation{
		SourceRevision: revision.ID, Scope: "full", Language: "en",
		Reason: "Create onboarding documentation", IdempotencyKey: "request-1",
	})
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusQueued, queued.Sources[0].WikiState)
	require.NotNil(t, queued.Sources[0].WikiRequest)
	require.Equal(t, "Create onboarding documentation", queued.Sources[0].WikiRequest.Reason)

	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, "source-commit", generator.inputs[0].SourceCommitSHA)

	ready, err := svc.GetKnowledgeBaseSetup(t.Context(), "ws_1", "owner", base.ID)
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusReady, ready.Sources[0].WikiState)
	require.Equal(t, "wiki-commit", ready.Sources[0].WikiRequest.ArtifactRevision)
	current, err := svc.store.GetCurrentSourceRevision(t.Context(), "ws_1", base.ID, "src_1")
	require.NoError(t, err)
	require.Equal(t, "wiki-commit", current.PublishedWikiRevision)
	require.Equal(t, "insights-commit", current.InsightsCommitSHA)

	replayed, err := svc.RequestDirectWikiGeneration(t.Context(), "ws_1", "owner", base.ID, "src_1", KBWikiGenerationMutation{
		SourceRevision: revision.ID, Scope: "full", Language: "en",
		Reason: "Create onboarding documentation", IdempotencyKey: "request-1",
	})
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusReady, replayed.Sources[0].WikiState)
	_, err = svc.RequestDirectWikiGeneration(t.Context(), "ws_1", "owner", base.ID, "src_1", KBWikiGenerationMutation{
		SourceRevision: revision.ID, IdempotencyKey: "replacement-request",
	})
	require.Error(t, err)
	require.Equal(t, "kb-wiki-already-ready", Code(err))
}

func TestDirectWikiGenerationFailsClosedForMemberAndStaleRevision(t *testing.T) {
	svc, base, revision := seedDirectWikiGenerationSetup(t, &fakeKBWikiGenerator{})
	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)
	memberSetup, err := svc.GetKnowledgeBaseSetup(t.Context(), "ws_1", "member", base.ID)
	require.NoError(t, err)
	require.False(t, memberSetup.CanDirectWikiGeneration)

	_, err = svc.RequestDirectWikiGeneration(t.Context(), "ws_1", "member", base.ID, "src_1", KBWikiGenerationMutation{
		SourceRevision: revision.ID, IdempotencyKey: "member-request",
	})
	require.Error(t, err)
	require.ErrorIs(t, err, ErrForbidden)

	_, err = svc.RequestDirectWikiGeneration(t.Context(), "ws_1", "owner", base.ID, "src_1", KBWikiGenerationMutation{
		SourceRevision: "rev_stale", IdempotencyKey: "stale-request",
	})
	require.Error(t, err)
	require.Equal(t, "kb-wiki-source-revision-stale", Code(err))
	requests, err := svc.store.ListLatestGenerationRequestsBySource(t.Context(), "ws_1", base.ID, storekb.GenerationKindWiki, []string{"src_1"})
	require.NoError(t, err)
	require.Empty(t, requests)
}

func TestWikiGenerationFailureIsDurableAndSanitized(t *testing.T) {
	generator := &fakeKBWikiGenerator{err: errors.New("/private/path token=secret worker log")}
	svc, base, revision := seedDirectWikiGenerationSetup(t, generator)
	queued, err := svc.RequestDirectWikiGeneration(t.Context(), "ws_1", "owner", base.ID, "src_1", KBWikiGenerationMutation{
		SourceRevision: revision.ID, IdempotencyKey: "failed-request",
	})
	require.NoError(t, err)
	require.NotNil(t, queued.Sources[0].WikiRequest)

	_, err = svc.RunPendingWikiGenerationJobs(t.Context(), WikiGenerationRunOptions{
		Limit: 1, LeaseOwner: "wiki-worker", LeaseDuration: time.Hour,
	})
	require.NoError(t, err)
	failed, err := svc.GetKnowledgeBaseSetup(t.Context(), "ws_1", "owner", base.ID)
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusFailed, failed.Sources[0].WikiState)
	require.Equal(t, "generation_failed", failed.Sources[0].WikiRequest.DecisionReason)
	job, err := svc.store.GetJob(t.Context(), "ws_1", queued.Sources[0].WikiRequest.JobID)
	require.NoError(t, err)
	require.Equal(t, "wiki generation failed", job.Error)
	require.NotContains(t, job.Error, "private")
}

func TestWikiGenerationTerminalNotificationRetriesFromDurableRequest(t *testing.T) {
	generator := &fakeKBWikiGenerator{output: KBWikiGenerationOutput{PublishedWikiCommit: "wiki-terminal"}}
	svc, base, revision := seedDirectWikiGenerationSetup(t, generator)
	gateway := &fakeWikiGenerationTerminalGateway{err: errors.New("notification transport unavailable")}
	svc.ConfigureWikiGenerationTerminalGateway(gateway)
	queued, err := svc.RequestDirectWikiGeneration(t.Context(), "ws_1", "owner", base.ID, "src_1", KBWikiGenerationMutation{
		SourceRevision: revision.ID, IdempotencyKey: "terminal-retry",
	})
	require.NoError(t, err)
	requestID := queued.Sources[0].WikiRequest.ID

	claimed, err := svc.RunPendingWikiGenerationJobs(t.Context(), WikiGenerationRunOptions{
		Limit: 1, LeaseOwner: "wiki-worker", LeaseDuration: time.Hour, WorkspaceIDs: []string{"ws_1"},
	})
	require.Equal(t, 1, claimed)
	require.NoError(t, err)
	projected, err := svc.ProjectPendingWikiGenerationTerminalNotifications(t.Context(), 1, []string{"ws_1"})
	require.Zero(t, projected)
	require.ErrorContains(t, err, "notification transport unavailable")
	require.Len(t, gateway.commands, 1)
	request, err := svc.store.GetGenerationRequest(t.Context(), "ws_1", requestID)
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusReady, request.Status)
	require.Zero(t, request.NotificationVersion)
	terminal, err := svc.InspectWikiGenerationTerminal(t.Context(), kbgovernance.InspectWikiGenerationTerminalCommand{
		WorkspaceID: "ws_1", RequestID: requestID,
	})
	require.NoError(t, err)
	require.Equal(t, "wiki-terminal", terminal.ArtifactRevision)
	require.Equal(t, request.Version, terminal.Version)

	gateway.err = nil
	projected, err = svc.ProjectPendingWikiGenerationTerminalNotifications(t.Context(), 1, []string{"ws_1"})
	require.NoError(t, err)
	require.Equal(t, 1, projected)
	require.Len(t, gateway.commands, 2)
	require.Equal(t, request.Version, gateway.commands[1].ExpectedVersion)
	request, err = svc.store.GetGenerationRequest(t.Context(), "ws_1", requestID)
	require.NoError(t, err)
	require.Equal(t, request.Version, request.NotificationVersion)
	require.NotNil(t, request.NotifiedAt)

	_, err = svc.ProjectPendingWikiGenerationTerminalNotifications(t.Context(), 1, []string{"ws_1"})
	require.NoError(t, err)
	require.Len(t, gateway.commands, 2)
}

func TestWikiGenerationFailsDurableRequestWhenJobPayloadIsCorrupt(t *testing.T) {
	svc, base, revision := seedDirectWikiGenerationSetup(t, &fakeKBWikiGenerator{})
	queued, err := svc.RequestDirectWikiGeneration(t.Context(), "ws_1", "owner", base.ID, "src_1", KBWikiGenerationMutation{
		SourceRevision: revision.ID, IdempotencyKey: "corrupt-job",
	})
	require.NoError(t, err)
	request := queued.Sources[0].WikiRequest
	require.NotNil(t, request)
	job, err := svc.store.GetJob(t.Context(), "ws_1", request.JobID)
	require.NoError(t, err)
	job.Progress = `{not-json`
	_, err = svc.store.UpdateJob(t.Context(), job)
	require.NoError(t, err)

	_, err = svc.RunPendingWikiGenerationJobs(t.Context(), WikiGenerationRunOptions{
		Limit: 1, LeaseOwner: "wiki-worker", LeaseDuration: time.Hour,
	})
	require.NoError(t, err)
	failed, err := svc.store.GetGenerationRequest(t.Context(), "ws_1", request.ID)
	require.NoError(t, err)
	require.Equal(t, storekb.GenerationRequestStatusFailed, failed.Status)
	job, err = svc.store.GetJob(t.Context(), "ws_1", request.JobID)
	require.NoError(t, err)
	require.Equal(t, KBJobStatusFailed, job.Status)
	require.Equal(t, "wiki generation failed", job.Error)
}

func TestDirectWikiGenerationRejectsUnsupportedScope(t *testing.T) {
	svc, base, revision := seedDirectWikiGenerationSetup(t, &fakeKBWikiGenerator{})

	_, err := svc.RequestDirectWikiGeneration(t.Context(), "ws_1", "owner", base.ID, "src_1", KBWikiGenerationMutation{
		SourceRevision: revision.ID, Scope: "repository_subset", IdempotencyKey: "unsupported-scope",
	})
	require.Error(t, err)
	require.ErrorIs(t, err, ErrInvalidArgument)
}
