package unread

import (
	"strings"
	"testing"
	"time"

	unreadservice "github.com/tidbcloud/AgentTeam/internal/unread"
)

func TestMergeStoredTaskSnapshotRejectsLateStaleRegularDomains(t *testing.T) {
	now := time.Date(2026, 7, 29, 3, 0, 0, 0, time.UTC)
	barrier := taskSnapshotFixture(now)
	barrier.MessageUnreadCount = 3
	barrier.LatestMessageAccountedSeq = 30
	barrier.ScopeMessageGeneration = 30
	barrier.ActivityUnreadCount = 2
	barrier.LatestActivityAccountedSeq = 20
	barrier.ScopeActivityGeneration = 20
	barrier.ManualUnread = true
	barrier.ManualUnreadGeneration = 9
	barrier.MembershipVisibility = "public"
	barrier.MembershipGeneration = 8
	barrier.NotificationState = "active"
	barrier.NotificationGeneration = 7

	version, payload, err := encodeTaskSnapshotShadow(barrier)
	if err != nil {
		t.Fatalf("encodeTaskSnapshotShadow returned error: %v", err)
	}
	staleRegular := barrier
	staleRegular.MessageUnreadCount = 1
	staleRegular.LatestMessageAccountedSeq = 10
	staleRegular.ScopeMessageGeneration = 10
	staleRegular.ActivityUnreadCount = 0
	staleRegular.LatestActivityAccountedSeq = 5
	staleRegular.ScopeActivityGeneration = 5
	staleRegular.ManualUnread = false
	staleRegular.ManualUnreadGeneration = 4
	staleRegular.MembershipVisibility = "hidden"
	staleRegular.MembershipGeneration = 4
	staleRegular.NotificationState = "muted"
	staleRegular.NotificationGeneration = 4
	staleRegular.UpdatedAt = now.Add(time.Minute) // commit/capture time is not the fence

	got, err := mergeStoredTaskSnapshot(staleRegular, version, []byte(payload))
	if err != nil {
		t.Fatalf("mergeStoredTaskSnapshot returned error: %v", err)
	}
	if got.MessageUnreadCount != 3 || got.LatestMessageAccountedSeq != 30 {
		t.Fatalf("message domain = (%d, %d), want barrier (3, 30)", got.MessageUnreadCount, got.LatestMessageAccountedSeq)
	}
	if got.ActivityUnreadCount != 2 || got.LatestActivityAccountedSeq != 20 {
		t.Fatalf("activity domain = (%d, %d), want barrier (2, 20)", got.ActivityUnreadCount, got.LatestActivityAccountedSeq)
	}
	if !got.ManualUnread || got.MembershipVisibility != "public" || got.NotificationState != "active" {
		t.Fatalf("control domains regressed to stale regular row: %+v", got)
	}
}

func TestMergeStoredTaskSnapshotIgnoresUnprovableNewerRegularAggregatesButKeepsControls(t *testing.T) {
	now := time.Date(2026, 7, 29, 3, 10, 0, 0, time.UTC)
	barrier := taskSnapshotFixture(now)
	barrier.MessageUnreadCount = 1
	barrier.LatestMessageAccountedSeq = 10
	barrier.ScopeMessageGeneration = 10
	barrier.ActivityUnreadCount = 1
	barrier.LatestActivityAccountedSeq = 20
	barrier.ScopeActivityGeneration = 20
	barrier.RewindGeneration = 20
	barrier.ManualUnread = true
	barrier.ManualUnreadGeneration = 3
	barrier.MembershipVisibility = "public"
	barrier.MembershipGeneration = 3
	barrier.NotificationState = "active"
	barrier.NotificationGeneration = 3
	version, payload, err := encodeTaskSnapshotShadow(barrier)
	if err != nil {
		t.Fatalf("encodeTaskSnapshotShadow returned error: %v", err)
	}

	newer := barrier
	newer.MessageUnreadCount = 2
	newer.LatestMessageAccountedSeq = 11
	newer.ScopeMessageGeneration = 11
	newer.ActivityUnreadCount = 4
	newer.LatestActivityAccountedSeq = 24
	newer.ScopeActivityGeneration = 24
	newer.RewindGeneration = 24
	newer.ManualUnread = false
	newer.ManualUnreadGeneration = 4
	newer.MembershipVisibility = "hidden"
	newer.MembershipGeneration = 4
	newer.NotificationState = "muted"
	newer.NotificationGeneration = 4

	got, err := mergeStoredTaskSnapshot(newer, version, []byte(payload))
	if err != nil {
		t.Fatalf("mergeStoredTaskSnapshot returned error: %v", err)
	}
	if got.MessageUnreadCount != 1 || got.LatestMessageAccountedSeq != 10 {
		t.Fatalf("message domain = (%d, %d), want protected shadow (1, 10)", got.MessageUnreadCount, got.LatestMessageAccountedSeq)
	}
	if got.ActivityUnreadCount != 1 || got.LatestActivityAccountedSeq != 20 {
		t.Fatalf("activity domain = (%d, %d), want protected shadow (1, 20)", got.ActivityUnreadCount, got.LatestActivityAccountedSeq)
	}
	if got.ScopeMessageGeneration != 10 ||
		got.ScopeActivityGeneration != 20 ||
		got.RewindGeneration != 20 {
		t.Fatalf(
			"opaque generations = (%d, %d, %d), want protected shadow (10, 20, 20)",
			got.ScopeMessageGeneration,
			got.ScopeActivityGeneration,
			got.RewindGeneration,
		)
	}
	if got.ManualUnread || got.MembershipVisibility != "hidden" || got.NotificationState != "muted" {
		t.Fatalf("newer regular controls were lost: %+v", got)
	}
}

func TestEstablishTaskSnapshotBarrierIgnoresLaterLegacyOpaqueDomain(t *testing.T) {
	now := time.Date(2026, 7, 29, 3, 15, 0, 0, time.UTC)
	legacy := taskSnapshotFixture(now.Add(time.Minute))
	legacy.MessageUnreadCount = 4
	legacy.LatestMessageAccountedSeq = 12
	legacy.ScopeMessageGeneration = 12
	legacy.ActivityUnreadCount = 3
	legacy.LatestActivityAccountedSeq = 22
	legacy.ScopeActivityGeneration = 22
	legacy.RewindGeneration = 12

	pendingV2 := taskSnapshotFixture(now)
	pendingV2.MessageUnreadCount = 1
	pendingV2.LatestMessageAccountedSeq = 11
	pendingV2.ScopeMessageGeneration = 11
	pendingV2.ActivityUnreadCount = 2
	pendingV2.LatestActivityAccountedSeq = 21
	pendingV2.ScopeActivityGeneration = 21
	pendingV2.RewindGeneration = 11

	got, err := establishTaskSnapshotBarrier(legacy, pendingV2)
	if err != nil {
		t.Fatalf("establishTaskSnapshotBarrier returned error: %v", err)
	}
	if got.MessageUnreadCount != 1 ||
		got.LatestMessageAccountedSeq != 11 ||
		got.ScopeMessageGeneration != 11 {
		t.Fatalf(
			"message domain = (%d, %d, %d), want pending v2 (1, 11, 11)",
			got.MessageUnreadCount,
			got.LatestMessageAccountedSeq,
			got.ScopeMessageGeneration,
		)
	}
	if got.ActivityUnreadCount != 2 ||
		got.LatestActivityAccountedSeq != 21 ||
		got.ScopeActivityGeneration != 21 {
		t.Fatalf(
			"activity domain = (%d, %d, %d), want pending v2 (2, 21, 21)",
			got.ActivityUnreadCount,
			got.LatestActivityAccountedSeq,
			got.ScopeActivityGeneration,
		)
	}
	if got.RewindGeneration != 11 {
		t.Fatalf("rewind generation = %d, want pending v2 11", got.RewindGeneration)
	}
}

func TestMergeStoredTaskSnapshotAppliesNewerReadCursorWithoutMaxingCount(t *testing.T) {
	now := time.Date(2026, 7, 29, 3, 20, 0, 0, time.UTC)
	barrier := taskSnapshotFixture(now)
	barrier.MessageUnreadCount = 4
	barrier.LatestMessageAccountedSeq = 14
	barrier.ScopeMessageGeneration = 14
	barrier.ActivityUnreadCount = 3
	barrier.LatestActivityAccountedSeq = 9
	barrier.ScopeActivityGeneration = 9
	version, payload, err := encodeTaskSnapshotShadow(barrier)
	if err != nil {
		t.Fatalf("encodeTaskSnapshotShadow returned error: %v", err)
	}

	read := barrier
	read.MessageUnreadCount = 0
	read.LastMessageReadSeq = 14
	read.ActivityUnreadCount = 0
	read.LastActivityReadSeq = 9
	got, err := mergeStoredTaskSnapshot(read, version, []byte(payload))
	if err != nil {
		t.Fatalf("mergeStoredTaskSnapshot returned error: %v", err)
	}
	if got.MessageUnreadCount != 0 || got.ActivityUnreadCount != 0 {
		t.Fatalf("newer read cursor must clear counts, got message=%d activity=%d", got.MessageUnreadCount, got.ActivityUnreadCount)
	}
}

func TestAdvanceTaskSnapshotBarrierAcceptsCandidateContainingProtectedAggregate(t *testing.T) {
	now := time.Date(2026, 7, 29, 3, 30, 0, 0, time.UTC)
	barrier := taskSnapshotFixture(now)
	barrier.LatestMessageAccountedSeq = 10
	barrier.ScopeMessageGeneration = 10
	barrier.MessageUnreadCount = 1
	periodic := barrier
	periodic.LatestMessageAccountedSeq = 12
	periodic.ScopeMessageGeneration = 12
	periodic.MessageUnreadCount = 3
	periodic.UpdatedAt = now.Add(time.Minute)

	got, err := advanceTaskSnapshotBarrier(barrier, periodic)
	if err != nil {
		t.Fatalf("advanceTaskSnapshotBarrier returned error: %v", err)
	}
	if got.LatestMessageAccountedSeq != 12 || got.MessageUnreadCount != 3 {
		t.Fatalf("Task barrier did not advance protected state: %+v", got)
	}
}

func TestAdvanceTaskSnapshotBarrierAllowsUnchangedCountAtAdvancedFrontier(t *testing.T) {
	now := time.Date(2026, 7, 29, 3, 35, 0, 0, time.UTC)
	protected := taskSnapshotFixture(now)
	protected.LatestMessageAccountedSeq = 11
	protected.ScopeMessageGeneration = 11
	protected.MessageUnreadCount = 1
	disjoint := protected
	disjoint.LatestMessageAccountedSeq = 12
	disjoint.ScopeMessageGeneration = 12
	disjoint.MessageUnreadCount = 1
	disjoint.UpdatedAt = now.Add(time.Minute)

	got, err := advanceTaskSnapshotBarrier(protected, disjoint)
	if err != nil {
		t.Fatalf("advanceTaskSnapshotBarrier returned error: %v", err)
	}
	if got.MessageUnreadCount != 1 || got.LatestMessageAccountedSeq != 12 {
		t.Fatalf("advanced aggregate = (%d, %d), want (1, 12)", got.MessageUnreadCount, got.LatestMessageAccountedSeq)
	}
}

func TestMergeStoredTaskSnapshotRejectsPartialLegacyCursor(t *testing.T) {
	now := time.Date(2026, 7, 29, 3, 37, 0, 0, time.UTC)
	protected := taskSnapshotFixture(now)
	protected.MessageUnreadCount = 3
	protected.LatestMessageAccountedSeq = 30
	protected.ScopeMessageGeneration = 30
	version, payload, err := encodeTaskSnapshotShadow(protected)
	if err != nil {
		t.Fatalf("encodeTaskSnapshotShadow returned error: %v", err)
	}
	partial := protected
	partial.LastMessageReadSeq = 20
	partial.MessageUnreadCount = 1

	if _, err := mergeStoredTaskSnapshot(partial, version, []byte(payload)); err == nil {
		t.Fatal("mergeStoredTaskSnapshot error = nil, want partial opaque read suppressed")
	}
}

func TestMergeStoredTaskSnapshotRejectsInvalidOrUnsupportedShadow(t *testing.T) {
	regular := taskSnapshotFixture(time.Date(2026, 7, 29, 3, 40, 0, 0, time.UTC))
	tests := []struct {
		name    string
		version int64
		payload string
	}{
		{name: "payload_without_version", version: 0, payload: `{}`},
		{name: "missing_payload", version: taskSnapshotShadowFormatV1},
		{name: "unsupported_version", version: taskSnapshotShadowFormatV1 + 1, payload: `{}`},
		{name: "invalid_json", version: taskSnapshotShadowFormatV1, payload: `{`},
		{name: "unknown_field", version: taskSnapshotShadowFormatV1, payload: `{"future":true}`},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			_, err := mergeStoredTaskSnapshot(regular, test.version, []byte(test.payload))
			if err == nil {
				t.Fatal("mergeStoredTaskSnapshot error = nil, want fail-safe suppression")
			}
		})
	}
}

func TestEncodeTaskSnapshotShadowLeavesOrdinaryV1SnapshotUnchanged(t *testing.T) {
	snapshot := taskSnapshotFixture(time.Date(2026, 7, 29, 3, 50, 0, 0, time.UTC))
	snapshot.ScopeID = "chn_1"
	snapshot.ScopeType = unreadservice.ScopeTypeChannel
	version, payload, err := encodeTaskSnapshotShadow(snapshot)
	if err != nil {
		t.Fatalf("encodeTaskSnapshotShadow returned error: %v", err)
	}
	if version != 0 || strings.TrimSpace(payload) != "" {
		t.Fatalf("ordinary snapshot shadow = (%d, %q), want (0, empty)", version, payload)
	}
}

func TestMergeStoredTaskSnapshotRejectsShadowOnOrdinaryScope(t *testing.T) {
	task := taskSnapshotFixture(time.Date(2026, 7, 29, 3, 55, 0, 0, time.UTC))
	version, payload, err := encodeTaskSnapshotShadow(task)
	if err != nil {
		t.Fatalf("encodeTaskSnapshotShadow returned error: %v", err)
	}
	ordinary := task
	ordinary.ScopeID = "chn_1"
	ordinary.ScopeType = unreadservice.ScopeTypeChannel
	if _, err := mergeStoredTaskSnapshot(ordinary, version, []byte(payload)); err == nil {
		t.Fatal("mergeStoredTaskSnapshot error = nil, want non-Task shadow suppressed")
	}
}

func taskSnapshotFixture(now time.Time) unreadservice.Snapshot {
	return unreadservice.Snapshot{
		WorkspaceID:          "w1",
		MemberID:             "m1",
		ScopeID:              "tsk_1",
		ScopeType:            unreadservice.ScopeTypeTask,
		MembershipVisibility: "public",
		NotificationState:    "active",
		UpdatedAt:            now,
	}
}
