package acp_test

import (
	"bytes"
	"encoding/json"
	"io"
	"testing"

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

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

func TestJSONRPC_RoundtripRequestNumberID(t *testing.T) {
	t.Parallel()
	req, err := acp.NewRequestInt(42, "session/prompt", map[string]any{"text": "hi"})
	require.NoError(t, err)

	var buf bytes.Buffer
	require.NoError(t, acp.NewEncoder(&buf).Encode(req))

	dec := acp.NewDecoder(&buf, 0)
	msg, err := dec.DecodeMessage()
	require.NoError(t, err)

	assert.True(t, msg.IsRequest())
	assert.Equal(t, "session/prompt", msg.Method)
	id, ok := msg.IDInt64()
	require.True(t, ok)
	assert.Equal(t, int64(42), id)
}

func TestJSONRPC_RoundtripStringID(t *testing.T) {
	t.Parallel()
	idJSON, _ := json.Marshal("client-abc")
	req := &acp.Message{JSONRPC: "2.0", ID: idJSON, Method: "session/prompt"}

	var buf bytes.Buffer
	require.NoError(t, acp.NewEncoder(&buf).Encode(req))

	dec := acp.NewDecoder(&buf, 0)
	msg, _ := dec.DecodeMessage()
	s, ok := msg.IDString()
	require.True(t, ok)
	assert.Equal(t, "client-abc", s)

	_, ok2 := msg.IDInt64()
	assert.False(t, ok2)
}

func TestJSONRPC_Notification_NoID(t *testing.T) {
	t.Parallel()
	notif, _ := acp.NewNotification("session/update", map[string]string{"x": "y"})

	var buf bytes.Buffer
	require.NoError(t, acp.NewEncoder(&buf).Encode(notif))

	msg, _ := acp.NewDecoder(&buf, 0).DecodeMessage()
	assert.True(t, msg.IsNotification())
	assert.False(t, msg.IsRequest())
	assert.False(t, msg.IsResponse())
}

func TestJSONRPC_Response(t *testing.T) {
	t.Parallel()
	idJSON, _ := json.Marshal(int64(7))
	resp, _ := acp.NewResponseOK(idJSON, map[string]string{"sessionId": "abc"})

	var buf bytes.Buffer
	require.NoError(t, acp.NewEncoder(&buf).Encode(resp))

	msg, _ := acp.NewDecoder(&buf, 0).DecodeMessage()
	assert.True(t, msg.IsResponse())

	var result struct {
		SessionID string `json:"sessionId"`
	}
	require.NoError(t, json.Unmarshal(msg.Result, &result))
	assert.Equal(t, "abc", result.SessionID)
}

func TestJSONRPC_ErrorResponse(t *testing.T) {
	t.Parallel()
	idJSON, _ := json.Marshal("client-1")
	resp := acp.NewResponseErr(idJSON, acp.JSONRPCMethodNotFound, "no such method")

	var buf bytes.Buffer
	require.NoError(t, acp.NewEncoder(&buf).Encode(resp))

	msg, _ := acp.NewDecoder(&buf, 0).DecodeMessage()
	require.NotNil(t, msg.Error)
	assert.Equal(t, acp.JSONRPCMethodNotFound, msg.Error.Code)
	assert.Equal(t, "no such method", msg.Error.Message)
}

func TestJSONRPC_DecoderEOF(t *testing.T) {
	t.Parallel()
	dec := acp.NewDecoder(bytes.NewReader(nil), 0)
	_, err := dec.DecodeMessage()
	assert.ErrorIs(t, err, io.EOF)
}

func TestJSONRPC_DecoderMaxSize(t *testing.T) {
	t.Parallel()
	huge := make([]byte, 200)
	for i := range huge {
		huge[i] = 'a'
	}
	body := []byte(`{"jsonrpc":"2.0","method":"x","params":"`)
	body = append(body, huge...)
	body = append(body, []byte(`"}`+"\n")...)

	dec := acp.NewDecoder(bytes.NewReader(body), 100)
	_, err := dec.DecodeMessage()
	require.Error(t, err)
	assert.Contains(t, err.Error(), "exceeds max size")
}

func TestJSONRPC_DecoderBadJSON(t *testing.T) {
	t.Parallel()
	dec := acp.NewDecoder(bytes.NewReader([]byte("not-json\n")), 0)
	_, err := dec.DecodeMessage()
	require.Error(t, err)
}

func TestJSONRPC_EncoderConcurrentSafe(t *testing.T) {
	t.Parallel()
	var buf bytes.Buffer
	enc := acp.NewEncoder(&buf)

	const N = 100
	done := make(chan struct{}, N)
	for i := 0; i < N; i++ {
		go func(id int64) {
			req, _ := acp.NewRequestInt(id, "m", nil)
			_ = enc.Encode(req)
			done <- struct{}{}
		}(int64(i))
	}
	for i := 0; i < N; i++ {
		<-done
	}

	dec := acp.NewDecoder(&buf, 0)
	count := 0
	for {
		_, err := dec.DecodeMessage()
		if err == io.EOF {
			break
		}
		require.NoError(t, err)
		count++
	}
	assert.Equal(t, N, count)
}
