// relay_discovery_test.go: relay dispatch tests for the fs/list + fs/grep agent
// discovery handlers (mirrors TestRelay_AgentFsRequest_Echoed).
package acp_test

import (
	"encoding/json"
	"errors"
	"io"
	"os"
	"path/filepath"
	"testing"
	"time"

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

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

func relayRoundTrip(t *testing.T, rig *relayTestRig, id int64, method string, params map[string]any) *acp.Message {
	t.Helper()
	agentDec := acp.NewDecoder(rig.agentReader, 0)
	agentEnc := acp.NewEncoder(rig.agentWriter)
	req, err := acp.NewRequestInt(id, method, params)
	require.NoError(t, err)
	require.NoError(t, agentEnc.Encode(req))

	respCh := make(chan *acp.Message, 1)
	errCh := make(chan error, 1)
	go func() {
		m, derr := agentDec.DecodeMessage()
		if derr != nil {
			errCh <- derr
			return
		}
		respCh <- m
	}()
	select {
	case resp := <-respCh:
		return resp
	case err := <-errCh:
		if errors.Is(err, io.EOF) {
			t.Fatal("agent saw EOF before relay responded")
		}
		t.Fatalf("agent decoder error: %v", err)
	case <-time.After(3 * time.Second):
		t.Fatalf("relay did not respond to %s within 3s", method)
	}
	return nil
}

func TestRelay_AgentFsList_Dispatched(t *testing.T) {
	t.Parallel()
	rig := newRelayTestRig(t)
	cancel, doneCh := rig.runRelay(t)
	t.Cleanup(func() { rig.shutdown(t, cancel, doneCh) })

	require.NoError(t, os.WriteFile(filepath.Join(rig.sess.CwdPath, "f.txt"), []byte("hi"), 0o644))

	resp := relayRoundTrip(t, rig, 201, "fs/list", map[string]any{"path": ".", "sessionId": "a"})
	require.True(t, resp.IsResponse())
	require.Nil(t, resp.Error)
	var out struct {
		Entries []struct {
			Name string `json:"name"`
		} `json:"entries"`
	}
	require.NoError(t, json.Unmarshal(resp.Result, &out))
	require.Len(t, out.Entries, 1)
	assert.Equal(t, "f.txt", out.Entries[0].Name)
}

func TestRelay_AgentFsGrep_Dispatched(t *testing.T) {
	t.Parallel()
	rig := newRelayTestRig(t)
	cancel, doneCh := rig.runRelay(t)
	t.Cleanup(func() { rig.shutdown(t, cancel, doneCh) })

	require.NoError(t, os.WriteFile(filepath.Join(rig.sess.CwdPath, "code.go"), []byte("package x\nfunc Needle() {}\n"), 0o644))

	resp := relayRoundTrip(t, rig, 202, "fs/grep", map[string]any{"pattern": "Needle", "sessionId": "a"})
	require.True(t, resp.IsResponse())
	require.Nil(t, resp.Error)
	var out struct {
		Matches []struct {
			File string `json:"file"`
			Line int    `json:"line"`
		} `json:"matches"`
	}
	require.NoError(t, json.Unmarshal(resp.Result, &out))
	require.Len(t, out.Matches, 1)
	assert.Equal(t, "code.go", out.Matches[0].File)
	assert.Equal(t, 2, out.Matches[0].Line)
}

func TestRelay_AgentFsGrep_PathEscapeRejected(t *testing.T) {
	t.Parallel()
	rig := newRelayTestRig(t)
	cancel, doneCh := rig.runRelay(t)
	t.Cleanup(func() { rig.shutdown(t, cancel, doneCh) })

	outside := filepath.Join(filepath.VolumeName(rig.sess.CwdPath)+string(filepath.Separator), "etc")
	resp := relayRoundTrip(t, rig, 203, "fs/grep", map[string]any{"pattern": "x", "path": outside, "sessionId": "a"})
	require.True(t, resp.IsResponse())
	require.NotNil(t, resp.Error)
	assert.Equal(t, -32001, resp.Error.Code)
}
