package dispatcher

import (
	"encoding/json"
	"testing"
	"time"

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

func fixedTime() time.Time {
	return time.Date(2025, 1, 15, 12, 0, 0, 0, time.UTC)
}

func TestDeferredMessageID(t *testing.T) {
	tests := []struct {
		name  string
		input string
		want  string
	}{
		{"valid", `{"message_id": "msg-123"}`, "msg-123"},
		{"whitespace trimmed", `{"message_id": "  msg-456  "}`, "msg-456"},
		{"missing field", `{"other": "value"}`, ""},
		{"invalid json", `{invalid}`, ""},
		{"empty", ``, ""},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := deferredMessageID(json.RawMessage(tt.input))
			if got != tt.want {
				t.Errorf("deferredMessageID() = %q, want %q", got, tt.want)
			}
		})
	}
}

func TestCompareDeferredDeliveryEntries(t *testing.T) {
	now := fixedTime()
	h := time.Hour

	tests := []struct {
		name string
		a    deferredDeliveryEntry
		b    deferredDeliveryEntry
		want int
	}{
		{
			"a before b by seq",
			deferredDeliveryEntry{Seq: 1, AcceptedAt: now, MessageID: "m1", Message: json.RawMessage(`{}`)},
			deferredDeliveryEntry{Seq: 2, AcceptedAt: now, MessageID: "m2", Message: json.RawMessage(`{}`)},
			-1,
		},
		{
			"b before a by seq",
			deferredDeliveryEntry{Seq: 2, AcceptedAt: now, MessageID: "m1", Message: json.RawMessage(`{}`)},
			deferredDeliveryEntry{Seq: 1, AcceptedAt: now, MessageID: "m2", Message: json.RawMessage(`{}`)},
			1,
		},
		{
			"same seq, a before b by time",
			deferredDeliveryEntry{Seq: 1, AcceptedAt: now.Add(-1 * h), MessageID: "m1", Message: json.RawMessage(`{}`)},
			deferredDeliveryEntry{Seq: 1, AcceptedAt: now, MessageID: "m2", Message: json.RawMessage(`{}`)},
			-1,
		},
		{
			"same seq and time, a before b by messageID",
			deferredDeliveryEntry{Seq: 1, AcceptedAt: now, MessageID: "m1", Message: json.RawMessage(`{}`)},
			deferredDeliveryEntry{Seq: 1, AcceptedAt: now, MessageID: "m2", Message: json.RawMessage(`{}`)},
			-1,
		},
		{
			"equal",
			deferredDeliveryEntry{Seq: 1, AcceptedAt: now, MessageID: "m1", Message: json.RawMessage(`{"same":"data"}`)},
			deferredDeliveryEntry{Seq: 1, AcceptedAt: now, MessageID: "m1", Message: json.RawMessage(`{"same":"data"}`)},
			0,
		},
		{
			"zero seq falls back to time",
			deferredDeliveryEntry{Seq: 0, AcceptedAt: now.Add(-1 * h), MessageID: "m1", Message: json.RawMessage(`{}`)},
			deferredDeliveryEntry{Seq: 0, AcceptedAt: now, MessageID: "m2", Message: json.RawMessage(`{}`)},
			-1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := compareDeferredDeliveryEntries(tt.a, tt.b)
			if got != tt.want {
				t.Errorf("compareDeferredDeliveryEntries() = %d, want %d", got, tt.want)
			}
		})
	}
}

func TestNormalizeDeferredDeliveryOrder(t *testing.T) {
	now := fixedTime()
	entries := []deferredDeliveryEntry{
		{Seq: 3, AcceptedAt: now, MessageID: "m3", Message: json.RawMessage(`{"id":3}`)},
		{Seq: 1, AcceptedAt: now, MessageID: "m1", Message: json.RawMessage(`{"id":1}`)},
		{Seq: 2, AcceptedAt: now, MessageID: "m2", Message: json.RawMessage(`{"id":2}`)},
	}

	changed := normalizeDeferredDeliveryOrder(entries)
	if !changed {
		t.Error("should return true when reordering is needed")
	}
	if entries[0].Seq != 1 || entries[1].Seq != 2 || entries[2].Seq != 3 {
		t.Error("entries should be sorted by seq")
	}

	changed = normalizeDeferredDeliveryOrder(entries)
	if changed {
		t.Error("should return false when already sorted")
	}
}

func TestNormalizeDeferredDeliveryOrder_EmptyAndSingle(t *testing.T) {
	changed := normalizeDeferredDeliveryOrder(nil)
	if changed {
		t.Error("nil entries should return false")
	}

	changed = normalizeDeferredDeliveryOrder([]deferredDeliveryEntry{})
	if changed {
		t.Error("empty entries should return false")
	}

	entries := []deferredDeliveryEntry{{Seq: 1}}
	changed = normalizeDeferredDeliveryOrder(entries)
	if changed {
		t.Error("single entry should return false")
	}
}

func TestReconcileDeferredDeliveriesForAgentUsesLocalIdentity(t *testing.T) {
	d := mustNewDispatcher("http://server.test")
	workDir := t.TempDir()
	d.sessionManager.Set("session-key", &interactiveSession{
		sessionKey:    "session-key",
		agentID:       "local-agent",
		serverAgentID: "server-agent",
		workDir:       workDir,
		startedAt:     time.Now(),
	})
	require.NoError(t, updateSessionResumeState(workDir, func(state *sessionResumeState) error {
		state.DeferredDeliveries = []deferredDeliveryEntry{{
			MessageID:     "message-1",
			ReplayPending: true,
		}}
		return nil
	}))

	suppressed, err := d.ReconcileDeferredDeliveriesForAgent("local-agent", []string{"message-1"})
	require.NoError(t, err)
	require.Equal(t, []string{"message-1"}, suppressed)

	state, err := loadSessionResumeState(workDir)
	require.NoError(t, err)
	require.Empty(t, state.DeferredDeliveries)
}
