package kb

import (
	"context"
	"testing"

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

func TestOfficialEditIsolatedBasicInfoAndAtomicDirectSave(t *testing.T) {
	ctx := context.Background()
	svc := newKBServiceForTest(t)
	svc.workspaceGovernanceAuthorizer = fakeWorkspaceGovernanceAuthorizer{authorize: func(_ context.Context, _, principalID string) (bool, error) {
		return principalID == "admin", nil
	}}
	tag, err := svc.CreateKnowledgeBaseTag(ctx, "ws_1", "admin", KnowledgeBaseTaxonomyMutation{Name: "Official tag"})
	require.NoError(t, err)
	base, err := svc.CreateKnowledgeBase(ctx, "ws_1", "admin", KBMutation{Name: "Published name", Description: "Published description"})
	require.NoError(t, err)
	entryView, err := svc.GetKnowledgeBase(ctx, "ws_1", "admin", base.ID)
	require.NoError(t, err)
	require.True(t, entryView.CanEditOfficial)
	_, err = svc.UpdateKnowledgeBase(ctx, "ws_1", "admin", base.ID, KBMutation{Name: "Bypassed name", Description: base.Description})
	require.ErrorIs(t, err, ErrConflict)
	publishedRevision := base.PublishedRevision
	now := svc.now()
	_, err = svc.store.CreateSource(ctx, storekb.Source{
		ID: "src_official", WorkspaceID: base.WorkspaceID, KnowledgeBaseID: base.ID,
		SourceType: "codebase", Status: "ready", SyncStatus: "ready", Name: "agentteam",
		CreatedBy: "admin", UpdatedBy: "admin", CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)

	setup, err := svc.StartKnowledgeBaseOfficialEdit(ctx, "ws_1", "admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, KBSetupModeEditOfficial, setup.Session.Mode)
	require.Equal(t, publishedRevision, setup.Session.PublishedRevision)
	require.NotEqual(t, publishedRevision, setup.Session.WorkingRevision)
	require.True(t, setup.CanDirectDashboardImport)

	setup, err = svc.SetKnowledgeBaseSetupSourceArtifact(ctx, "ws_1", "admin", base.ID, "src_official", "wiki", "skipped")
	require.NoError(t, err)
	setup, err = svc.SetKnowledgeBaseSetupSourceArtifact(ctx, "ws_1", "admin", base.ID, "src_official", "dashboard", "skipped")
	require.NoError(t, err)
	require.Equal(t, "ready", setup.StepSummary["review"])

	setup, err = svc.UpdateKnowledgeBaseOfficialEditBasic(ctx, "ws_1", "admin", base.ID, KBOfficialEditBasicMutation{
		Name: "Working name", Description: "Working description", GroupID: setup.BasicInfo.GroupID,
		TagIDs: []string{tag.ID}, ExpectedVersion: setup.Session.Version,
	})
	require.NoError(t, err)
	require.Equal(t, "Working name", setup.BasicInfo.Name)
	require.Contains(t, setup.OfficialChangeFields, "name")
	readerView, err := svc.GetKnowledgeBase(ctx, "ws_1", "admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, "Published name", readerView.Name)
	require.Equal(t, publishedRevision, readerView.PublishedRevision)
	require.Equal(t, base.UpdatedAt, readerView.UpdatedAt)
	originalDigest := setup.OfficialContentDigest
	changedArtifacts, err := svc.SetKnowledgeBaseSetupSourceArtifact(ctx, "ws_1", "admin", base.ID, "src_official", "wiki", "not_requested")
	require.NoError(t, err)
	require.Equal(t, setup.Session.Version, changedArtifacts.Session.Version)
	require.NotEqual(t, originalDigest, changedArtifacts.OfficialContentDigest)
	_, err = svc.SaveKnowledgeBaseOfficialEdit(ctx, "ws_1", "admin", base.ID, KBOfficialEditSaveMutation{
		WorkingRevision: setup.Session.WorkingRevision, PublishedRevision: setup.Session.PublishedRevision,
		ContentDigest: originalDigest, ExpectedVersion: setup.Session.Version, ClientRequestID: "stale-content",
	})
	require.ErrorIs(t, err, ErrConflict)
	setup, err = svc.SetKnowledgeBaseSetupSourceArtifact(ctx, "ws_1", "admin", base.ID, "src_official", "wiki", "skipped")
	require.NoError(t, err)
	require.Equal(t, originalDigest, setup.OfficialContentDigest)
	require.NoError(t, svc.store.DB().Where("workspace_id = ? AND id = ?", "ws_1", tag.ID).Delete(&storekb.KnowledgeBaseTag{}).Error)
	_, err = svc.SaveKnowledgeBaseOfficialEdit(ctx, "ws_1", "admin", base.ID, KBOfficialEditSaveMutation{
		WorkingRevision: setup.Session.WorkingRevision, PublishedRevision: setup.Session.PublishedRevision,
		ContentDigest:   setup.OfficialContentDigest,
		ExpectedVersion: setup.Session.Version, ClientRequestID: "stale-taxonomy",
	})
	require.ErrorIs(t, err, ErrConflict)
	readerView, err = svc.GetKnowledgeBase(ctx, "ws_1", "admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, "Published name", readerView.Name)
	require.Equal(t, publishedRevision, readerView.PublishedRevision)
	_, err = svc.store.CreateKnowledgeBaseTag(ctx, tag)
	require.NoError(t, err)

	rollbackAudit := svc.auditEvent(base.WorkspaceID, base.ID, "admin", "kb.official_edit.saved", "knowledge_base", base.ID, "Forced late save failure", "")
	_, err = svc.store.CreateAuditEvent(ctx, rollbackAudit)
	require.NoError(t, err)
	_, err = svc.store.SaveKnowledgeBaseOfficialEdit(ctx, storekb.SaveKnowledgeBaseOfficialEditParams{
		WorkspaceID: base.WorkspaceID, KnowledgeBaseID: base.ID,
		WorkingRevision: setup.Session.WorkingRevision, PublishedRevision: setup.Session.PublishedRevision,
		ExpectedContentDigest: setup.OfficialContentDigest,
		ExpectedVersion:       setup.Session.Version, ClientRequestID: "late-failure",
		ActorID: "admin", AuditEvent: rollbackAudit, Now: svc.now(),
	})
	require.Error(t, err)
	readerView, err = svc.GetKnowledgeBase(ctx, "ws_1", "admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, "Published name", readerView.Name)
	require.Equal(t, publishedRevision, readerView.PublishedRevision)
	uncompleted, err := svc.store.GetKnowledgeBaseSetupSession(ctx, "ws_1", base.ID)
	require.NoError(t, err)
	require.Nil(t, uncompleted.CompletedAt)
	require.Empty(t, uncompleted.SaveRequestID)

	_, err = svc.SaveKnowledgeBaseOfficialEdit(ctx, "ws_1", "admin", base.ID, KBOfficialEditSaveMutation{
		WorkingRevision: setup.Session.WorkingRevision, PublishedRevision: setup.Session.PublishedRevision,
		ContentDigest:   setup.OfficialContentDigest,
		ExpectedVersion: setup.Session.Version + 1, ClientRequestID: "save-official-1",
	})
	require.ErrorIs(t, err, ErrConflict)
	readerView, err = svc.GetKnowledgeBase(ctx, "ws_1", "admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, "Published name", readerView.Name)
	require.Equal(t, publishedRevision, readerView.PublishedRevision)

	require.NoError(t, svc.store.DB().Model(&storekb.KnowledgeBase{}).
		Where("workspace_id = ? AND id = ?", base.WorkspaceID, base.ID).
		Update("is_active", false).Error)
	_, err = svc.SaveKnowledgeBaseOfficialEdit(ctx, "ws_1", "admin", base.ID, KBOfficialEditSaveMutation{
		WorkingRevision: setup.Session.WorkingRevision, PublishedRevision: setup.Session.PublishedRevision,
		ContentDigest:   setup.OfficialContentDigest,
		ExpectedVersion: setup.Session.Version, ClientRequestID: "archived-save",
	})
	require.ErrorIs(t, err, ErrConflict)
	require.NoError(t, svc.store.DB().Model(&storekb.KnowledgeBase{}).
		Where("workspace_id = ? AND id = ?", base.WorkspaceID, base.ID).
		Update("is_active", true).Error)

	saved, err := svc.SaveKnowledgeBaseOfficialEdit(ctx, "ws_1", "admin", base.ID, KBOfficialEditSaveMutation{
		WorkingRevision: setup.Session.WorkingRevision, PublishedRevision: setup.Session.PublishedRevision,
		ContentDigest:   setup.OfficialContentDigest,
		ExpectedVersion: setup.Session.Version, ClientRequestID: "save-official-1",
	})
	require.NoError(t, err)
	require.Equal(t, "Working name", saved.Name)
	require.Equal(t, setup.Session.WorkingRevision, saved.PublishedRevision)
	require.Empty(t, saved.WorkingRevision)
	_, err = svc.store.CreateSource(ctx, storekb.Source{
		ID: "src_after_official_save", WorkspaceID: base.WorkspaceID, KnowledgeBaseID: base.ID,
		SourceType: "codebase", Status: "ready", SyncStatus: "ready", Name: "later source",
		CreatedBy: "admin", UpdatedBy: "admin", CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)

	replayed, err := svc.SaveKnowledgeBaseOfficialEdit(ctx, "ws_1", "admin", base.ID, KBOfficialEditSaveMutation{
		WorkingRevision: setup.Session.WorkingRevision, PublishedRevision: setup.Session.PublishedRevision,
		ContentDigest:   setup.OfficialContentDigest,
		ExpectedVersion: setup.Session.Version, ClientRequestID: "save-official-1",
	})
	require.NoError(t, err)
	require.Equal(t, saved.PublishedRevision, replayed.PublishedRevision)
	completed, err := svc.store.GetKnowledgeBaseSetupSession(ctx, "ws_1", base.ID)
	require.NoError(t, err)
	require.NotNil(t, completed.CompletedAt)
	require.Equal(t, "save-official-1", completed.SaveRequestID)
	_, err = svc.UpdateKnowledgeBaseSetupStep(ctx, "ws_1", "admin", base.ID, "basic", completed.Version)
	require.ErrorIs(t, err, ErrConflict)
	_, err = svc.SetKnowledgeBaseSetupSourceArtifact(ctx, "ws_1", "admin", base.ID, "src_official", "wiki", "not_requested")
	require.ErrorIs(t, err, ErrConflict)
}

func TestOfficialEditAllowsKnowledgeBaseOwnerAndWorkspaceAdmin(t *testing.T) {
	ctx := context.Background()
	svc := newKBServiceForTest(t)
	svc.workspaceGovernanceAuthorizer = fakeWorkspaceGovernanceAuthorizer{authorize: func(_ context.Context, _, principalID string) (bool, error) {
		return principalID == "admin", nil
	}}
	base, err := svc.CreateKnowledgeBase(ctx, "ws_1", "owner", KBMutation{Name: "Official"})
	require.NoError(t, err)
	now := svc.now()
	_, err = svc.store.UpsertKnowledgeBaseGrant(ctx, storekb.KnowledgeBaseGrant{
		ID: "grant_admin_view", WorkspaceID: "ws_1", KnowledgeBaseID: base.ID,
		SubjectType: "user", SubjectID: "admin", Role: "view", CreatedBy: "owner", UpdatedBy: "owner",
		CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)
	_, err = svc.store.UpsertKnowledgeBaseGrant(ctx, storekb.KnowledgeBaseGrant{
		ID: "grant_manager", WorkspaceID: "ws_1", KnowledgeBaseID: base.ID,
		SubjectType: "user", SubjectID: "manager", Role: "manage", CreatedBy: "owner", UpdatedBy: "owner",
		CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)

	ownerView, err := svc.GetKnowledgeBase(ctx, "ws_1", "owner", base.ID)
	require.NoError(t, err)
	require.True(t, ownerView.CanEditOfficial)
	adminView, err := svc.GetKnowledgeBase(ctx, "ws_1", "admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, "manage", adminView.Role)
	require.True(t, adminView.CanManage)
	require.True(t, adminView.CanEditOfficial)
	managerView, err := svc.GetKnowledgeBase(ctx, "ws_1", "manager", base.ID)
	require.NoError(t, err)
	require.False(t, managerView.CanEditOfficial)
	_, err = svc.StartKnowledgeBaseOfficialEdit(ctx, "ws_1", "manager", base.ID)
	require.ErrorIs(t, err, ErrForbidden)

	_, err = svc.StartKnowledgeBaseOfficialEdit(ctx, "ws_1", "owner", base.ID)
	require.NoError(t, err)
	stored, err := svc.store.GetKnowledgeBase(ctx, "ws_1", base.ID)
	require.NoError(t, err)
	require.NotEmpty(t, stored.WorkingRevision)

	otherBase, err := svc.CreateKnowledgeBase(ctx, "ws_1", "other-owner", KBMutation{Name: "Other Official"})
	require.NoError(t, err)
	_, err = svc.StartKnowledgeBaseOfficialEdit(ctx, "ws_1", "admin", otherBase.ID)
	require.NoError(t, err)
}

func TestOrganizationAdminConvertsAnotherOwnersOfficialToDraft(t *testing.T) {
	ctx := context.Background()
	svc := newKBServiceForTest(t)
	svc.workspaceGovernanceAuthorizer = fakeWorkspaceGovernanceAuthorizer{authorize: func(_ context.Context, workspaceID, principalID string) (bool, error) {
		return workspaceID == "ws_1" && principalID == "org-admin", nil
	}}
	base, err := svc.CreateKnowledgeBase(ctx, "ws_1", "other-owner", KBMutation{Name: "Published runbooks"})
	require.NoError(t, err)
	_, err = svc.UpsertKnowledgeBasePermission(ctx, "ws_1", "other-owner", base.ID, KBPermissionGrantMutation{
		SubjectType: "user", SubjectID: "org-admin", Role: "none", RoleSet: true,
	})
	require.NoError(t, err)
	_, err = svc.UpsertKnowledgeBasePermission(ctx, "ws_1", "other-owner", base.ID, KBPermissionGrantMutation{
		SubjectType: "user", SubjectID: "kb-manager", Role: "manage", RoleSet: true,
	})
	require.NoError(t, err)

	ownerView, err := svc.GetKnowledgeBase(ctx, "ws_1", "other-owner", base.ID)
	require.NoError(t, err)
	require.True(t, ownerView.CanEditOfficial)
	require.False(t, ownerView.CanConvertToDraft)
	_, err = svc.ConvertKnowledgeBaseOfficialToDraft(ctx, "ws_1", "other-owner", base.ID)
	require.ErrorIs(t, err, ErrForbidden)

	managerView, err := svc.GetKnowledgeBase(ctx, "ws_1", "kb-manager", base.ID)
	require.NoError(t, err)
	require.False(t, managerView.CanEditOfficial)
	require.False(t, managerView.CanConvertToDraft)
	_, err = svc.ConvertKnowledgeBaseOfficialToDraft(ctx, "ws_1", "kb-manager", base.ID)
	require.ErrorIs(t, err, ErrForbidden)

	adminView, err := svc.GetKnowledgeBase(ctx, "ws_1", "org-admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, "manage", adminView.Role)
	require.True(t, adminView.CanConvertToDraft)
	setup, err := svc.ConvertKnowledgeBaseOfficialToDraft(ctx, "ws_1", "org-admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, KBSetupModeCreateDraft, setup.Session.Mode)
	require.Equal(t, "basic", setup.Session.ActiveStep)
	require.NotEmpty(t, setup.Session.WorkingRevision)
	require.NotEqual(t, base.PublishedRevision, setup.Session.WorkingRevision)
	require.Empty(t, setup.Session.PublishedRevision)
	require.Equal(t, "Published runbooks", setup.BasicInfo.Name)

	converted, err := svc.GetKnowledgeBase(ctx, "ws_1", "org-admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, KBPublicationDraft, converted.PublicationState)
	require.Equal(t, setup.Session.WorkingRevision, converted.WorkingRevision)
	require.Empty(t, converted.PublishedRevision)
	require.Nil(t, converted.PublishedAt)
	require.False(t, converted.CanConvertToDraft)

	drafts, err := svc.ListKnowledgeBases(ctx, "ws_1", "org-admin", KBPublicationDraft, "", nil, 1, 20)
	require.NoError(t, err)
	require.Len(t, drafts.Items, 1)
	require.Equal(t, base.ID, drafts.Items[0].ID)
	require.Equal(t, "manage", drafts.Items[0].Role)
}

func TestOrganizationAdminCanConvertLegacyOfficialWithoutPublishedRevision(t *testing.T) {
	ctx := context.Background()
	svc := newKBServiceForTest(t)
	svc.workspaceGovernanceAuthorizer = fakeWorkspaceGovernanceAuthorizer{authorize: func(_ context.Context, _, principalID string) (bool, error) {
		return principalID == "org-admin", nil
	}}
	base, err := svc.CreateKnowledgeBase(ctx, "ws_1", "other-owner", KBMutation{Name: "Legacy Official"})
	require.NoError(t, err)
	require.NoError(t, svc.store.DB().Model(&storekb.KnowledgeBase{}).
		Where("workspace_id = ? AND id = ?", base.WorkspaceID, base.ID).
		Update("published_revision", "").Error)

	legacy, err := svc.GetKnowledgeBase(ctx, "ws_1", "org-admin", base.ID)
	require.NoError(t, err)
	require.False(t, legacy.CanEditOfficial)
	require.True(t, legacy.CanConvertToDraft)
	_, err = svc.StartKnowledgeBaseOfficialEdit(ctx, "ws_1", "org-admin", base.ID)
	require.ErrorIs(t, err, ErrConflict)

	setup, err := svc.ConvertKnowledgeBaseOfficialToDraft(ctx, "ws_1", "org-admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, KBSetupModeCreateDraft, setup.Session.Mode)
	require.NotEmpty(t, setup.Session.WorkingRevision)
}

func TestOfficialToDraftConversionReplacesIncompleteOfficialEditor(t *testing.T) {
	ctx := context.Background()
	svc := newKBServiceForTest(t)
	svc.workspaceGovernanceAuthorizer = fakeWorkspaceGovernanceAuthorizer{authorize: func(_ context.Context, _, principalID string) (bool, error) {
		return principalID == "org-admin", nil
	}}
	base, err := svc.CreateKnowledgeBase(ctx, "ws_1", "org-admin", KBMutation{Name: "Current Official"})
	require.NoError(t, err)
	started, err := svc.StartKnowledgeBaseOfficialEdit(ctx, "ws_1", "org-admin", base.ID)
	require.NoError(t, err)
	working, err := svc.UpdateKnowledgeBaseOfficialEditBasic(ctx, "ws_1", "org-admin", base.ID, KBOfficialEditBasicMutation{
		Name: "Unsaved hidden name", Description: "Unsaved hidden description", GroupID: started.BasicInfo.GroupID,
		TagIDs: started.BasicInfo.TagIDs, ExpectedVersion: started.Session.Version,
	})
	require.NoError(t, err)
	require.Equal(t, "Unsaved hidden name", working.BasicInfo.Name)

	convertedSetup, err := svc.ConvertKnowledgeBaseOfficialToDraft(ctx, "ws_1", "org-admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, KBSetupModeCreateDraft, convertedSetup.Session.Mode)
	require.NotEqual(t, working.Session.WorkingRevision, convertedSetup.Session.WorkingRevision)
	require.Equal(t, "Current Official", convertedSetup.BasicInfo.Name)
	require.Nil(t, convertedSetup.Session.CompletedAt)

	converted, err := svc.GetKnowledgeBase(ctx, "ws_1", "org-admin", base.ID)
	require.NoError(t, err)
	require.Equal(t, "Current Official", converted.Name)
	require.Equal(t, KBPublicationDraft, converted.PublicationState)
}
