package daemon

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

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent"
	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/runtimeconfig"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

// TestOrphanedManagedRepoIDs pins the set-diff the reconciler uses to decide
// which local managed skill clones to remove: a RepoID is an orphan only when
// it is present locally but absent from the active (still-bound) set.
func TestOrphanedManagedRepoIDs(t *testing.T) {
	local := []profile.ManagedSkillRepositoryRecord{
		{RepoID: "sk_a"}, // still bound -> keep
		{RepoID: "sk_b"}, // orphan -> remove
		{RepoID: "sk_c"}, // orphan -> remove
		{RepoID: "  "},   // blank -> ignored
		{RepoID: "sk_a"}, // duplicate orphan/keep -> deduped
	}
	active := []string{"sk_a", " sk_a ", ""} // whitespace-trimmed; blank ignored

	orphans := orphanedManagedRepoIDs(local, active)
	require.ElementsMatch(t, []string{"sk_b", "sk_c"}, orphans)
}

// TestOrphanedManagedRepoIDsAllActive verifies that when every local repo is
// still bound the reconciler removes nothing.
func TestOrphanedManagedRepoIDsAllActive(t *testing.T) {
	local := []profile.ManagedSkillRepositoryRecord{{RepoID: "sk_a"}, {RepoID: "sk_b"}}
	active := []string{"sk_a", "sk_b"}
	require.Empty(t, orphanedManagedRepoIDs(local, active))
}

// TestOrphanedManagedRepoIDsEmptyActive verifies that an empty active set (no
// agent bound to any repo) orphans every local repo.
func TestOrphanedManagedRepoIDsEmptyActive(t *testing.T) {
	local := []profile.ManagedSkillRepositoryRecord{{RepoID: "sk_a"}, {RepoID: "sk_b"}}
	require.ElementsMatch(t, []string{"sk_a", "sk_b"}, orphanedManagedRepoIDs(local, nil))
}

func TestReconcileManagedRepositoriesPrunesRuntimeCopiesAfterCloneRemoval(t *testing.T) {
	t.Setenv(profile.ConfigDirEnvKey, t.TempDir())
	require.NoError(t, profile.Default().Servers().Add(&profile.AgentTeamServer{ID: "agentteam-main", Enabled: true}))
	require.NoError(t, profile.Default().Agents().Add(agent.Config{
		ID:      "agt_runtime_prune",
		Runtime: string(runtimeconfig.TypeCodex),
		Source:  agent.NewAgentTeamSource("agentteam-main"),
	}))

	manager := profile.Default().ManagedSkillRepositories()
	repoID := "repo_retired_team"
	cloneDir := manager.RepositoryPath(repoID)
	skillDir := filepath.Join(cloneDir, "skills", "retired-team-helper")
	require.NoError(t, os.MkdirAll(skillDir, 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte("# retired\n"), 0o644))
	require.NoError(t, manager.Upsert(profile.ManagedSkillRepositoryRecord{
		RepoID: repoID, CloneURL: "https://agentteam.test/retired.git", Branch: "main", LocalPath: cloneDir,
	}))

	workDir := profile.AgentWorkDir("agt_runtime_prune")
	require.NoError(t, runtimeconfig.MaterializeRuntimeSkillsFromSources(runtimeconfig.TypeCodex, workDir, []runtimeconfig.RuntimeSkillSource{{
		Dir: cloneDir, Priority: runtimeconfig.RuntimeSkillPriorityManaged, Origin: runtimeconfig.RuntimeSkillOriginManaged,
	}}))
	runtimeSlot := filepath.Join(workDir, ".agents", "skills", "retired-team-helper")
	require.DirExists(t, runtimeSlot)

	syncer := newManagedSkillRefreshTestSyncer()
	disp := newDaemonTestDispatcher("http://server.test")
	registrar, ok := disp.ServerConnections().(managedSkillRefreshChannelRegistrar)
	require.True(t, ok)
	registrar.Register(&managedSkillRefreshTestChannel{id: "agentteam-main", serverURL: "https://agentteam.test", workspaceID: "ws_1"})
	disp.SetSkillSyncer(syncer)
	// The server's authoritative empty cache means the old repository is no
	// longer accessible to any local AgentTeam Agent.
	d := &Daemon{dispatcher: disp}
	d.reconcileManagedRepositories(context.Background())

	_, exists := manager.Get(repoID)
	require.False(t, exists)
	require.NoDirExists(t, cloneDir)
	require.NoDirExists(t, runtimeSlot)
}
