package store

import (
	"context"
	"errors"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgconn"
	"github.com/jackc/pgx/v5/pgxpool"
)

const identitySecretCleanupMigration = "0067_identity_secret_cleanup_queue.sql"

func TestIdentitySecretCleanupMigrationDefinesClaimLifecycle(t *testing.T) {
	payload, err := os.ReadFile(identitySecretCleanupMigrationPath(t, identitySecretCleanupMigration))
	if err != nil {
		t.Fatal(err)
	}
	content := strings.ToLower(string(payload))
	for _, required := range []string{
		"create table if not exists gateway_identity_secret_cleanup_queue",
		"secret_ref text primary key",
		"status text not null default 'pending'",
		"claim_token uuid",
		"lease_expires_at timestamptz",
		"gateway_identity_secret_cleanup_claim_check",
		"idx_gateway_identity_secret_cleanup_due",
	} {
		if !strings.Contains(content, required) {
			t.Fatalf("identity Secret cleanup migration is missing %q", required)
		}
	}
	for _, forbidden := range []string{"secret_value", "client_secret", "machine_secret", "token text", "drop ", "set not null"} {
		if strings.Contains(content, forbidden) {
			t.Fatalf("identity Secret cleanup migration contains forbidden content %q", forbidden)
		}
	}
}

func TestIdentitySecretCleanupMigrationSupportsClaimLifecycle(t *testing.T) {
	db := newIdentitySecretCleanupMigrationPostgresStore(t)
	ctx := context.Background()
	applyIdentitySecretCleanupMigration(t, ctx, db.pool, identitySecretCleanupMigration)
	reference := "identity-cleanup-" + uuid.NewString()

	if err := db.QueueIdentitySecretCleanup(ctx, reference, time.Now().Add(-time.Minute)); err != nil {
		t.Fatalf("queue identity Secret cleanup: %v", err)
	}
	claims, err := db.ClaimIdentitySecretCleanups(ctx, 1, time.Minute)
	if err != nil {
		t.Fatalf("claim identity Secret cleanup: %v", err)
	}
	if len(claims) != 1 || claims[0].Reference != reference || claims[0].ClaimToken == "" {
		t.Fatalf("claim identity Secret cleanup returned unexpected metadata: %#v", claims)
	}
	completed, err := db.CompleteIdentitySecretCleanup(ctx, reference, claims[0].ClaimToken)
	if err != nil || !completed {
		t.Fatalf("complete identity Secret cleanup: completed=%t err=%v", completed, err)
	}

	invalidReference := "identity-cleanup-invalid-" + uuid.NewString()
	_, err = db.pool.Exec(ctx, `
INSERT INTO gateway_identity_secret_cleanup_queue(
  secret_ref,not_before,status,claim_token,lease_expires_at
) VALUES($1,now(),'claimed',NULL,NULL)`, invalidReference)
	requireIdentitySecretCleanupMigrationCheckViolation(t, err)

	var indexDefinition string
	if err := db.pool.QueryRow(ctx, `
SELECT pg_get_indexdef(indexrelid)
FROM pg_index
WHERE indexrelid='idx_gateway_identity_secret_cleanup_due'::regclass`).Scan(&indexDefinition); err != nil {
		t.Fatalf("read cleanup due index: %v", err)
	}
	if !strings.Contains(strings.ToLower(indexDefinition), "(status, not_before, lease_expires_at, updated_at)") {
		t.Fatalf("cleanup due index does not cover the claim lifecycle: %q", indexDefinition)
	}
}

func newIdentitySecretCleanupMigrationPostgresStore(t *testing.T) *Store {
	t.Helper()
	databaseURL := strings.TrimSpace(os.Getenv("AI_GATEWAY_TEST_DATABASE_URL"))
	if databaseURL == "" {
		t.Skip("set AI_GATEWAY_TEST_DATABASE_URL to run identity Secret cleanup migration integration tests")
	}
	ctx := context.Background()
	admin, err := pgxpool.New(ctx, databaseURL)
	if err != nil {
		t.Fatalf("connect identity migration test database: %v", err)
	}
	var databaseName string
	if err := admin.QueryRow(ctx, `SELECT current_database()`).Scan(&databaseName); err != nil {
		admin.Close()
		t.Fatalf("read identity migration test database name: %v", err)
	}
	if !strings.Contains(strings.ToLower(databaseName), "test") {
		admin.Close()
		t.Fatalf("refusing to use non-test database %q", databaseName)
	}

	schemaName := "gateway_identity_cleanup_migration_" + strings.ReplaceAll(uuid.NewString(), "-", "")
	schemaIdentifier := pgx.Identifier{schemaName}.Sanitize()
	if _, err := admin.Exec(ctx, `CREATE SCHEMA `+schemaIdentifier); err != nil {
		admin.Close()
		t.Fatalf("create identity migration test schema: %v", err)
	}
	config, err := pgxpool.ParseConfig(databaseURL)
	if err != nil {
		_, _ = admin.Exec(ctx, `DROP SCHEMA IF EXISTS `+schemaIdentifier+` CASCADE`)
		admin.Close()
		t.Fatalf("parse identity migration test database URL: %v", err)
	}
	config.ConnConfig.RuntimeParams["search_path"] = schemaName
	pool, err := pgxpool.NewWithConfig(ctx, config)
	if err != nil {
		_, _ = admin.Exec(ctx, `DROP SCHEMA IF EXISTS `+schemaIdentifier+` CASCADE`)
		admin.Close()
		t.Fatalf("connect identity migration test schema: %v", err)
	}
	t.Cleanup(func() {
		pool.Close()
		if _, err := admin.Exec(context.Background(), `DROP SCHEMA IF EXISTS `+schemaIdentifier+` CASCADE`); err != nil {
			t.Errorf("drop identity migration test schema: %v", err)
		}
		admin.Close()
	})
	return &Store{pool: pool}
}

func applyIdentitySecretCleanupMigration(t *testing.T, ctx context.Context, pool *pgxpool.Pool, name string) {
	t.Helper()
	payload, err := os.ReadFile(identitySecretCleanupMigrationPath(t, name))
	if err != nil {
		t.Fatalf("read identity Secret cleanup migration: %v", err)
	}
	tx, err := pool.Begin(ctx)
	if err != nil {
		t.Fatalf("begin identity Secret cleanup migration: %v", err)
	}
	if _, err := tx.Exec(ctx, string(payload)); err != nil {
		_ = tx.Rollback(ctx)
		t.Fatalf("execute identity Secret cleanup migration: %v", err)
	}
	if err := tx.Commit(ctx); err != nil {
		t.Fatalf("commit identity Secret cleanup migration: %v", err)
	}
}

func identitySecretCleanupMigrationPath(t *testing.T, name string) string {
	t.Helper()
	_, filename, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("locate identity Secret cleanup migration test")
	}
	return filepath.Join(filepath.Dir(filename), "..", "..", "migrations", name)
}

func requireIdentitySecretCleanupMigrationCheckViolation(t *testing.T, err error) {
	t.Helper()
	if err == nil {
		t.Fatal("invalid cleanup claim lifecycle unexpectedly satisfied migration constraints")
	}
	var postgresError *pgconn.PgError
	if !errors.As(err, &postgresError) || postgresError.Code != "23514" {
		t.Fatalf("invalid cleanup claim lifecycle error=%v, want PostgreSQL check violation", err)
	}
}
