package acp_test

import (
	"context"
	"encoding/json"
	"sync"
	"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/acp/handlers"
)

// ----- NormalizeStopReason -----

func TestNormalizeStopReason(t *testing.T) {
	t.Parallel()
	cases := []struct {
		raw  string
		want acp.StopReason
	}{
		{"end_turn", acp.StopEndTurn},
		{"max_tokens", acp.StopMaxTokens},
		{"refusal", acp.StopRefusal},
		{"cancelled", acp.StopCancelled},
		{"vendor_specific_thing", acp.StopOther},
		{"", acp.StopOther},
	}
	for _, c := range cases {
		assert.Equal(t, c.want, acp.NormalizeStopReason(c.raw), "raw=%q", c.raw)
	}
}

func TestParseStopReason(t *testing.T) {
	t.Parallel()
	assert.Equal(t, "end_turn", acp.ParseStopReason(json.RawMessage(`{"stopReason":"end_turn"}`)))
	assert.Equal(t, "max_tokens", acp.ParseStopReason(json.RawMessage(`{"stopReason":"max_tokens","other":1}`)))
	assert.Equal(t, "", acp.ParseStopReason(json.RawMessage(`{}`)))
	assert.Equal(t, "", acp.ParseStopReason(nil))
	assert.Equal(t, "", acp.ParseStopReason(json.RawMessage(`not json`)))
}

// ----- fakeTurnRepo: 记录 turn 调用 -----

type fakeTurnRepo struct {
	mu              sync.Mutex
	inserted        []*acp.Turn
	completed       []completedCall
	aborted         []int
	lastStopUpdates []string
}

type completedCall struct {
	turnIndex   int
	stop        string
	updateCount int
}

func (f *fakeTurnRepo) InsertTurn(_ context.Context, t *acp.Turn) (*acp.Turn, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	cp := *t
	cp.ID = int64(len(f.inserted) + 1)
	cp.Status = acp.TurnInProgress
	f.inserted = append(f.inserted, &cp)
	out := cp
	return &out, nil
}
func (f *fakeTurnRepo) MarkTurnCompleted(_ context.Context, _ uuid.UUID, turnIndex int, stop string, updateCount int) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.completed = append(f.completed, completedCall{turnIndex, stop, updateCount})
	return nil
}
func (f *fakeTurnRepo) MarkTurnAborted(_ context.Context, _ uuid.UUID, turnIndex int) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.aborted = append(f.aborted, turnIndex)
	return nil
}
func (f *fakeTurnRepo) GetLatestTurnBySession(context.Context, uuid.UUID) (*acp.Turn, error) {
	return nil, nil
}
func (f *fakeTurnRepo) UpdateSessionLastStopReason(_ context.Context, _ uuid.UUID, reason string) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.lastStopUpdates = append(f.lastStopUpdates, reason)
	return nil
}

// ----- fakeBus: 记录发布的事件，保留顺序 -----

type fakeBus struct {
	mu     sync.Mutex
	events []acp.TurnEvent
}

func (b *fakeBus) Subscribe(string, func(context.Context, acp.TurnEvent)) {}
func (b *fakeBus) Publish(_ context.Context, ev acp.TurnEvent) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.events = append(b.events, ev)
}
func (b *fakeBus) snapshot() []acp.TurnEvent {
	b.mu.Lock()
	defer b.mu.Unlock()
	out := make([]acp.TurnEvent, len(b.events))
	copy(out, b.events)
	return out
}

func newTestSessCtx() handlers.SessionContext {
	return handlers.SessionContext{
		SessionID:   uuid.New(),
		WorkspaceID: uuid.New(),
		UserID:      uuid.New(),
	}
}

// ----- TurnTracker lifecycle -----

func TestTurnTracker_Lifecycle(t *testing.T) {
	t.Parallel()
	repo := &fakeTurnRepo{}
	bus := &fakeBus{}
	sess := newTestSessCtx()
	tr := acp.NewTurnTracker(repo, bus, sess, nil)

	ctx := context.Background()
	idx, err := tr.StartTurn(ctx, "client-init")
	require.NoError(t, err)
	assert.Equal(t, 0, idx)
	require.Len(t, repo.inserted, 1)
	assert.Equal(t, 0, repo.inserted[0].TurnIndex)

	// 3 个 update
	const n = 3
	for i := 0; i < n; i++ {
		tr.OnUpdate(ctx)
	}

	// id 不匹配 → 不应完成
	tr.OnPromptResponse(ctx, "wrong-id", "end_turn")
	assert.Empty(t, repo.completed, "mismatched id must not complete")

	// 匹配 → 完成
	tr.OnPromptResponse(ctx, "client-init", "end_turn")
	require.Len(t, repo.completed, 1)
	assert.Equal(t, 0, repo.completed[0].turnIndex)
	assert.Equal(t, "end_turn", repo.completed[0].stop)
	assert.Equal(t, n, repo.completed[0].updateCount, "update_count must equal observed updates")

	require.Len(t, repo.lastStopUpdates, 1)
	assert.Equal(t, "end_turn", repo.lastStopUpdates[0])

	// 事件顺序：turn_completed 然后 session_idle
	evs := bus.snapshot()
	require.Len(t, evs, 2)
	assert.Equal(t, acp.TurnCompletedEvent, evs[0].Kind)
	assert.Equal(t, acp.SessionIdleEvent, evs[1].Kind)
	assert.Equal(t, acp.StopEndTurn, evs[0].StopReason)
	assert.Equal(t, "end_turn", evs[0].RawStopReason)
	assert.Equal(t, sess.SessionID, evs[0].SessionID)
	assert.Equal(t, sess.UserID, evs[0].UserID)

	// 重复响应 → no-op（回合已闭合）
	tr.OnPromptResponse(ctx, "client-init", "end_turn")
	assert.Len(t, repo.completed, 1, "duplicate response must be ignored")
}

func TestTurnTracker_UnknownStopReason(t *testing.T) {
	t.Parallel()
	repo := &fakeTurnRepo{}
	bus := &fakeBus{}
	tr := acp.NewTurnTracker(repo, bus, newTestSessCtx(), nil)
	ctx := context.Background()

	_, err := tr.StartTurn(ctx, "p1")
	require.NoError(t, err)
	tr.OnPromptResponse(ctx, "p1", "weird_vendor_reason")

	require.Len(t, repo.completed, 1)
	// 原始字符串原样落库
	assert.Equal(t, "weird_vendor_reason", repo.completed[0].stop)
	assert.Equal(t, "weird_vendor_reason", repo.lastStopUpdates[0])
	// 归一化为 other
	evs := bus.snapshot()
	require.Len(t, evs, 2)
	assert.Equal(t, acp.StopOther, evs[0].StopReason)
	assert.Equal(t, "weird_vendor_reason", evs[0].RawStopReason)
}

func TestTurnTracker_Abort(t *testing.T) {
	t.Parallel()
	repo := &fakeTurnRepo{}
	bus := &fakeBus{}
	tr := acp.NewTurnTracker(repo, bus, newTestSessCtx(), nil)
	ctx := context.Background()

	_, err := tr.StartTurn(ctx, "p1")
	require.NoError(t, err)
	tr.Abort(ctx, "crashed")

	require.Len(t, repo.aborted, 1)
	assert.Equal(t, 0, repo.aborted[0])
	evs := bus.snapshot()
	require.Len(t, evs, 1)
	assert.Equal(t, acp.SessionIdleEvent, evs[0].Kind)

	// 无开放回合再 Abort → no-op
	tr.Abort(ctx, "crashed")
	assert.Len(t, repo.aborted, 1, "abort with no open turn must be no-op")
}

func TestTurnTracker_AutoAbortPriorTurn(t *testing.T) {
	t.Parallel()
	repo := &fakeTurnRepo{}
	bus := &fakeBus{}
	tr := acp.NewTurnTracker(repo, bus, newTestSessCtx(), nil)
	ctx := context.Background()

	idx0, err := tr.StartTurn(ctx, "p1")
	require.NoError(t, err)
	assert.Equal(t, 0, idx0)
	// 第二次 StartTurn 在前一个开放时 → auto-abort 前一个
	idx1, err := tr.StartTurn(ctx, "p2")
	require.NoError(t, err)
	assert.Equal(t, 1, idx1)
	require.Len(t, repo.aborted, 1)
	assert.Equal(t, 0, repo.aborted[0], "prior open turn must be auto-aborted")
	require.Len(t, repo.inserted, 2)
}

func TestTurnTracker_NilSafe(t *testing.T) {
	t.Parallel()
	var tr *acp.TurnTracker
	ctx := context.Background()
	// 所有方法在 nil receiver 上安全 no-op
	_, err := tr.StartTurn(ctx, "x")
	assert.NoError(t, err)
	tr.OnUpdate(ctx)
	tr.OnPromptResponse(ctx, "x", "end_turn")
	tr.Abort(ctx, "x")
	assert.False(t, tr.IsTrackedPrompt("x"))
}
