import { describe, expect, it } from 'vitest';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const currentDir = path.dirname(fileURLToPath(import.meta.url));
const migrationFile = '039_create-payments-schema.sql';
const migrationPath = path.resolve(
  currentDir,
  `../../src/infra/database/migrations/${migrationFile}`
);
const sql = fs.readFileSync(migrationPath, 'utf8');

describe('payments-schema migration', () => {
  it('migration file exists', () => {
    expect(fs.existsSync(migrationPath)).toBe(true);
  });

  it('labels the migration with a migration version comment', () => {
    expect(sql).toMatch(/^-- Migration \d+:/);
  });

  it('creates the payments catalog and runtime tables', () => {
    expect(sql).toMatch(/CREATE TABLE IF NOT EXISTS payments\.stripe_connections/i);
    expect(sql).toMatch(/CREATE TABLE IF NOT EXISTS payments\.products/i);
    expect(sql).toMatch(/CREATE TABLE IF NOT EXISTS payments\.prices/i);
    expect(sql).toMatch(/CREATE TABLE IF NOT EXISTS payments\.stripe_customer_mappings/i);
    expect(sql).toMatch(/CREATE TABLE IF NOT EXISTS payments\.checkout_sessions/i);
    expect(sql).toMatch(/CREATE TABLE IF NOT EXISTS payments\.customer_portal_sessions/i);
    expect(sql).toMatch(/CREATE TABLE IF NOT EXISTS payments\.payment_history/i);
    expect(sql).toMatch(/CREATE TABLE IF NOT EXISTS payments\.subscriptions/i);
    expect(sql).toMatch(/CREATE TABLE IF NOT EXISTS payments\.subscription_items/i);
    expect(sql).toMatch(/CREATE TABLE IF NOT EXISTS payments\.webhook_events/i);
    expect(sql).not.toMatch(/CREATE TABLE IF NOT EXISTS payments\.sync_runs/i);
    expect(sql).not.toMatch(/CREATE TABLE IF NOT EXISTS payments\.customers/i);
    expect(sql).not.toMatch(/CREATE TABLE IF NOT EXISTS payments\.webhook_endpoints/i);
  });

  it('uses environment checks for test and live rows', () => {
    expect(
      sql.match(/CHECK \(environment IN \('test', 'live'\)\)/gi)?.length
    ).toBeGreaterThanOrEqual(8);
  });

  it('uses idempotent guards for schema objects and trigger recreation', () => {
    expect(sql).not.toMatch(/CREATE SCHEMA payments/i);
    expect(sql).not.toMatch(/CREATE TABLE payments\./i);
    expect(sql).not.toMatch(/CREATE INDEX idx_payments/i);
    expect(sql).not.toMatch(/CREATE UNIQUE INDEX idx_payments/i);

    const dropTriggers = sql.match(/DROP TRIGGER IF EXISTS trg_payments_/gi) ?? [];
    const createTriggers = sql.match(/CREATE TRIGGER trg_payments_/gi) ?? [];
    expect(dropTriggers.length).toBe(createTriggers.length);
  });

  it('does not store key source on mirrored Stripe connection rows', () => {
    expect(sql).not.toMatch(/key_source/i);
  });

  it('stores raw Stripe payloads as jsonb', () => {
    expect(sql).toMatch(/raw JSONB NOT NULL DEFAULT '\{\}'::JSONB/i);
  });

  it('does not keep soft-delete tombstones in Stripe catalog mirror rows', () => {
    expect(sql).not.toMatch(/is_deleted/i);
  });

  it('creates unique indexes for environment and Stripe object id pairs', () => {
    expect(sql).toMatch(/UNIQUE \(environment, stripe_product_id\)/i);
    expect(sql).toMatch(/UNIQUE \(environment, stripe_price_id\)/i);
    expect(sql).toMatch(/UNIQUE \(environment, subject_type, subject_id\)/i);
    expect(sql).toMatch(/UNIQUE \(environment, stripe_customer_id\)/i);
    expect(sql).toMatch(/UNIQUE \(environment, stripe_checkout_session_id\)/i);
    expect(sql).toMatch(/UNIQUE \(environment, stripe_subscription_id\)/i);
    expect(sql).toMatch(/UNIQUE \(environment, stripe_subscription_item_id\)/i);
    expect(sql).toMatch(/UNIQUE \(environment, stripe_event_id\)/i);
  });

  it('stores latest sync state on stripe_connections', () => {
    expect(sql).toMatch(/last_sync_status TEXT/i);
    expect(sql).toMatch(/last_sync_error TEXT/i);
    expect(sql).toMatch(/last_sync_counts JSONB NOT NULL DEFAULT '\{\}'::JSONB/i);
  });

  it('stores managed Stripe webhook endpoint metadata on stripe_connections', () => {
    expect(sql).toMatch(/webhook_endpoint_id TEXT/i);
    expect(sql).toMatch(/webhook_endpoint_url TEXT/i);
    expect(sql).toMatch(/webhook_configured_at TIMESTAMPTZ/i);
  });

  it('keeps consolidated stripe_connections webhook columns idempotent', () => {
    expect(sql).toMatch(
      /ALTER TABLE payments\.stripe_connections\s+ADD COLUMN IF NOT EXISTS webhook_endpoint_id TEXT/i
    );
    expect(sql).toMatch(
      /ALTER TABLE payments\.stripe_connections\s+ADD COLUMN IF NOT EXISTS webhook_endpoint_url TEXT/i
    );
    expect(sql).toMatch(
      /ALTER TABLE payments\.stripe_connections\s+ADD COLUMN IF NOT EXISTS webhook_configured_at TIMESTAMPTZ/i
    );
  });

  it('keeps customer mappings generic without foreign keys to app tables', () => {
    expect(sql).toMatch(/subject_type TEXT NOT NULL/i);
    expect(sql).toMatch(/subject_id TEXT NOT NULL/i);
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.stripe_customer_mappings[\s\S]*stripe_customer_id TEXT NOT NULL[\s\S]*created_at TIMESTAMPTZ NOT NULL DEFAULT NOW\(\)/i
    );
    expect(sql).not.toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.stripe_customer_mappings\s*\([^;]*customer_email_snapshot/i
    );
    expect(sql).not.toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.stripe_customer_mappings\s*\([^;]*metadata JSONB/i
    );
    expect(sql).not.toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.stripe_customer_mappings\s*\([^;]*raw JSONB/i
    );
    expect(sql).not.toMatch(/REFERENCES public\./i);
    expect(sql).not.toMatch(/REFERENCES auth\./i);
  });

  it('allows anonymous one-time payment history rows', () => {
    expect(sql).toMatch(/subject_type TEXT,/i);
    expect(sql).toMatch(/subject_id TEXT,/i);
    expect(sql).toMatch(/customer_email_snapshot TEXT/i);
  });

  it('creates checkout sessions for runtime checkout state without default RLS friction', () => {
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.checkout_sessions[\s\S]*status TEXT NOT NULL DEFAULT 'initialized'\s+CHECK \(status IN \('initialized', 'open', 'completed', 'expired', 'failed'\)\)/i
    );
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.checkout_sessions[\s\S]*payment_status TEXT\s+CHECK \(payment_status IS NULL OR payment_status IN \('paid', 'unpaid', 'no_payment_required'\)\)/i
    );
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.checkout_sessions[\s\S]*line_items JSONB NOT NULL DEFAULT '\[\]'::JSONB CHECK \(jsonb_typeof\(line_items\) = 'array'\)/i
    );
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.checkout_sessions[\s\S]*customer_email TEXT/i
    );
    expect(sql).not.toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.checkout_sessions\s*\([^;]*customer_email_snapshot/i
    );
    expect(sql).toMatch(/idempotency_key TEXT/i);
    expect(sql).not.toMatch(/ALTER TABLE payments\.checkout_sessions ENABLE ROW LEVEL SECURITY/i);
    expect(sql).toMatch(
      /GRANT INSERT, SELECT ON payments\.checkout_sessions TO anon, authenticated, project_admin/i
    );
  });

  it('creates customer portal sessions without default RLS friction', () => {
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.customer_portal_sessions[\s\S]*status TEXT NOT NULL DEFAULT 'initialized'\s+CHECK \(status IN \('initialized', 'created', 'failed'\)\)/i
    );
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.customer_portal_sessions[\s\S]*subject_type TEXT NOT NULL/i
    );
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.customer_portal_sessions[\s\S]*subject_id TEXT NOT NULL/i
    );
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.customer_portal_sessions[\s\S]*stripe_customer_id TEXT/i
    );
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.customer_portal_sessions[\s\S]*return_url TEXT/i
    );
    expect(sql).not.toMatch(
      /ALTER TABLE payments\.customer_portal_sessions ENABLE ROW LEVEL SECURITY/i
    );
    expect(sql).toMatch(
      /GRANT INSERT, SELECT ON payments\.customer_portal_sessions TO anon, authenticated, project_admin/i
    );
  });

  it('allows unmapped subscription rows for existing Stripe syncs', () => {
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.subscriptions[\s\S]*subject_type TEXT,/i
    );
    expect(sql).toMatch(
      /CREATE TABLE IF NOT EXISTS payments\.subscriptions[\s\S]*subject_id TEXT,/i
    );
    expect(sql).not.toMatch(
      /ALTER TABLE payments\.subscriptions\s+ALTER COLUMN subject_type DROP NOT NULL/i
    );
    expect(sql).not.toMatch(
      /ALTER TABLE payments\.subscriptions\s+ALTER COLUMN subject_id DROP NOT NULL/i
    );
  });

  it('stores webhook events idempotently with processing state', () => {
    expect(sql).toMatch(
      /processing_status TEXT NOT NULL DEFAULT 'pending' CHECK \(processing_status IN \('pending', 'processed', 'failed', 'ignored'\)\)/i
    );
    expect(sql).toMatch(/attempt_count INTEGER NOT NULL DEFAULT 0/i);
    expect(sql).toMatch(/payload JSONB NOT NULL/i);
  });

  it('adds useful runtime indexes', () => {
    expect(sql).toMatch(/idx_payments_payment_history_environment_subject/i);
    expect(sql).toMatch(/idx_payments_checkout_sessions_environment_status/i);
    expect(sql).toMatch(/idx_payments_checkout_sessions_environment_subject/i);
    expect(sql).toMatch(/idx_payments_checkout_sessions_environment_idempotency/i);
    expect(sql).toMatch(/idx_payments_customer_portal_sessions_environment_status/i);
    expect(sql).toMatch(/idx_payments_customer_portal_sessions_environment_subject/i);
    expect(sql).toMatch(/idx_payments_payment_history_environment_payment_intent/i);
    expect(sql).toMatch(/idx_payments_payment_history_environment_checkout_session/i);
    expect(sql).toMatch(/idx_payments_payment_history_environment_invoice/i);
    expect(sql).toMatch(/idx_payments_payment_history_environment_refund/i);
    expect(sql).toMatch(/idx_payments_subscriptions_environment_subject/i);
    expect(sql).toMatch(/idx_payments_webhook_events_environment_status/i);
  });

  it('keeps refund rows from conflicting with original payment and invoice rows', () => {
    expect(sql).toMatch(
      /CREATE UNIQUE INDEX IF NOT EXISTS idx_payments_payment_history_environment_payment_intent[\s\S]*WHERE stripe_payment_intent_id IS NOT NULL\s+AND type <> 'refund'/i
    );
    expect(sql).toMatch(
      /CREATE UNIQUE INDEX IF NOT EXISTS idx_payments_payment_history_environment_checkout_session[\s\S]*WHERE stripe_checkout_session_id IS NOT NULL\s+AND type <> 'refund'/i
    );
    expect(sql).toMatch(
      /CREATE UNIQUE INDEX IF NOT EXISTS idx_payments_payment_history_environment_invoice[\s\S]*WHERE stripe_invoice_id IS NOT NULL\s+AND type <> 'refund'/i
    );
    expect(sql).toMatch(
      /CREATE UNIQUE INDEX IF NOT EXISTS idx_payments_payment_history_environment_refund[\s\S]*WHERE stripe_refund_id IS NOT NULL/i
    );
  });

  it('adds updated_at triggers for all payments tables', () => {
    expect(sql).toMatch(/CREATE TRIGGER trg_payments_stripe_connections_updated_at/i);
    expect(sql).toMatch(/CREATE TRIGGER trg_payments_products_updated_at/i);
    expect(sql).toMatch(/CREATE TRIGGER trg_payments_prices_updated_at/i);
    expect(sql).toMatch(/CREATE TRIGGER trg_payments_stripe_customer_mappings_updated_at/i);
    expect(sql).toMatch(/CREATE TRIGGER trg_payments_checkout_sessions_updated_at/i);
    expect(sql).toMatch(/CREATE TRIGGER trg_payments_customer_portal_sessions_updated_at/i);
    expect(sql).toMatch(/CREATE TRIGGER trg_payments_payment_history_updated_at/i);
    expect(sql).toMatch(/CREATE TRIGGER trg_payments_subscriptions_updated_at/i);
    expect(sql).toMatch(/CREATE TRIGGER trg_payments_subscription_items_updated_at/i);
    expect(sql).toMatch(/CREATE TRIGGER trg_payments_webhook_events_updated_at/i);
    expect(
      sql.match(/EXECUTE FUNCTION system\.update_updated_at\(\)/gi)?.length
    ).toBeGreaterThanOrEqual(10);
  });
});
