package acp

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

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/yan1h/agent-coding-workflow/internal/infra/crypto"
)

// cfgRepoStub 只实现 ListConfigFiles；其余方法走嵌入的 nil 接口（调用即 panic）。
type cfgRepoStub struct {
	Repository
	files []*ConfigFile
}

func (s cfgRepoStub) ListConfigFiles(context.Context, uuid.UUID) ([]*ConfigFile, error) {
	return s.files, nil
}

func TestMaterializeAgentHome(t *testing.T) {
	t.Parallel()
	enc, err := crypto.NewEncryptor([]byte("0123456789abcdef0123456789abcdef"))
	require.NoError(t, err)

	kind := &AgentKind{ID: uuid.New(), ClientType: ClientCodex}
	content := []byte(`model = "gpt-5.5"`)
	encrypted, err := enc.Encrypt(content)
	require.NoError(t, err)

	root := t.TempDir()
	s := &Supervisor{
		repo:   cfgRepoStub{files: []*ConfigFile{{AgentKindID: kind.ID, RelPath: ".codex/config.toml", EncryptedContent: encrypted}}},
		crypto: enc,
		cfg:    SupervisorConfig{AgentHomesDir: root},
	}

	sess := &Session{ID: uuid.New(), UserID: uuid.New()}
	home, err := s.materializeAgentHome(context.Background(), sess, kind)
	require.NoError(t, err)
	// per-user home：<root>/<user_id>/<kind_id>
	assert.Equal(t, filepath.Join(root, sess.UserID.String(), kind.ID.String()), home)

	got, err := os.ReadFile(filepath.Join(home, ".codex", "config.toml"))
	require.NoError(t, err)
	assert.Equal(t, content, got)

	// 二次物化幂等（覆写）
	_, err = s.materializeAgentHome(context.Background(), sess, kind)
	require.NoError(t, err)
}

func TestMaterializeAgentHome_PerUserIsolation(t *testing.T) {
	t.Parallel()
	enc, err := crypto.NewEncryptor([]byte("0123456789abcdef0123456789abcdef"))
	require.NoError(t, err)

	kind := &AgentKind{ID: uuid.New(), ClientType: ClientCodex}
	encrypted, err := enc.Encrypt([]byte("x = 1"))
	require.NoError(t, err)

	root := t.TempDir()
	s := &Supervisor{
		repo:   cfgRepoStub{files: []*ConfigFile{{AgentKindID: kind.ID, RelPath: ".codex/config.toml", EncryptedContent: encrypted}}},
		crypto: enc,
		cfg:    SupervisorConfig{AgentHomesDir: root},
	}

	// 同一 agent-kind、两个不同用户 → 两个互不相同的 home 目录。
	sessA := &Session{ID: uuid.New(), UserID: uuid.New()}
	sessB := &Session{ID: uuid.New(), UserID: uuid.New()}
	homeA, err := s.materializeAgentHome(context.Background(), sessA, kind)
	require.NoError(t, err)
	homeB, err := s.materializeAgentHome(context.Background(), sessB, kind)
	require.NoError(t, err)

	assert.NotEqual(t, homeA, homeB)
	// 两个 home 各自物化了配置文件。
	_, err = os.Stat(filepath.Join(homeA, ".codex", "config.toml"))
	require.NoError(t, err)
	_, err = os.Stat(filepath.Join(homeB, ".codex", "config.toml"))
	require.NoError(t, err)
}

func TestMaterializeAgentHome_RejectsEscape(t *testing.T) {
	t.Parallel()
	enc, err := crypto.NewEncryptor([]byte("0123456789abcdef0123456789abcdef"))
	require.NoError(t, err)
	encrypted, err := enc.Encrypt([]byte("x"))
	require.NoError(t, err)

	kind := &AgentKind{ID: uuid.New(), ClientType: ClientGeneric}
	s := &Supervisor{
		repo:   cfgRepoStub{files: []*ConfigFile{{AgentKindID: kind.ID, RelPath: "../escape.txt", EncryptedContent: encrypted}}},
		crypto: enc,
		cfg:    SupervisorConfig{AgentHomesDir: t.TempDir()},
	}
	sess := &Session{ID: uuid.New(), UserID: uuid.New()}
	_, err = s.materializeAgentHome(context.Background(), sess, kind)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "escapes agent home")
}

func TestAgentHomeEnv_ByClientType(t *testing.T) {
	t.Parallel()
	home := filepath.Join(string(filepath.Separator), "data", "agent-homes", "x")

	env := agentHomeEnv(home, ClientClaudeCode)
	assert.Equal(t, home, env["HOME"])
	assert.Equal(t, filepath.Join(home, ".claude"), env["CLAUDE_CONFIG_DIR"])

	env = agentHomeEnv(home, ClientCodex)
	assert.Equal(t, filepath.Join(home, ".codex"), env["CODEX_HOME"])

	env = agentHomeEnv(home, ClientGemini)
	assert.Equal(t, home, env["GEMINI_CLI_HOME"])

	env = agentHomeEnv(home, ClientGeneric)
	assert.Equal(t, map[string]string{"HOME": home}, stripWinEnv(env))

	if runtime.GOOS == "windows" {
		assert.Equal(t, home, agentHomeEnv(home, ClientGeneric)["USERPROFILE"])
	}
}

// stripWinEnv 去掉平台相关的 USERPROFILE，便于跨平台断言。
func stripWinEnv(env map[string]string) map[string]string {
	out := map[string]string{}
	for k, v := range env {
		if k == "USERPROFILE" {
			continue
		}
		out[k] = v
	}
	return out
}
