package store

import (
	"context"
	"strings"
	"testing"
	"time"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
	"github.com/google/uuid"
)

func TestTaskEventAllowlistRejectsSyntheticProgress(t *testing.T) {
	if taskEventAllowed("task.progress") || taskEventAllowed("task.attempt.started") || taskEventAllowed("unknown") {
		t.Fatal("synthetic or unknown event type was allowed")
	}
	for _, eventType := range []string{"task.accepted", "task.running", "task.completed", "task.attempt.failed", "task.policy.priority_demoted"} {
		if !taskEventAllowed(eventType) {
			t.Fatalf("required event type %q was rejected", eventType)
		}
	}
}

func TestMinimalTaskResultDropsProviderRawCopy(t *testing.T) {
	result := minimalTaskResult(map[string]any{
		"data":              []any{map[string]any{"url": "https://example.invalid/result"}},
		"raw":               map[string]any{"provider": "duplicate"},
		"raw_data":          map[string]any{"provider": "duplicate"},
		"providerResponse":  map[string]any{"provider": "duplicate"},
		"upstream_task_id":  "remote-task",
		"provider_specific": "kept",
	})
	if result["raw"] != nil || result["raw_data"] != nil || result["providerResponse"] != nil ||
		result["upstream_task_id"] != nil || result["data"] == nil || result["provider_specific"] != "kept" {
		t.Fatalf("minimal task result=%#v", result)
	}
}

func TestMinimalTaskAttemptMetricsKeepsBoundedRoutingSnapshot(t *testing.T) {
	metrics := minimalTaskAttemptMetrics(map[string]any{
		"loadMetrics":     map[string]any{"concurrent": map[string]any{"softRatio": 0.5}},
		"fullReasons":     []any{"waiting"},
		"selectionReason": "full_avoided",
		"requestBody":     map[string]any{"secret": "must not persist"},
	})
	if metrics["loadMetrics"] == nil || metrics["fullReasons"] == nil || metrics["selectionReason"] != "full_avoided" {
		t.Fatalf("routing snapshot was dropped: %+v", metrics)
	}
	if metrics["requestBody"] != nil {
		t.Fatalf("unbounded attempt payload was retained: %+v", metrics)
	}
}

func TestTaskEventsAreMinimalAndConsecutiveDuplicatesAreSkipped(t *testing.T) {
	db := billingV2IntegrationStore(t)
	ctx := context.Background()
	user := &auth.User{ID: "event-minimal-" + uuid.NewString()}
	task, err := db.CreateTask(ctx, CreateTaskInput{
		Kind: "images.generations", Model: "event-minimal", RunMode: "simulation",
		Request: map[string]any{"prompt": "minimal"},
	}, user)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		_, _ = db.pool.Exec(context.Background(), `DELETE FROM gateway_tasks WHERE id=$1::uuid`, task.ID)
	})

	running, err := db.AddTaskEvent(ctx, task.ID, "task.running", "running", "starting", 0.1, "ignored", map[string]any{"large": "ignored"}, true)
	if err != nil || running.ID == "" || len(running.Payload) != 0 || running.Phase != "" || running.Progress != 0 || running.Message != "" {
		t.Fatalf("running=%+v err=%v", running, err)
	}
	duplicate, err := db.AddTaskEvent(ctx, task.ID, "task.running", "running", "polling", 0.8, "ignored again", map[string]any{"other": "ignored"}, true)
	if err != nil || duplicate.ID != "" || duplicate.SkippedReason != "duplicate" {
		t.Fatalf("duplicate=%+v err=%v", duplicate, err)
	}
	synthetic, err := db.AddTaskEvent(ctx, task.ID, "task.progress", "running", "polling", 0.9, "ignored", nil, true)
	if err != nil || synthetic.ID != "" || synthetic.SkippedReason != "unknown_type" {
		t.Fatalf("synthetic=%+v err=%v", synthetic, err)
	}
	completed, err := db.AddTaskEventWithCallback(
		ctx,
		task.ID,
		"task.completed",
		"succeeded",
		"completed",
		1,
		"ignored",
		map[string]any{"result": map[string]any{"duplicate": true}},
		true,
		"https://callback.invalid/task",
	)
	if err != nil || completed.ID == "" || len(completed.Payload) != 0 {
		t.Fatalf("completed=%+v err=%v", completed, err)
	}

	var events int
	var nonEmptyPayloads int
	if err := db.pool.QueryRow(ctx, `
SELECT count(*), count(*) FILTER (WHERE payload <> '{}'::jsonb)
FROM gateway_task_events
WHERE task_id=$1::uuid`, task.ID).Scan(&events, &nonEmptyPayloads); err != nil {
		t.Fatal(err)
	}
	if events != 3 || nonEmptyPayloads != 0 {
		t.Fatalf("events=%d nonEmptyPayloads=%d, want 3/0", events, nonEmptyPayloads)
	}
	var callbackPayloadBytes int
	if err := db.pool.QueryRow(ctx, `
SELECT pg_column_size(payload)
FROM gateway_task_callback_outbox
WHERE task_id=$1::uuid`, task.ID).Scan(&callbackPayloadBytes); err != nil {
		t.Fatal(err)
	}
	if callbackPayloadBytes > 16 {
		t.Fatalf("callback payload storage=%d, want an empty json object", callbackPayloadBytes)
	}
	budgetExceeded := false
	for index := 0; index < 20; index++ {
		eventType := "task.running"
		status := "running"
		if index%2 == 1 {
			eventType = "task.queued"
			status = "queued"
		}
		event, err := db.AddTaskEvent(ctx, task.ID, eventType, status, "", 0, "", nil, true)
		if err != nil {
			t.Fatal(err)
		}
		if event.SkippedReason == "budget_exceeded" {
			budgetExceeded = true
			break
		}
	}
	if !budgetExceeded {
		t.Fatal("non-terminal event budget was not enforced")
	}
	var nonTerminalEvents int
	if err := db.pool.QueryRow(ctx, `
SELECT count(*)
FROM gateway_task_events
WHERE task_id=$1::uuid
  AND event_type NOT IN (
    'task.completed', 'task.failed', 'task.cancelled',
    'task.billing.settled', 'task.billing.released', 'task.billing.review'
  )`, task.ID).Scan(&nonTerminalEvents); err != nil {
		t.Fatal(err)
	}
	if nonTerminalEvents != 16 {
		t.Fatalf("non-terminal events=%d, want 16", nonTerminalEvents)
	}
}

func TestTaskAttemptKeepsOnlyCompactDiagnostics(t *testing.T) {
	db := billingV2IntegrationStore(t)
	ctx := context.Background()
	user := &auth.User{ID: "attempt-minimal-" + uuid.NewString()}
	task, err := db.CreateTask(ctx, CreateTaskInput{
		Kind: "images.generations", Model: "attempt-minimal", RunMode: "simulation",
		Request: map[string]any{"prompt": "canonical"},
	}, user)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		_, _ = db.pool.Exec(context.Background(), `DELETE FROM gateway_tasks WHERE id=$1::uuid`, task.ID)
	})
	attemptID, err := db.CreateTaskAttempt(ctx, CreateTaskAttemptInput{
		TaskID: task.ID, AttemptNo: 1, QueueKey: "test", Status: "running",
		RequestSnapshot:    map[string]any{"prompt": "duplicate"},
		Metrics:            map[string]any{"trace": []any{map[string]any{"large": "duplicate"}}},
		PricingSnapshot:    map[string]any{"price": 1},
		RequestFingerprint: "duplicate-fingerprint",
	})
	if err != nil {
		t.Fatal(err)
	}
	if err := db.FinishTaskAttempt(ctx, FinishTaskAttemptInput{
		AttemptID:        attemptID,
		Status:           "failed",
		Retryable:        true,
		StatusCode:       503,
		Usage:            map[string]any{"output_tokens": 100, "raw": strings.Repeat("duplicate", 100)},
		Metrics:          map[string]any{"trace": []any{map[string]any{"large": "duplicate"}}, "cacheAffinityMatched": true, "cacheAffinityMatchedPrefixDepth": 3},
		ResponseSnapshot: map[string]any{"result": "duplicate"},
		ErrorCode:        "upstream_error",
		ErrorMessage:     strings.Repeat("错误", 4096),
	}); err != nil {
		t.Fatal(err)
	}
	attempts, err := db.ListTaskAttempts(ctx, task.ID)
	if err != nil {
		t.Fatal(err)
	}
	if len(attempts) != 1 {
		t.Fatalf("attempts=%d", len(attempts))
	}
	attempt := attempts[0]
	if len(attempt.RequestSnapshot) != 0 || len(attempt.ResponseSnapshot) != 0 || len(attempt.PricingSnapshot) != 0 {
		t.Fatalf("attempt contains duplicate snapshots: %+v", attempt)
	}
	if len(attempt.Usage) != 1 || taskAttemptMetricInt(attempt.Usage, "output_tokens") != 100 {
		t.Fatalf("attempt compact usage mismatch: %+v", attempt.Usage)
	}
	if len(attempt.Metrics) != 2 ||
		attempt.Metrics["cacheAffinityMatched"] != true ||
		taskAttemptMetricInt(attempt.Metrics, "cacheAffinityMatchedPrefixDepth") != 3 {
		t.Fatalf("attempt compact metrics mismatch: %+v", attempt.Metrics)
	}
	if attempt.StatusCode != 503 || len(attempt.ErrorMessage) > 2048 {
		t.Fatalf("attempt status/error not preserved safely: %+v", attempt)
	}
}

func TestTaskRunningDoesNotPersistNormalizedRequestCopy(t *testing.T) {
	db := billingV2IntegrationStore(t)
	ctx := context.Background()
	user := &auth.User{ID: "request-minimal-" + uuid.NewString()}
	task, err := db.CreateTask(ctx, CreateTaskInput{
		Kind: "images.generations", Model: "request-minimal", RunMode: "simulation",
		Request: map[string]any{"prompt": "canonical"},
	}, user)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		_, _ = db.pool.Exec(context.Background(), `DELETE FROM gateway_tasks WHERE id=$1::uuid`, task.ID)
	})
	executionToken := uuid.NewString()
	if _, err := db.ClaimTaskExecution(ctx, task.ID, executionToken, time.Minute); err != nil {
		t.Fatal(err)
	}
	if err := db.MarkTaskRunning(ctx, task.ID, executionToken, "image_generate", map[string]any{
		"prompt": "duplicate normalized request",
	}); err != nil {
		t.Fatal(err)
	}
	var normalizedBytes int
	if err := db.pool.QueryRow(ctx, `
SELECT pg_column_size(normalized_request)
FROM gateway_tasks
WHERE id=$1::uuid`, task.ID).Scan(&normalizedBytes); err != nil {
		t.Fatal(err)
	}
	if normalizedBytes > 16 {
		t.Fatalf("normalized request storage=%d, want an empty json object", normalizedBytes)
	}
}

func TestTaskHistoryCleanupRemovesHistoricalProviderCopies(t *testing.T) {
	db := billingV2IntegrationStore(t)
	ctx := context.Background()
	user := &auth.User{ID: "history-compaction-" + uuid.NewString()}
	task, err := db.CreateTask(ctx, CreateTaskInput{
		Kind: "images.generations", Model: "history-compaction", RunMode: "simulation",
		Request: map[string]any{"prompt": "canonical"},
	}, user)
	if err != nil {
		t.Fatal(err)
	}
	voiceID := uuid.NewString()
	t.Cleanup(func() {
		_, _ = db.pool.Exec(context.Background(), `DELETE FROM gateway_cloned_voices WHERE id=$1::uuid`, voiceID)
		_, _ = db.pool.Exec(context.Background(), `DELETE FROM gateway_tasks WHERE id=$1::uuid`, task.ID)
	})
	if _, err := db.pool.Exec(ctx, `
UPDATE gateway_tasks
SET result='{"data":[{"url":"https://example.invalid/result"}],"raw_data":{"provider":"duplicate"},"upstream_task_id":"remote"}'::jsonb
WHERE id=$1::uuid`,
		task.ID); err != nil {
		t.Fatal(err)
	}
	if _, err := db.pool.Exec(ctx, `
INSERT INTO gateway_cloned_voices (id, user_id, provider, voice_id, metadata)
VALUES ($1::uuid, 'history-compaction', 'test', $2, '{"request":{"duplicate":true},"rawData":{"provider":"duplicate"},"keep":"value"}'::jsonb)`,
		voiceID, "voice-"+voiceID); err != nil {
		t.Fatal(err)
	}
	result, err := db.CleanupTaskHistory(ctx, time.Now().AddDate(0, 0, -7), time.Now().AddDate(0, 0, -30), 100)
	if err != nil {
		t.Fatal(err)
	}
	if result.CompactedTasks < 1 || result.CompactedClonedVoices < 1 {
		t.Fatalf("cleanup result=%+v", result)
	}
	var taskResult map[string]any
	var voiceMetadata map[string]any
	if err := db.pool.QueryRow(ctx, `SELECT result FROM gateway_tasks WHERE id=$1::uuid`, task.ID).Scan(&taskResult); err != nil {
		t.Fatal(err)
	}
	if err := db.pool.QueryRow(ctx, `SELECT metadata FROM gateway_cloned_voices WHERE id=$1::uuid`, voiceID).Scan(&voiceMetadata); err != nil {
		t.Fatal(err)
	}
	if taskResult["raw_data"] != nil || taskResult["upstream_task_id"] != nil || taskResult["data"] == nil {
		t.Fatalf("compacted task result=%+v", taskResult)
	}
	if voiceMetadata["request"] != nil || voiceMetadata["rawData"] != nil || voiceMetadata["keep"] != "value" {
		t.Fatalf("compacted voice metadata=%+v", voiceMetadata)
	}
}

func TestTaskHistoryCleanupKeepsTaskUntilCallbackRetentionExpires(t *testing.T) {
	db := billingV2IntegrationStore(t)
	ctx := context.Background()
	user := &auth.User{ID: "cleanup-" + uuid.NewString()}
	createOldTask := func(label string) GatewayTask {
		t.Helper()
		task, err := db.CreateTask(ctx, CreateTaskInput{
			Kind: "images.generations", Model: label, RunMode: "simulation",
			Request: map[string]any{"prompt": label},
		}, user)
		if err != nil {
			t.Fatal(err)
		}
		if _, err := db.pool.Exec(ctx, `
UPDATE gateway_tasks
SET status='succeeded', billing_status='not_required',
    finished_at=now()-interval '40 days', updated_at=now()-interval '40 days'
WHERE id=$1::uuid`, task.ID); err != nil {
			t.Fatal(err)
		}
		if _, err := db.pool.Exec(ctx, `
UPDATE gateway_task_events
SET created_at=now()-interval '10 days'
WHERE task_id=$1::uuid`, task.ID); err != nil {
			t.Fatal(err)
		}
		return task
	}
	safe := createOldTask("cleanup-safe")
	protected := createOldTask("cleanup-protected")
	t.Cleanup(func() {
		_, _ = db.pool.Exec(context.Background(), `DELETE FROM gateway_tasks WHERE id IN ($1::uuid, $2::uuid)`, safe.ID, protected.ID)
	})
	completed, err := db.AddTaskEvent(ctx, protected.ID, "task.completed", "succeeded", "", 0, "", nil, true)
	if err != nil {
		t.Fatal(err)
	}
	if err := db.QueueTaskCallback(ctx, completed, "https://callback.invalid/task"); err != nil {
		t.Fatal(err)
	}
	if _, err := db.pool.Exec(ctx, `
UPDATE gateway_task_callback_outbox
SET status='delivered', delivered_at=now(), updated_at=now()
WHERE task_id=$1::uuid`, protected.ID); err != nil {
		t.Fatal(err)
	}

	result, err := db.CleanupTaskHistory(ctx, time.Now().AddDate(0, 0, -7), time.Now().AddDate(0, 0, -30), 100)
	if err != nil {
		t.Fatal(err)
	}
	if result.DeletedTasks < 1 {
		t.Fatalf("deletedTasks=%d, want at least 1", result.DeletedTasks)
	}
	var safeExists bool
	var protectedExists bool
	if err := db.pool.QueryRow(ctx, `SELECT EXISTS (SELECT 1 FROM gateway_tasks WHERE id=$1::uuid)`, safe.ID).Scan(&safeExists); err != nil {
		t.Fatal(err)
	}
	if err := db.pool.QueryRow(ctx, `SELECT EXISTS (SELECT 1 FROM gateway_tasks WHERE id=$1::uuid)`, protected.ID).Scan(&protectedExists); err != nil {
		t.Fatal(err)
	}
	if safeExists || !protectedExists {
		t.Fatalf("safeExists=%t protectedExists=%t", safeExists, protectedExists)
	}
}

func TestLegacyCallbacksAreNotReplayed(t *testing.T) {
	db := billingV2IntegrationStore(t)
	ctx := context.Background()
	user := &auth.User{ID: "legacy-callback-" + uuid.NewString()}
	task, err := db.CreateTask(ctx, CreateTaskInput{
		Kind: "images.generations", Model: "legacy-callback", RunMode: "simulation",
		Request: map[string]any{"prompt": "legacy"},
	}, user)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() {
		_, _ = db.pool.Exec(context.Background(), `DELETE FROM gateway_tasks WHERE id=$1::uuid`, task.ID)
	})
	event, err := db.AddTaskEvent(ctx, task.ID, "task.running", "running", "", 0, "", nil, true)
	if err != nil {
		t.Fatal(err)
	}
	if err := db.QueueTaskCallback(ctx, event, "https://callback.invalid/legacy"); err != nil {
		t.Fatal(err)
	}
	if _, err := db.pool.Exec(ctx, `
UPDATE gateway_task_callback_outbox
SET created_at=(
  SELECT applied_at - interval '1 second'
  FROM schema_migrations
  WHERE version='0083_task_history_minimal_storage'
)
WHERE task_id=$1::uuid`, task.ID); err != nil {
		t.Fatal(err)
	}
	claimed, err := db.ClaimTaskCallbacks(ctx, "legacy-test", 10, time.Minute)
	if err != nil {
		t.Fatal(err)
	}
	if len(claimed) != 0 {
		t.Fatalf("claimed legacy callbacks=%d, want 0", len(claimed))
	}
	result, err := db.CleanupTaskHistory(ctx, time.Now().AddDate(0, 0, -7), time.Now().AddDate(0, 0, -30), 100)
	if err != nil {
		t.Fatal(err)
	}
	if result.RetiredCallbacks < 1 {
		t.Fatalf("retired legacy callbacks=%d, want at least 1", result.RetiredCallbacks)
	}
	var status string
	if err := db.pool.QueryRow(ctx, `
SELECT status
FROM gateway_task_callback_outbox
WHERE task_id=$1::uuid`, task.ID).Scan(&status); err != nil {
		t.Fatal(err)
	}
	if status != "failed" {
		t.Fatalf("legacy callback status=%q, want failed", status)
	}
}
