package kb

import (
	"encoding/json"
	"strings"
	"testing"
	"time"

	"github.com/go-git/go-git/v5/plumbing"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/store/content"
	storekb "github.com/tidbcloud/AgentTeam/internal/store/kb"
	"github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
)

func TestKBSourceNativeActivationMigratesLegacyDashboardAndSwitchesGroupOnce(t *testing.T) {
	fixture := newKBSourceContentMigrationFixture(t, true)
	source, err := fixture.service.RunExact(t.Context(), fixture.request)
	require.NoError(t, err)

	dashboardBytes := []byte(`{"graph":true}`)
	legacyDigest := plumbing.ComputeHash(plumbing.BlobObject, dashboardBytes).String()
	manifest := UADashboardManifest{
		SchemaVersion: 1, SourceCommitSHA: fixture.request.CommitSHA, DashboardCommitSHA: "legacy-dashboard-commit",
	}
	for _, path := range []string{
		UADashboardConfigFile, UADashboardDomainGraphFile, UADashboardFingerprintsFile,
		UADashboardKnowledgeGraphFile, UADashboardMetaFile,
	} {
		manifest.Files = append(manifest.Files, UADashboardManifestFile{Path: path, Bytes: int64(len(dashboardBytes)), BlobSHA: legacyDigest})
	}
	encoded, err := json.Marshal(manifest)
	require.NoError(t, err)
	publishedAt := time.Date(2026, 8, 22, 8, 0, 0, 0, time.UTC)
	require.NoError(t, fixture.db.Model(&storekb.SourceRevision{}).Where("id = ?", fixture.request.SourceRevisionID).Updates(map[string]any{
		"ua_dashboard_commit_sha": "legacy-dashboard-commit", "ua_dashboard_manifest_json": string(encoded),
		"ua_dashboard_published_at": publishedAt,
	}).Error)
	_, err = fixture.repos.Upsert(t.Context(), repos.ResourceRepository{
		ID: "rr_insights", WorkspaceID: fixture.request.WorkspaceID, ResourceType: repos.ResourceTypeKBSource,
		ResourceID: fixture.request.SourceID, RepoRole: repos.RepoRoleInsights,
		OwnerKind: "workspace", RepoOwner: "org", RepoName: "insights", RepoFullName: "org/insights",
		AGSRepoID: "84", DefaultRef: "main", CreatedBy: "owner", UpdatedBy: "owner", CreatedAt: publishedAt, UpdatedAt: publishedAt,
	})
	require.NoError(t, err)

	legacy := &fakeUADashboardRepository{content: dashboardBytes}
	authority := content.New(fixture.db)
	native := NativeUADashboardRepository{Content: fixture.authorized}
	activation, err := NewKBSourceNativeActivationService(KBSourceNativeActivationOptions{
		Enabled: true, Store: fixture.store, Repositories: fixture.repos, Authorizer: fixture.authorizer,
		Content: fixture.authorized, LegacyDashboard: legacy, NativeDashboard: native, Authority: authority,
		Now: func() time.Time { return time.Date(2026, 8, 22, 8, 1, 0, 0, time.UTC) },
	})
	require.NoError(t, err)
	request := KBSourceNativeActivationRequest{
		WorkspaceID: fixture.request.WorkspaceID, KnowledgeBaseID: fixture.request.KnowledgeBaseID,
		SourceID: fixture.request.SourceID, SourceRevisionID: fixture.request.SourceRevisionID,
		ExpectedSourceContentVersionID:   source.ContentVersionID,
		ExpectedUADashboardPublicationID: "legacy-dashboard-commit", Actor: fixture.request.Actor,
	}
	activated, err := activation.RunExact(t.Context(), request)
	require.NoError(t, err)
	require.True(t, activated.DashboardMigrated)
	require.True(t, validKBSourceCutoverVersionID(activated.UADashboardVersionID))
	require.Equal(t, 5, legacy.readCalls)

	storedAuthority, found, err := authority.GetAuthority(t.Context(), request.WorkspaceID, kbSourceContentDomain, request.SourceID)
	require.NoError(t, err)
	require.True(t, found)
	require.Equal(t, content.AuthorityStateNativeActive, storedAuthority.State)
	checkpoint, err := decodeKBSourceCutoverCheckpoint(storedAuthority.Checkpoint)
	require.NoError(t, err)
	require.Equal(t, source.ContentVersionID, checkpoint.SourceContentVersionID)
	require.Equal(t, activated.UADashboardVersionID, checkpoint.UADashboardVersionID)
	persisted, err := fixture.store.GetCurrentSourceRevision(t.Context(), request.WorkspaceID, request.KnowledgeBaseID, request.SourceID)
	require.NoError(t, err)
	require.Equal(t, "legacy-dashboard-commit", persisted.UADashboardCommitSHA, "activation must not expose a native pointer before the authority flip")

	service := NewKBServiceWithOptions(fixture.store, time.Now, KBServiceOptions{
		ContentAuthority: authority, NativeUADashboardRepository: native,
		PrincipalWorkspaceAccessVerifier: fixture.workspace, WorkspaceGovernanceAuthorizer: fixture.workspace,
	})
	status, err := service.GetUADashboardStatus(t.Context(), request.WorkspaceID, request.Actor, request.KnowledgeBaseID, request.SourceID)
	require.NoError(t, err)
	require.Equal(t, activated.UADashboardVersionID, status.DashboardCommitSHA)
	file, err := service.ReadUADashboardFile(t.Context(), request.WorkspaceID, request.Actor, request.KnowledgeBaseID, request.SourceID, UADashboardKnowledgeGraphFile)
	require.NoError(t, err)
	require.Equal(t, dashboardBytes, file.Content)
	require.Equal(t, 5, legacy.readCalls, "native routing must not call AGS after activation")
	_, err = service.GetUADashboardSourcePreview(t.Context(), request.WorkspaceID, request.Actor, request.KnowledgeBaseID, request.SourceID)
	require.Equal(t, "dashboard-source-preview-unavailable", Code(err))

	replayed, err := activation.RunExact(t.Context(), request)
	require.NoError(t, err)
	require.True(t, replayed.Replayed)
	require.Equal(t, activated.UADashboardVersionID, replayed.UADashboardVersionID)
	require.Equal(t, 5, legacy.readCalls, "activation replay must not reread AGS")
}

func TestKBSourceNativeActivationFailsClosedBeforeAuthorityWhenGroupIsIncomplete(t *testing.T) {
	fixture := newKBSourceContentMigrationFixture(t, true)
	source, err := fixture.service.RunExact(t.Context(), fixture.request)
	require.NoError(t, err)
	require.NoError(t, fixture.db.Model(&storekb.SourceRevision{}).Where("id = ?", fixture.request.SourceRevisionID).
		Update("source_content_verified_at", nil).Error)
	authority := content.New(fixture.db)
	activation, err := NewKBSourceNativeActivationService(KBSourceNativeActivationOptions{
		Enabled: true, Store: fixture.store, Authorizer: fixture.authorizer, Content: fixture.authorized,
		NativeDashboard: NativeUADashboardRepository{Content: fixture.authorized}, Authority: authority,
	})
	require.NoError(t, err)
	_, err = activation.RunExact(t.Context(), KBSourceNativeActivationRequest{
		WorkspaceID: fixture.request.WorkspaceID, KnowledgeBaseID: fixture.request.KnowledgeBaseID,
		SourceID: fixture.request.SourceID, SourceRevisionID: fixture.request.SourceRevisionID,
		ExpectedSourceContentVersionID: source.ContentVersionID, Actor: fixture.request.Actor,
	})
	require.ErrorContains(t, err, "target changed")
	_, found, err := authority.GetAuthority(t.Context(), fixture.request.WorkspaceID, kbSourceContentDomain, fixture.request.SourceID)
	require.NoError(t, err)
	require.False(t, found)
}

func TestKBSourceNativeActivationRequiresLegacyWritersDrained(t *testing.T) {
	for _, test := range []struct {
		name      string
		configure func(*testing.T, *kbSourceMigrationFixture)
		want      string
	}{
		{
			name: "enabled mirror",
			configure: func(t *testing.T, fixture *kbSourceMigrationFixture) {
				require.NoError(t, fixture.db.Create(&storekb.SourceMirror{
					SourceID: fixture.request.SourceID, WorkspaceID: fixture.request.WorkspaceID,
					KnowledgeBaseID: fixture.request.KnowledgeBaseID, Enabled: true,
					CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
				}).Error)
			},
			want: "mirror must be disabled",
		},
		{
			name: "queued wiki job",
			configure: func(t *testing.T, fixture *kbSourceMigrationFixture) {
				now := time.Now().UTC()
				_, err := fixture.store.CreateJob(t.Context(), storekb.Job{
					ID: "job_pending", WorkspaceID: fixture.request.WorkspaceID,
					KnowledgeBaseID: fixture.request.KnowledgeBaseID, SourceID: fixture.request.SourceID,
					JobType: KBJobTypeGenerateWiki, Status: KBJobStatusQueued,
					Progress: `{}`, CreatedAt: now, UpdatedAt: now,
				})
				require.NoError(t, err)
			},
			want: "jobs must be drained",
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			fixture := newKBSourceContentMigrationFixture(t, true)
			source, err := fixture.service.RunExact(t.Context(), fixture.request)
			require.NoError(t, err)
			test.configure(t, fixture)
			authority := content.New(fixture.db)
			activation, err := NewKBSourceNativeActivationService(KBSourceNativeActivationOptions{
				Enabled: true, Store: fixture.store, Authorizer: fixture.authorizer, Content: fixture.authorized,
				NativeDashboard: NativeUADashboardRepository{Content: fixture.authorized}, Authority: authority,
			})
			require.NoError(t, err)
			_, err = activation.RunExact(t.Context(), KBSourceNativeActivationRequest{
				WorkspaceID: fixture.request.WorkspaceID, KnowledgeBaseID: fixture.request.KnowledgeBaseID,
				SourceID: fixture.request.SourceID, SourceRevisionID: fixture.request.SourceRevisionID,
				ExpectedSourceContentVersionID: source.ContentVersionID, Actor: fixture.request.Actor,
			})
			require.ErrorContains(t, err, test.want)
			_, found, err := authority.GetAuthority(t.Context(), fixture.request.WorkspaceID, kbSourceContentDomain, fixture.request.SourceID)
			require.NoError(t, err)
			require.False(t, found)
		})
	}
}

func TestKBSourceCutoverCheckpointRejectsNonVersionIdentity(t *testing.T) {
	_, err := encodeKBSourceCutoverCheckpoint(kbSourceCutoverCheckpoint{SourceContentVersionID: "legacy-sha"})
	require.Error(t, err)
	_, err = decodeKBSourceCutoverCheckpoint(kbSourceCutoverCheckpointPrefix + strings.Repeat("x", 65) + "|")
	require.Error(t, err)
}
