package handlers_test

import (
	"context"
	"encoding/json"
	"math"
	"os"
	"path/filepath"
	"testing"

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

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

func TestFsRead_HappyPath(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(tmp, "a.txt"), []byte("hello\n"), 0o644))

	h := handlers.NewFsReadHandler()
	sess := handlers.SessionContext{SessionID: uuid.New(), CwdPath: tmp}
	params := mustJSON(t, map[string]any{"sessionId": "x", "path": filepath.Join(tmp, "a.txt")})

	res := h.Handle(context.Background(), sess, params)
	require.Nil(t, res.Err)
	require.NotNil(t, res.OK)
	var out struct {
		Content string `json:"content"`
	}
	require.NoError(t, json.Unmarshal(res.OK, &out))
	assert.Equal(t, "hello\n", out.Content)
}

func TestFsRead_PathOutsideWorktree(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	h := handlers.NewFsReadHandler()
	sess := handlers.SessionContext{CwdPath: tmp}
	outside := filepath.Join(filepath.VolumeName(tmp)+string(filepath.Separator), "etc", "passwd")
	params := mustJSON(t, map[string]any{"sessionId": "x", "path": outside})

	res := h.Handle(context.Background(), sess, params)
	require.NotNil(t, res.Err)
	assert.Equal(t, -32001, res.Err.Code)
}

func TestFsRead_FileNotFound(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	h := handlers.NewFsReadHandler()
	sess := handlers.SessionContext{CwdPath: tmp}
	params := mustJSON(t, map[string]any{"sessionId": "x", "path": filepath.Join(tmp, "nope.txt")})

	res := h.Handle(context.Background(), sess, params)
	require.NotNil(t, res.Err)
	assert.Equal(t, -32603, res.Err.Code)
}

func TestFsRead_LineLimit(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(tmp, "f.txt"),
		[]byte("L0\nL1\nL2\nL3\n"), 0o644))

	h := handlers.NewFsReadHandler()
	sess := handlers.SessionContext{CwdPath: tmp}
	line := 1
	limit := 2
	params := mustJSON(t, map[string]any{
		"sessionId": "x", "path": filepath.Join(tmp, "f.txt"),
		"line": line, "limit": limit,
	})
	res := h.Handle(context.Background(), sess, params)
	require.Nil(t, res.Err)
	var out struct {
		Content string `json:"content"`
	}
	_ = json.Unmarshal(res.OK, &out)
	assert.Equal(t, "L1\nL2", out.Content)
}

// TestFsRead_HugeLimit_NoOverflow 锁定 bug #2：limit 为 math.MaxInt 时
// start+limit 会整数溢出为负，旧逻辑下 lines[start:end] 会 panic。修复后应安全
// 钳制到文件末尾，返回从 line 起的剩余内容。
func TestFsRead_HugeLimit_NoOverflow(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(tmp, "f.txt"),
		[]byte("L0\nL1\nL2\nL3\n"), 0o644))

	h := handlers.NewFsReadHandler()
	sess := handlers.SessionContext{CwdPath: tmp}
	line := 1
	limit := math.MaxInt
	params := mustJSON(t, map[string]any{
		"sessionId": "x", "path": filepath.Join(tmp, "f.txt"),
		"line": line, "limit": limit,
	})

	var res handlers.FsResult
	require.NotPanics(t, func() {
		res = h.Handle(context.Background(), sess, params)
	})
	require.Nil(t, res.Err)
	var out struct {
		Content string `json:"content"`
	}
	require.NoError(t, json.Unmarshal(res.OK, &out))
	// 从 line=1 到末尾（split 出 5 个元素：L0 L1 L2 L3 ""）。
	assert.Equal(t, "L1\nL2\nL3\n", out.Content)
}

func mustJSON(t *testing.T, v any) json.RawMessage {
	t.Helper()
	b, err := json.Marshal(v)
	require.NoError(t, err)
	return b
}
