package postgres

import (
	"context"
	"net/http"
	"testing"
	"time"

	"github.com/bridge/ai-customer-service/internal/domain/platformevent"
)

func TestPlatformEventStore_ShouldInsertPendingEvent(t *testing.T) {
	db := openDBForTest(t)
	defer db.Close()

	store := NewPlatformEventStore(db)
	now := time.Now().UTC().Truncate(time.Second)
	event := &platformevent.Event{
		ID:              uniqueID("evt"),
		Platform:        "sub2api",
		EventType:       platformevent.TypeMessageReceived,
		Payload:         map[string]any{"message": "hello"},
		Status:          platformevent.StatusPending,
		AttemptCount:    0,
		NextAttemptAt:   now,
		OccurredAt:      now,
		CreatedAt:       now,
		UpdatedAt:       now,
		SourceMessageID: uniqueID("msg"),
	}

	if err := store.InsertPending(context.Background(), event); err != nil {
		t.Fatalf("InsertPending() error = %v", err)
	}
	var (
		status string
	)

	if err := db.QueryRowContext(context.Background(), `
		SELECT status
		FROM cs_platform_event_outbox
		WHERE id = $1
	`, event.ID).Scan(&status); err != nil {
		t.Fatalf("query inserted event failed: %v", err)
	}
	if status != string(platformevent.StatusPending) {
		t.Fatalf("status = %s, want %s", status, platformevent.StatusPending)
	}
}

func TestPlatformEventStore_ShouldListPendingEventsInOrder(t *testing.T) {
	db := openDBForTest(t)
	defer db.Close()

	store := NewPlatformEventStore(db)
	now := time.Now().UTC().Truncate(time.Second)
	platformName := "s2a-" + uniqueID("plt")[:8]

	first := &platformevent.Event{
		ID:        uniqueID("evt"),
		Platform:  platformName,
		EventType: platformevent.TypeMessageReceived,
		Payload:   map[string]any{"step": 1},
		Status:    platformevent.StatusPending,
		NextAttemptAt:  now,
		OccurredAt:     now.Add(-2 * time.Millisecond),
		CreatedAt:      now.Add(-2 * time.Millisecond),
		UpdatedAt:      now.Add(-2 * time.Millisecond),
	}
	second := &platformevent.Event{
		ID:        uniqueID("evt"),
		Platform:  platformName,
		EventType: platformevent.TypeMessageProcessing,
		Payload:   map[string]any{"step": 2},
		Status:    platformevent.StatusPending,
		NextAttemptAt:  now,
		OccurredAt:     now.Add(-1 * time.Millisecond),
		CreatedAt:      now.Add(-1 * time.Millisecond),
		UpdatedAt:      now.Add(-1 * time.Millisecond),
	}

	if err := store.InsertPending(context.Background(), second); err != nil {
		t.Fatalf("InsertPending(second) error = %v", err)
	}
	if err := store.InsertPending(context.Background(), first); err != nil {
		t.Fatalf("InsertPending(first) error = %v", err)
	}

	events, err := store.ListDue(context.Background(), platformName, now, 10)
	if err != nil {
		t.Fatalf("ListDue() error = %v", err)
	}
	if len(events) < 2 {
		t.Fatalf("due events count = %d, want at least 2", len(events))
	}

	if events[0].EventType != platformevent.TypeMessageReceived {
		t.Fatalf("first due event type = %s, want %s", events[0].EventType, platformevent.TypeMessageReceived)
	}
	if events[1].EventType != platformevent.TypeMessageProcessing {
		t.Fatalf("second due event type = %s, want %s", events[1].EventType, platformevent.TypeMessageProcessing)
	}
	if events[0].OccurredAt.After(events[1].OccurredAt) {
		t.Fatalf("due events out of order: first occurred_at=%s second occurred_at=%s", events[0].OccurredAt, events[1].OccurredAt)
	}
}

func TestPlatformEventStore_ShouldPersistDeliveryAttemptAudit(t *testing.T) {
	db := openDBForTest(t)
	defer db.Close()

	store := NewPlatformEventStore(db)
	now := time.Now().UTC().Truncate(time.Second)
	event := &platformevent.Event{
		ID:        uniqueID("evt"),
		Platform:  "s2a-" + uniqueID("plt")[:8],
		EventType: platformevent.TypeReplyGenerated,
		Payload:   map[string]any{"reply": "好的"},
		Status:    platformevent.StatusPending,
		NextAttemptAt:  now,
		OccurredAt:     now,
		CreatedAt:      now,
		UpdatedAt:      now,
	}

	if err := store.InsertPending(context.Background(), event); err != nil {
		t.Fatalf("InsertPending() error = %v", err)
	}
	if err := store.RecordDeliveryAttempt(context.Background(), event.ID, 1, http.StatusBadGateway, `{"error":"upstream"}`, ""); err != nil {
		t.Fatalf("RecordDeliveryAttempt() error = %v", err)
	}

	var (
		attemptNo      int
		responseStatus int
	)
	if err := db.QueryRowContext(context.Background(), `
		SELECT attempt_no, response_status
		FROM cs_platform_event_delivery_attempts
		WHERE event_id = $1
		ORDER BY created_at DESC
		LIMIT 1
	`, event.ID).Scan(&attemptNo, &responseStatus); err != nil {
		t.Fatalf("query delivery attempt failed: %v", err)
	}
	if attemptNo != 1 {
		t.Fatalf("attempt no = %d, want 1", attemptNo)
	}
	if responseStatus != http.StatusBadGateway {
		t.Fatalf("response status = %d, want %d", responseStatus, http.StatusBadGateway)
	}
}

func TestPlatformEventStore_ShouldMoveToDeadLetter(t *testing.T) {
	db := openDBForTest(t)
	defer db.Close()

	store := NewPlatformEventStore(db)
	now := time.Now().UTC().Truncate(time.Second)
	event := &platformevent.Event{
		ID:        uniqueID("evt"),
		Platform:  "s2a-" + uniqueID("plt")[:8],
		EventType: platformevent.TypeReplyGenerated,
		Payload:   map[string]any{"reply": "失败"},
		Status:    platformevent.StatusPending,
		NextAttemptAt:  now,
		OccurredAt:     now,
		CreatedAt:      now,
		UpdatedAt:      now,
	}

	if err := store.InsertPending(context.Background(), event); err != nil {
		t.Fatalf("InsertPending() error = %v", err)
	}
	if err := store.MarkDeadLetter(context.Background(), event.ID, 5, "callback failed"); err != nil {
		t.Fatalf("MarkDeadLetter() error = %v", err)
	}

	var status string
	if err := db.QueryRowContext(context.Background(), `SELECT status FROM cs_platform_event_outbox WHERE id = $1`, event.ID).Scan(&status); err != nil {
		t.Fatalf("query outbox status failed: %v", err)
	}
	if status != string(platformevent.StatusDeadLetter) {
		t.Fatalf("status = %s, want %s", status, platformevent.StatusDeadLetter)
	}

	var finalError string
	if err := db.QueryRowContext(context.Background(), `SELECT final_error FROM cs_platform_event_dead_letters WHERE event_id = $1`, event.ID).Scan(&finalError); err != nil {
		t.Fatalf("query dead letter failed: %v", err)
	}
	if finalError != "callback failed" {
		t.Fatalf("final error = %s, want callback failed", finalError)
	}
}
