package activitymq

import (
	"strings"
	"testing"
	"time"
)

func TestActivityTemplateFactRoundTrip(t *testing.T) {
	occurredAt := time.Date(2026, time.July, 14, 8, 30, 0, 0, time.UTC).UnixMilli()
	want := ActivityTemplateFactMessage{
		AppCode: "lalu", EventID: "activity:g1", EventType: EventTypeGiftScored,
		TemplateID: "template-1", TemplateCode: "summer_rank", PublishedVersion: 3,
		StatDay: "2026-07-14", RegionID: 9, UserID: 1001, GiftID: "rose",
		GiftCountDelta: 2, CoinAmountDelta: 200, OccurredAtMS: occurredAt,
	}
	body, err := EncodeActivityTemplateFactMessage(want)
	if err != nil {
		t.Fatalf("encode: %v", err)
	}
	got, err := DecodeActivityTemplateFactMessage(body)
	if err != nil {
		t.Fatalf("decode: %v", err)
	}
	if got.MessageType != MessageTypeActivityTemplateFact || got.EventID != want.EventID || got.CoinAmountDelta != 200 {
		t.Fatalf("unexpected round trip: %+v", got)
	}
}

func TestActivityTemplateFactRejectsMismatchedDayAndIncompleteDimensions(t *testing.T) {
	base := ActivityTemplateFactMessage{
		AppCode: "lalu", EventID: "activity:t1", EventType: EventTypeTaskProgressed,
		TemplateID: "template-1", TemplateCode: "summer_rank", PublishedVersion: 1,
		StatDay: "2026-07-13", UserID: 1001, TaskKey: "send_gift", TaskType: "gift",
		ProgressDelta: 1, OccurredAtMS: time.Date(2026, time.July, 14, 0, 0, 0, 0, time.UTC).UnixMilli(),
	}
	if _, err := EncodeActivityTemplateFactMessage(base); err == nil || !strings.Contains(err.Error(), "does not match") {
		t.Fatalf("expected stat day mismatch, got %v", err)
	}
	base.StatDay = "2026-07-14"
	base.TaskKey = ""
	if _, err := EncodeActivityTemplateFactMessage(base); err == nil {
		t.Fatal("expected incomplete task identity rejection")
	}
}

func TestTaskClaimedUsesImmutableEarningAttributionDay(t *testing.T) {
	earningAt := time.Date(2026, time.July, 14, 23, 50, 0, 0, time.UTC).UnixMilli()
	claimedAt := time.Date(2026, time.July, 17, 9, 15, 0, 0, time.UTC).UnixMilli()
	want := ActivityTemplateFactMessage{
		AppCode: "lalu", EventID: "activity:claim:delayed", EventType: EventTypeTaskClaimed,
		TemplateID: "template-1", TemplateCode: "summer_rank", PublishedVersion: 3,
		StatDay: "2026-07-14", RegionID: 9, UserID: 1001, TaskKey: "send_gift", TaskType: "gift",
		ClaimedDelta: 1, OccurredAtMS: claimedAt, AttributionAtMS: earningAt,
	}
	body, err := EncodeActivityTemplateFactMessage(want)
	if err != nil {
		t.Fatalf("encode delayed task claim: %v", err)
	}
	got, err := DecodeActivityTemplateFactMessage(body)
	if err != nil {
		t.Fatalf("decode delayed task claim: %v", err)
	}
	if got.StatDay != "2026-07-14" || got.OccurredAtMS != claimedAt || got.AttributionAtMS != earningAt {
		t.Fatalf("task claim attribution changed: %+v", got)
	}

	// A claim cannot relabel its earning day independently from the persisted
	// attribution timestamp, otherwise retry time or a moved user region could
	// rewrite already-earned statistics.
	want.AttributionAtMS = time.Date(2026, time.July, 15, 0, 0, 0, 0, time.UTC).UnixMilli()
	if _, err := EncodeActivityTemplateFactMessage(want); err == nil || !strings.Contains(err.Error(), "attribution") {
		t.Fatalf("expected attribution mismatch rejection, got %v", err)
	}
}

func TestActivityVisitedRequiresPositiveVisitDelta(t *testing.T) {
	visitedAt := time.Date(2026, time.July, 14, 8, 30, 0, 0, time.UTC).UnixMilli()
	fact := ActivityTemplateFactMessage{
		AppCode: "lalu", EventID: "activity:visit:1", EventType: EventTypeActivityVisited,
		TemplateID: "template-1", TemplateCode: "summer_rank", PublishedVersion: 3,
		StatDay: "2026-07-14", RegionID: 9, UserID: 1001, OccurredAtMS: visitedAt,
	}
	if _, err := EncodeActivityTemplateFactMessage(fact); err == nil {
		t.Fatal("expected zero visit delta rejection")
	}
	fact.VisitCountDelta = 1
	if _, err := EncodeActivityTemplateFactMessage(fact); err != nil {
		t.Fatalf("encode activity visit: %v", err)
	}
}

func TestActivityTemplateFactTagExpressionIsSafeAndDeterministic(t *testing.T) {
	expression, err := LegacyCompatibleTagExpression(EventTypeTaskClaimed, EventTypeGiftScored, EventTypeTaskClaimed)
	if err != nil {
		t.Fatalf("expression: %v", err)
	}
	if expression != "activity_template_fact || GiftScored || TaskClaimed" {
		t.Fatalf("unexpected expression: %s", expression)
	}
	if _, err := EventTypeTag("GiftScored || *"); err == nil {
		t.Fatal("expected unsafe tag rejection")
	}
}
