package main

import (
	"context"
	"errors"
	"os"
	"strings"
	"testing"
	"time"

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

const identityPairingStartReservationMigration = "../../migrations/0068_identity_pairing_start_reservation.sql"

func TestIdentityPairingStartReservationMigrationDefinesCurrentLifecycle(t *testing.T) {
	payload, err := os.ReadFile(identityPairingStartReservationMigration)
	if err != nil {
		t.Fatal(err)
	}
	content := strings.ToLower(string(payload))
	for _, required := range []string{
		"state text not null default 'starting'",
		"revision_id uuid unique references gateway_identity_configuration_revisions",
		"updated_at timestamptz not null default now()",
		"gateway_identity_pairing_start_state_check",
		"idx_gateway_identity_pairing_start_expiry",
		"on conflict do nothing",
	} {
		if !strings.Contains(content, required) {
			t.Fatalf("identity pairing start reservation migration is missing %q", required)
		}
	}
	for _, forbidden := range []string{"exchange_token text", "client_secret", "machine_secret", "secret_value", "drop ", "do $"} {
		if strings.Contains(content, forbidden) {
			t.Fatalf("identity pairing start reservation migration contains forbidden content %q", forbidden)
		}
	}
}

func TestIdentityPairingStartReservationMigrationSeedsOutstandingPairing(t *testing.T) {
	pool := newIdentityMigrationPostgresTestSchema(t)
	ctx := context.Background()
	for _, migration := range []string{
		"../../migrations/0065_identity_configuration_revisions.sql",
		"../../migrations/0066_identity_onboarding_exchanges.sql",
	} {
		applyIdentityMigrationTestFile(t, ctx, pool, migration)
	}

	revisionID := uuid.NewString()
	pairingID := uuid.NewString()
	expiresAt := time.Now().UTC().Add(30 * time.Minute).Truncate(time.Microsecond)
	if _, err := pool.Exec(ctx, `
INSERT INTO gateway_identity_configuration_revisions (
  id,state,auth_center_url,local_tenant_key,public_base_url,web_base_url
) VALUES ($1::uuid,'draft','https://auth.test.example','default',
  'https://gateway.test.example','https://gateway-web.test.example')`, revisionID); err != nil {
		t.Fatalf("seed identity revision for pairing reservation migration: %v", err)
	}
	if _, err := pool.Exec(ctx, `
INSERT INTO gateway_identity_onboarding_exchanges (
  id,revision_id,remote_exchange_id,exchange_token_ref,status,remote_version,expires_at
) VALUES ($1::uuid,$2::uuid,$3::uuid,$4,'credentials_saved',4,$5)`,
		pairingID, revisionID, uuid.NewString(), "identity-exchange-"+pairingID, expiresAt); err != nil {
		t.Fatalf("seed identity exchange for pairing reservation migration: %v", err)
	}

	applyIdentityMigrationTestFile(t, ctx, pool, identityPairingStartReservationMigration)

	var attemptID, state, reservedRevisionID string
	var gotExpiresAt, updatedAt time.Time
	if err := pool.QueryRow(ctx, `
SELECT attempt_id::text,state,revision_id::text,expires_at,updated_at
FROM gateway_identity_pairing_start_reservation`).Scan(
		&attemptID, &state, &reservedRevisionID, &gotExpiresAt, &updatedAt,
	); err != nil {
		t.Fatalf("read pairing start reservation: %v", err)
	}
	if attemptID != pairingID || state != "paired" || reservedRevisionID != revisionID ||
		!gotExpiresAt.Equal(expiresAt) || updatedAt.IsZero() {
		t.Fatalf("unexpected pairing reservation attempt=%q state=%q revision=%q expires=%v updated=%v",
			attemptID, state, reservedRevisionID, gotExpiresAt, updatedAt)
	}

	_, err := pool.Exec(ctx, `UPDATE gateway_identity_pairing_start_reservation SET state='starting'`)
	requirePairingReservationMigrationCode(t, err, "23514", "starting reservation with revision")
	_, err = pool.Exec(ctx, `UPDATE gateway_identity_pairing_start_reservation SET revision_id=$1::uuid`, uuid.NewString())
	requirePairingReservationMigrationCode(t, err, "23503", "reservation with unknown revision")
}

func requirePairingReservationMigrationCode(t *testing.T, err error, wantCode, operation string) {
	t.Helper()
	if err == nil {
		t.Fatalf("%s unexpectedly satisfied migration constraints", operation)
	}
	var postgresError *pgconn.PgError
	if !errors.As(err, &postgresError) || postgresError.Code != wantCode {
		t.Fatalf("%s error=%v, want PostgreSQL code %s", operation, err, wantCode)
	}
}
