package acp_test

import (
	"context"
	"testing"

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

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

func newConfigFileTestSvc(t *testing.T) (acp.AgentKindService, *acp.AgentKind, acp.Caller) {
	t.Helper()
	repo := newFakeAgentKindRepo()
	svc := acp.NewAgentKindService(repo, testEncryptor(t), &agentKindRecordingRecorder{})
	admin := acp.Caller{UserID: uuid.New(), IsAdmin: true}
	k, err := svc.Create(context.Background(), admin, acp.CreateAgentKindInput{
		Name: "claude", DisplayName: "Claude", BinaryPath: "/x",
		ClientType: acp.ClientClaudeCode, Enabled: true,
	})
	require.NoError(t, err)
	return svc, k, admin
}

func TestConfigFiles_UpsertListDelete_Roundtrip(t *testing.T) {
	t.Parallel()
	ctx := context.Background()
	svc, k, admin := newConfigFileTestSvc(t)

	content := `{"model":"claude-sonnet-4-6"}`
	out, err := svc.UpsertConfigFile(ctx, admin, k.ID, ".claude/settings.json", content)
	require.NoError(t, err)
	assert.Equal(t, ".claude/settings.json", out.RelPath)
	assert.Equal(t, content, out.Content)

	list, err := svc.ListConfigFiles(ctx, admin, k.ID)
	require.NoError(t, err)
	require.Len(t, list, 1)
	assert.Equal(t, content, list[0].Content) // 解密往返

	require.NoError(t, svc.DeleteConfigFile(ctx, admin, k.ID, ".claude/settings.json"))
	list, err = svc.ListConfigFiles(ctx, admin, k.ID)
	require.NoError(t, err)
	assert.Empty(t, list)
}

func TestConfigFiles_AdminOnly(t *testing.T) {
	t.Parallel()
	ctx := context.Background()
	svc, k, _ := newConfigFileTestSvc(t)
	user := acp.Caller{UserID: uuid.New(), IsAdmin: false}

	_, err := svc.ListConfigFiles(ctx, user, k.ID)
	ae, ok := errs.As(err)
	require.True(t, ok)
	assert.Equal(t, errs.CodeForbidden, ae.Code)

	_, err = svc.UpsertConfigFile(ctx, user, k.ID, "a.json", "{}")
	ae, ok = errs.As(err)
	require.True(t, ok)
	assert.Equal(t, errs.CodeForbidden, ae.Code)
}

func TestConfigFiles_RelPathValidation(t *testing.T) {
	t.Parallel()
	ctx := context.Background()
	svc, k, admin := newConfigFileTestSvc(t)

	bad := []string{
		"", "/etc/passwd", "../escape.json", "a/../../b.json",
		"C:/windows/x.json", "..", ".",
	}
	for _, p := range bad {
		_, err := svc.UpsertConfigFile(ctx, admin, k.ID, p, "{}")
		ae, ok := errs.As(err)
		require.True(t, ok, "rel_path %q should be rejected", p)
		assert.Equal(t, errs.CodeInvalidInput, ae.Code, "rel_path %q", p)
	}

	// 反斜杠归一为正斜杠；./ 前缀被 Clean 掉
	out, err := svc.UpsertConfigFile(ctx, admin, k.ID, `.codex\config.toml`, `model = "gpt-5.5"`)
	require.NoError(t, err)
	assert.Equal(t, ".codex/config.toml", out.RelPath)
	out, err = svc.UpsertConfigFile(ctx, admin, k.ID, "./a/b.json", "{}")
	require.NoError(t, err)
	assert.Equal(t, "a/b.json", out.RelPath)
}

func TestConfigFiles_SyntaxValidation(t *testing.T) {
	t.Parallel()
	ctx := context.Background()
	svc, k, admin := newConfigFileTestSvc(t)

	_, err := svc.UpsertConfigFile(ctx, admin, k.ID, ".claude/settings.json", "{not json")
	ae, ok := errs.As(err)
	require.True(t, ok)
	assert.Equal(t, errs.CodeInvalidInput, ae.Code)

	_, err = svc.UpsertConfigFile(ctx, admin, k.ID, ".codex/config.toml", "= broken toml")
	ae, ok = errs.As(err)
	require.True(t, ok)
	assert.Equal(t, errs.CodeInvalidInput, ae.Code)

	// 未识别扩展名不校验
	_, err = svc.UpsertConfigFile(ctx, admin, k.ID, ".claude/CLAUDE.md", "# 任意文本")
	require.NoError(t, err)
}

func TestConfigFiles_DeleteNotFound(t *testing.T) {
	t.Parallel()
	ctx := context.Background()
	svc, k, admin := newConfigFileTestSvc(t)

	err := svc.DeleteConfigFile(ctx, admin, k.ID, "missing.json")
	ae, ok := errs.As(err)
	require.True(t, ok)
	assert.Equal(t, errs.CodeNotFound, ae.Code)
}

func TestAgentKindService_ClientType(t *testing.T) {
	t.Parallel()
	ctx := context.Background()
	repo := newFakeAgentKindRepo()
	svc := acp.NewAgentKindService(repo, testEncryptor(t), nil)
	admin := acp.Caller{UserID: uuid.New(), IsAdmin: true}

	// 缺省 → generic
	k, err := svc.Create(ctx, admin, acp.CreateAgentKindInput{
		Name: "k1", DisplayName: "K1", BinaryPath: "/x", Enabled: true,
	})
	require.NoError(t, err)
	assert.Equal(t, acp.ClientGeneric, k.ClientType)

	// 非法值拒绝
	_, err = svc.Create(ctx, admin, acp.CreateAgentKindInput{
		Name: "k2", DisplayName: "K2", BinaryPath: "/x", ClientType: "bogus", Enabled: true,
	})
	ae, ok := errs.As(err)
	require.True(t, ok)
	assert.Equal(t, errs.CodeInvalidInput, ae.Code)

	// 更新 client_type
	ct := acp.ClientCodex
	updated, err := svc.Update(ctx, admin, k.ID, acp.UpdateAgentKindInput{ClientType: &ct})
	require.NoError(t, err)
	assert.Equal(t, acp.ClientCodex, updated.ClientType)

	bad := acp.ClientType("nope")
	_, err = svc.Update(ctx, admin, k.ID, acp.UpdateAgentKindInput{ClientType: &bad})
	ae, ok = errs.As(err)
	require.True(t, ok)
	assert.Equal(t, errs.CodeInvalidInput, ae.Code)
}
