package kb

import (
	"context"
	"errors"
	"os"
	"path/filepath"
	"testing"

	gittransport "github.com/go-git/go-git/v5/plumbing/transport/http"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/desktop/internal/dispatcher"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

type fakeKBGitRunner struct {
	fetchErr    error
	worktreeErr error
	clones      int
	fetches     int
	worktree    string
}

func (g *fakeKBGitRunner) CloneBare(context.Context, string, string, string, gittransport.AuthMethod) (string, error) {
	g.clones++
	return "rev-clone", nil
}

func (g *fakeKBGitRunner) FetchBare(context.Context, string, string, gittransport.AuthMethod) (string, error) {
	g.fetches++
	if g.fetchErr != nil {
		return "", g.fetchErr
	}
	return "rev-fetch", nil
}

func (g *fakeKBGitRunner) WorktreeCheckout(_ context.Context, _, worktreePath, _ string) error {
	g.worktree = worktreePath
	if g.worktreeErr != nil {
		return g.worktreeErr
	}
	if err := os.MkdirAll(worktreePath, 0o755); err != nil {
		return err
	}
	return os.WriteFile(filepath.Join(worktreePath, ".git"), []byte("gitdir: fake"), 0o644)
}

func TestSyncAgentMountsKnowledgeBaseWorktreeAndUpdatesIndex(t *testing.T) {
	mgr := profile.New(t.TempDir()).ManagedKnowledgeBaseRepositories()
	git := &fakeKBGitRunner{fetchErr: errors.New("missing")}
	access := []KnowledgeBaseAccess{{
		KnowledgeBaseID:          "kb_1",
		KnowledgeBaseName:        "Runbooks",
		KnowledgeBaseDescription: "Ops notes",
		SourceID:                 "src_1",
		SourceName:               "AgentTeam",
		SourceType:               "codebase",
		RepoRole:                 "primary",
		Branch:                   "main",
		CloneURL:                 "https://agentteam.test/org/repo.git",
		RepoID:                   "repo_1",
		AccessProfile:            "read",
	}}
	syncer := NewSync(func(context.Context, string) ([]KnowledgeBaseAccess, dispatcher.SkillLookupState, error) {
		return access, LookupBound, nil
	}, git, mgr)

	state, err := syncer.SyncAgent(context.Background(), "agt_1", "scope")
	require.NoError(t, err)
	require.Equal(t, LookupBound, state)
	require.Equal(t, 1, git.fetches)
	require.Equal(t, 1, git.clones)
	require.NotEmpty(t, git.worktree)

	records := mgr.RecordsForAgent("agt_1")
	require.Len(t, records, 1)
	require.Equal(t, "kb_1", records[0].KnowledgeBaseID)
	require.Equal(t, "repo_1", records[0].RepoID)
	require.Equal(t, "scope", records[0].ScopeKey)
	require.Equal(t, profile.SkillSyncStateReady, records[0].LastSyncState)
	require.Contains(t, filepath.ToSlash(records[0].WorktreePath), "/agents/agt_1/kb/managed/kb_1/src_1/")

	indexBytes, err := os.ReadFile(filepath.Join(mgr.AgentWorkDir("agt_1"), "kb", "index.md"))
	require.NoError(t, err)
	index := string(indexBytes)
	require.Contains(t, index, "Managed Knowledge Bases")
	require.Contains(t, index, "Runbooks")
	require.Contains(t, index, "kb/managed/kb_1/src_1")
}

func TestSyncAgentRecordsFailedRepositoryWithoutAdvertisingInIndex(t *testing.T) {
	mgr := profile.New(t.TempDir()).ManagedKnowledgeBaseRepositories()
	git := &fakeKBGitRunner{worktreeErr: errors.New("checkout failed")}
	access := []KnowledgeBaseAccess{{
		KnowledgeBaseID:   "kb_1",
		KnowledgeBaseName: "Runbooks",
		SourceID:          "src_1",
		SourceName:        "AgentTeam",
		RepoRole:          "primary",
		Branch:            "main",
		CloneURL:          "https://agentteam.test/org/repo.git",
		RepoID:            "repo_1",
	}}
	syncer := NewSync(func(context.Context, string) ([]KnowledgeBaseAccess, dispatcher.SkillLookupState, error) {
		return access, LookupBound, nil
	}, git, mgr)

	state, err := syncer.SyncAgent(context.Background(), "agt_1", "scope")
	require.NoError(t, err)
	require.Equal(t, LookupBound, state)
	records := mgr.RecordsForAgent("agt_1")
	require.Len(t, records, 1)
	require.Equal(t, profile.SkillSyncStateFailed, records[0].LastSyncState)
	require.Contains(t, records[0].LastError, "checkout failed")

	indexBytes, err := os.ReadFile(filepath.Join(mgr.AgentWorkDir("agt_1"), "kb", "index.md"))
	require.NoError(t, err)
	require.Contains(t, string(indexBytes), "No managed MeshX knowledge bases")
	require.NotContains(t, string(indexBytes), "Runbooks")
}

func TestSyncAgentUnboundRemovesAgentRecordsAndClearsIndex(t *testing.T) {
	mgr := profile.New(t.TempDir()).ManagedKnowledgeBaseRepositories()
	oldPath := mgr.WorktreePath("agt_1", "kb_1", "src_1", "repo_1", "canonical")
	require.NoError(t, os.MkdirAll(oldPath, 0o755))
	_, err := mgr.ActivateAgentScope("agt_1", "scope")
	require.NoError(t, err)
	require.NoError(t, mgr.ReplaceAgentRecords("agt_1", "scope", []profile.ManagedKnowledgeBaseRepositoryRecord{{
		AgentID:           "agt_1",
		ScopeKey:          "scope",
		KnowledgeBaseID:   "kb_1",
		KnowledgeBaseName: "Runbooks",
		SourceID:          "src_1",
		SourceName:        "AgentTeam",
		RepoID:            "repo_1",
		RepoRole:          "primary",
		CloneURL:          "https://agentteam.test/org/repo.git",
		Branch:            "main",
		BarePath:          mgr.BareRepositoryPath("repo_1"),
		WorktreePath:      oldPath,
	}}))

	syncer := NewSync(func(context.Context, string) ([]KnowledgeBaseAccess, dispatcher.SkillLookupState, error) {
		return nil, LookupUnbound, nil
	}, &fakeKBGitRunner{}, mgr)
	state, err := syncer.SyncAgent(context.Background(), "agt_1", "scope")
	require.NoError(t, err)
	require.Equal(t, LookupUnbound, state)
	require.Empty(t, mgr.RecordsForAgent("agt_1"))
	_, statErr := os.Stat(oldPath)
	require.True(t, os.IsNotExist(statErr))

	indexBytes, err := os.ReadFile(filepath.Join(mgr.AgentWorkDir("agt_1"), "kb", "index.md"))
	require.NoError(t, err)
	require.Contains(t, string(indexBytes), "No managed MeshX knowledge bases")
}

func TestSyncAgentCrossScopeUnknownRemovesPriorRuntimeView(t *testing.T) {
	mgr := profile.New(t.TempDir()).ManagedKnowledgeBaseRepositories()
	calls := 0
	syncer := NewSync(func(context.Context, string) ([]KnowledgeBaseAccess, dispatcher.SkillLookupState, error) {
		calls++
		if calls == 1 {
			return []KnowledgeBaseAccess{{
				KnowledgeBaseID:   "kb_a",
				KnowledgeBaseName: "Workspace A Secret",
				SourceID:          "src_a",
				SourceName:        "A",
				RepoRole:          "primary",
				Branch:            "main",
				CloneURL:          "https://a.example/repo.git",
				RepoID:            "repo_a",
			}}, LookupBound, nil
		}
		return nil, LookupUnknown, nil
	}, &fakeKBGitRunner{}, mgr)

	state, err := syncer.SyncAgent(context.Background(), "agt_1", "scope-a")
	require.NoError(t, err)
	require.Equal(t, LookupBound, state)
	records := mgr.RecordsForAgent("agt_1")
	require.Len(t, records, 1)
	oldWorktree := records[0].WorktreePath
	indexPath := filepath.Join(mgr.AgentWorkDir("agt_1"), "kb", "index.md")

	state, err = syncer.SyncAgent(context.Background(), "agt_1", "scope-b")
	require.NoError(t, err)
	require.Equal(t, LookupUnknown, state)
	require.Empty(t, mgr.RecordsForAgent("agt_1"))
	require.NoDirExists(t, oldWorktree)
	indexBytes, err := os.ReadFile(indexPath)
	require.NoError(t, err)
	require.Contains(t, string(indexBytes), "No managed MeshX knowledge bases")
	require.NotContains(t, string(indexBytes), "Workspace A Secret")
}

func TestSyncAgentSameScopeUnknownPreservesPriorRuntimeView(t *testing.T) {
	mgr := profile.New(t.TempDir()).ManagedKnowledgeBaseRepositories()
	calls := 0
	syncer := NewSync(func(context.Context, string) ([]KnowledgeBaseAccess, dispatcher.SkillLookupState, error) {
		calls++
		if calls == 1 {
			return []KnowledgeBaseAccess{{
				KnowledgeBaseID:   "kb_a",
				KnowledgeBaseName: "Workspace A Runbook",
				SourceID:          "src_a",
				SourceName:        "A",
				RepoRole:          "primary",
				Branch:            "main",
				CloneURL:          "https://a.example/repo.git",
				RepoID:            "repo_a",
			}}, LookupBound, nil
		}
		return nil, LookupUnknown, nil
	}, &fakeKBGitRunner{}, mgr)

	state, err := syncer.SyncAgent(context.Background(), "agt_1", "scope-a")
	require.NoError(t, err)
	require.Equal(t, LookupBound, state)
	records := mgr.RecordsForAgent("agt_1")
	require.Len(t, records, 1)
	oldWorktree := records[0].WorktreePath
	indexPath := filepath.Join(mgr.AgentWorkDir("agt_1"), "kb", "index.md")

	state, err = syncer.SyncAgent(context.Background(), "agt_1", "scope-a")
	require.NoError(t, err)
	require.Equal(t, LookupUnknown, state)
	require.Len(t, mgr.RecordsForAgent("agt_1"), 1)
	require.DirExists(t, oldWorktree)
	indexBytes, err := os.ReadFile(indexPath)
	require.NoError(t, err)
	require.Contains(t, string(indexBytes), "Workspace A Runbook")
}

func TestSyncAgentFirstScopedUnknownRemovesLegacyUnscopedRuntimeView(t *testing.T) {
	configDir := t.TempDir()
	mgr := profile.New(configDir).ManagedKnowledgeBaseRepositories()
	legacyWorktree := mgr.WorktreePath("agt_1", "kb_legacy", "src_legacy", "repo_legacy", "canonical")
	require.NoError(t, os.MkdirAll(legacyWorktree, 0o755))
	legacyYAML := `repositories:
  - agent_id: agt_1
    knowledge_base_id: kb_legacy
    knowledge_base_name: Legacy Secret
    source_id: src_legacy
    source_name: Legacy
    repo_id: repo_legacy
    repo_role: canonical
    clone_url: https://legacy.example/repo.git
    branch: main
    bare_path: ` + filepath.ToSlash(mgr.BareRepositoryPath("repo_legacy")) + `
    worktree_path: ` + filepath.ToSlash(legacyWorktree) + `
`
	require.NoError(t, os.WriteFile(filepath.Join(configDir, "managed_kbs.yaml"), []byte(legacyYAML), 0o600))
	indexPath := filepath.Join(mgr.AgentWorkDir("agt_1"), "kb", "index.md")
	require.NoError(t, os.MkdirAll(filepath.Dir(indexPath), 0o755))
	require.NoError(t, os.WriteFile(indexPath, []byte("# Knowledge Index\n\n<!-- agentteam-managed-kb:start -->\n## Managed Knowledge Bases\n\n- **Legacy Secret**\n<!-- agentteam-managed-kb:end -->\n"), 0o644))

	syncer := NewSync(func(context.Context, string) ([]KnowledgeBaseAccess, dispatcher.SkillLookupState, error) {
		return nil, LookupUnknown, nil
	}, &fakeKBGitRunner{}, mgr)
	state, err := syncer.SyncAgent(context.Background(), "agt_1", "scope-current")

	require.NoError(t, err)
	require.Equal(t, LookupUnknown, state)
	require.NoDirExists(t, legacyWorktree)
	require.Empty(t, mgr.RecordsForAgent("agt_1"))
	indexBytes, err := os.ReadFile(indexPath)
	require.NoError(t, err)
	require.Contains(t, string(indexBytes), "No managed MeshX knowledge bases")
	require.NotContains(t, string(indexBytes), "Legacy Secret")
}
