package handlers_test

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

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

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

func TestGrep_HappyPath(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(tmp, "a.go"), []byte("package a\n\nfunc Foo() {}\n"), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(tmp, "b.go"), []byte("package b\n\nfunc Bar() {}\n"), 0o644))

	h := handlers.NewGrepHandler()
	sess := handlers.SessionContext{CwdPath: tmp}
	res := h.Handle(context.Background(), sess, mustJSON(t, map[string]any{"sessionId": "x", "pattern": "func Foo"}))
	require.Nil(t, res.Err)

	var out struct {
		Matches []struct {
			File string `json:"file"`
			Line int    `json:"line"`
			Text string `json:"text"`
		} `json:"matches"`
		Truncated bool `json:"truncated"`
	}
	require.NoError(t, json.Unmarshal(res.OK, &out))
	require.Len(t, out.Matches, 1)
	assert.Equal(t, "a.go", out.Matches[0].File)
	assert.Equal(t, 3, out.Matches[0].Line)
	assert.Contains(t, out.Matches[0].Text, "func Foo")
}

func TestGrep_CaseInsensitiveByDefault(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(tmp, "a.go"), []byte("HELLO world\n"), 0o644))
	h := handlers.NewGrepHandler()
	sess := handlers.SessionContext{CwdPath: tmp}
	res := h.Handle(context.Background(), sess, mustJSON(t, map[string]any{"sessionId": "x", "pattern": "hello"}))
	require.Nil(t, res.Err)
	var out struct {
		Matches []any `json:"matches"`
	}
	require.NoError(t, json.Unmarshal(res.OK, &out))
	require.Len(t, out.Matches, 1)
}

func TestGrep_PathOutsideWorktree(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	h := handlers.NewGrepHandler()
	sess := handlers.SessionContext{CwdPath: tmp}
	outside := filepath.Join(filepath.VolumeName(tmp)+string(filepath.Separator), "etc")
	res := h.Handle(context.Background(), sess, mustJSON(t, map[string]any{"sessionId": "x", "pattern": "x", "path": outside}))
	require.NotNil(t, res.Err)
	assert.Equal(t, -32001, res.Err.Code)
}

func TestGrep_InvalidPattern(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	h := handlers.NewGrepHandler()
	sess := handlers.SessionContext{CwdPath: tmp}
	res := h.Handle(context.Background(), sess, mustJSON(t, map[string]any{"sessionId": "x", "pattern": "[invalid("}))
	require.NotNil(t, res.Err)
	assert.Equal(t, -32602, res.Err.Code)
}

func TestGrep_EmptyPattern(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	h := handlers.NewGrepHandler()
	sess := handlers.SessionContext{CwdPath: tmp}
	res := h.Handle(context.Background(), sess, mustJSON(t, map[string]any{"sessionId": "x", "pattern": ""}))
	require.NotNil(t, res.Err)
	assert.Equal(t, -32602, res.Err.Code)
}

func TestGrep_MaxMatchesCap(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	// 10 matching lines; cap at 3.
	content := "m\nm\nm\nm\nm\nm\nm\nm\nm\nm\n"
	require.NoError(t, os.WriteFile(filepath.Join(tmp, "a.txt"), []byte(content), 0o644))
	h := &handlers.GrepHandler{MaxMatches: 3, MaxFiles: 100, MaxFileSize: 1 << 20}
	sess := handlers.SessionContext{CwdPath: tmp}
	res := h.Handle(context.Background(), sess, mustJSON(t, map[string]any{"sessionId": "x", "pattern": "m"}))
	require.Nil(t, res.Err)
	var out struct {
		Matches   []any `json:"matches"`
		Truncated bool  `json:"truncated"`
	}
	require.NoError(t, json.Unmarshal(res.OK, &out))
	require.Len(t, out.Matches, 3)
	require.True(t, out.Truncated)
}

func TestGrep_SkipsGitDir(t *testing.T) {
	t.Parallel()
	tmp := t.TempDir()
	require.NoError(t, os.MkdirAll(filepath.Join(tmp, ".git"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(tmp, ".git", "config"), []byte("secret pattern\n"), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(tmp, "a.txt"), []byte("normal pattern\n"), 0o644))
	h := handlers.NewGrepHandler()
	sess := handlers.SessionContext{CwdPath: tmp}
	res := h.Handle(context.Background(), sess, mustJSON(t, map[string]any{"sessionId": "x", "pattern": "pattern"}))
	require.Nil(t, res.Err)
	var out struct {
		Matches []struct {
			File string `json:"file"`
		} `json:"matches"`
	}
	require.NoError(t, json.Unmarshal(res.OK, &out))
	require.Len(t, out.Matches, 1)
	assert.Equal(t, "a.txt", out.Matches[0].File)
}
