/**
 * F-145 — Reporting payment lines: DB integration test.
 *
 * Verifies the schema of `reporting_payment_lines` (FKs, CHECKs, indices)
 * and the immutability contract (INSERT-only, refunds as new rows) against
 * a real PostgreSQL database created fresh per test run.
 *
 * Run with: TEST_DATABASE_URL=... npx vitest run src/app/tests/reporting-payment-lines.itest.ts
 */

import type pg from 'pg';
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { createPool } from '../../infrastructure/db/pool.js';
import {
  getTestDbUrl,
  recreateDatabase,
  runMigrations,
} from '../../infrastructure/db/tests/db-test-support.js';

const hasDb = Boolean(process.env.TEST_DATABASE_URL);

// ── Constants from existing modules ──────────────────────────────────────────

const DEFAULT_STORE_ID = '00000000-0000-0000-0000-000000000001'; // from 043

describe.skipIf(!hasDb)('F-145 reporting payment lines (real PostgreSQL)', () => {
  const url = hasDb ? getTestDbUrl() : '';
  let pool: pg.Pool;
  let dbVersion: string;

  beforeAll(async () => {
    await recreateDatabase(url);
    await runMigrations(url, 'up');
    pool = createPool(url);
    const verRows = await pool.query<{ version: string }>(
      'SHOW server_version',
    );
    dbVersion = verRows.rows[0]?.version ?? 'unknown';
  });

  afterAll(async () => {
    await pool.end();
  });

  beforeEach(async () => {
    // Ensure table exists (migrations already applied).
    await pool.query(`
      CREATE TABLE IF NOT EXISTS reporting_payment_lines (
        id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
        order_id uuid NOT NULL,
        store_id uuid NOT NULL,
        terminal_id uuid,
        cash_session_id uuid,
        payment_method_id uuid,
        provider text NOT NULL,
        amount_cents integer NOT NULL,
        currency text NOT NULL DEFAULT 'EUR',
        status text NOT NULL,
        provider_ref text,
        created_at timestamptz NOT NULL DEFAULT now(),
        updated_at timestamptz NOT NULL DEFAULT now(),
        CONSTRAINT nonzero_amount CHECK (amount_cents != 0),
        CONSTRAINT eur_only CHECK (currency = 'EUR'),
        CONSTRAINT valid_status CHECK (status IN ('payment', 'refund', 'partial_refund')),
        CONSTRAINT reporting_payment_lines_order_id_fkey
          FOREIGN KEY (order_id) REFERENCES orders_orders(id) ON DELETE RESTRICT,
        CONSTRAINT reporting_payment_lines_store_id_fkey
          FOREIGN KEY (store_id) REFERENCES pos_stores(id) ON DELETE RESTRICT
      )
    `);
  });

  // ── Helpers ─────────────────────────────────────────────────────────────

  async function columnInfo(column: string) {
    const result = await pool.query<{
      column_name: string;
      data_type: string;
      is_nullable: string;
      column_default: string | null;
    }>(
      `SELECT column_name, data_type, is_nullable, column_default
       FROM information_schema.columns
       WHERE table_name = 'reporting_payment_lines' AND column_name = $1`,
      [column],
    );
    return result.rows[0] ?? null;
  }

  async function fkInfo(fkName: string) {
    const result = await pool.query<{
      conname: string;
      contype: string;
      definition: string;
    }>(
      `SELECT conname, contype, pg_get_constraintdef(oid) AS definition
       FROM pg_constraint
       WHERE conrelid = 'reporting_payment_lines'::regclass
         AND contype = 'f' AND conname = $1`,
      [fkName],
    );
    return result.rows[0] ?? null;
  }

  async function indexExists(idxName: string) {
    const result = await pool.query<{ indexname: string }>(
      `SELECT indexname FROM pg_indexes
       WHERE tablename = 'reporting_payment_lines' AND indexname = $1`,
      [idxName],
    );
    return result.rows.length > 0;
  }

  async function insertPaymentLine(
    overrides: Partial<{
      orderId: string;
      storeId: string;
      terminalId: string | null;
      cashSessionId: string | null;
      paymentMethodId: string | null;
      provider: string;
      amountCents: number;
      currency: string;
      status: string;
      providerRef: string | null;
    }> = {},
  ) {
    const orderId = overrides.orderId ?? '00000000-0000-0000-0000-000000000002';
    const storeId = overrides.storeId ?? DEFAULT_STORE_ID;
    const provider = overrides.provider ?? 'stripe';
    const amountCents = overrides.amountCents ?? 1000;
    const currency = overrides.currency ?? 'EUR';
    const status = overrides.status ?? 'payment';

    const result = await pool.query(
      `INSERT INTO reporting_payment_lines
         (order_id, store_id, terminal_id, cash_session_id, payment_method_id,
          provider, amount_cents, currency, status, provider_ref)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
       RETURNING *`,
      [
        orderId,
        storeId,
        overrides.terminalId ?? null,
        overrides.cashSessionId ?? null,
        overrides.paymentMethodId ?? null,
        provider,
        amountCents,
        currency,
        status,
        overrides.providerRef ?? null,
      ],
    );
    return result.rows[0];
  }

  // ── AC1: Table structure ───────────────────────────────────────────────

  it('AC1: all 13 required columns exist', async () => {
    const cols = [
      'id', 'order_id', 'store_id', 'terminal_id', 'cash_session_id',
      'payment_method_id', 'provider', 'amount_cents', 'currency',
      'status', 'provider_ref', 'created_at', 'updated_at',
    ];
    for (const col of cols) {
      const info = await columnInfo(col);
      expect(info?.column_name).toBe(col);
    }
  });

  it('AC1: id is uuid with gen_random_uuid default', async () => {
    const info = await columnInfo('id');
    expect(info?.data_type).toBe('uuid');
    expect(info?.column_default).toContain('gen_random_uuid');
  });

  it('AC1: order_id NOT NULL uuid', async () => {
    const info = await columnInfo('order_id');
    expect(info?.is_nullable).toBe('NO');
    expect(info?.data_type).toBe('uuid');
  });

  it('AC1: store_id NOT NULL uuid', async () => {
    const info = await columnInfo('store_id');
    expect(info?.is_nullable).toBe('NO');
    expect(info?.data_type).toBe('uuid');
  });

  it('AC1: provider NOT NULL text', async () => {
    const info = await columnInfo('provider');
    expect(info?.is_nullable).toBe('NO');
  });

  it('AC1: amount_cents NOT NULL integer', async () => {
    const info = await columnInfo('amount_cents');
    expect(info?.is_nullable).toBe('NO');
  });

  it('AC1: currency defaults to EUR', async () => {
    const info = await columnInfo('currency');
    expect(info?.column_default).toBe("'EUR'::text");
  });

  it('AC1: nullable columns are nullable', async () => {
    for (const col of [
      'terminal_id', 'cash_session_id', 'payment_method_id', 'provider_ref',
    ]) {
      const info = await columnInfo(col);
      expect(info?.is_nullable).toBe('YES');
    }
  });

  // ── AC1: CHECK constraints ──────────────────────────────────────────────

  it('AC1: CHECK nonzero_amount rejects amount_cents=0', async () => {
    await expect(
      pool.query(
        `INSERT INTO reporting_payment_lines
           (order_id, store_id, provider, amount_cents, currency, status)
         VALUES ($1, $2, $3, 0, 'EUR', 'payment')`,
        ['00000000-0000-0000-0000-000000000002', DEFAULT_STORE_ID, 'stripe'],
      ),
    ).rejects.toThrow();
  });

  it('AC1: CHECK eur_only rejects non-EUR currency', async () => {
    await expect(
      pool.query(
        `INSERT INTO reporting_payment_lines
           (order_id, store_id, provider, amount_cents, currency, status)
         VALUES ($1, $2, $3, 500, 'USD', 'payment')`,
        ['00000000-0000-0000-0000-000000000002', DEFAULT_STORE_ID, 'stripe'],
      ),
    ).rejects.toThrow();
  });

  it('AC1: CHECK valid_status rejects invalid status', async () => {
    await expect(
      pool.query(
        `INSERT INTO reporting_payment_lines
           (order_id, store_id, provider, amount_cents, currency, status)
         VALUES ($1, $2, $3, 500, 'EUR', 'pending')`,
        ['00000000-0000-0000-0000-000000000002', DEFAULT_STORE_ID, 'stripe'],
      ),
    ).rejects.toThrow();
  });

  // ── AC1: Foreign Keys ───────────────────────────────────────────────────

  it('AC1: FK order_id references orders_orders(id)', async () => {
    const fk = await fkInfo('reporting_payment_lines_order_id_fkey');
    expect(fk?.conname).toBe('reporting_payment_lines_order_id_fkey');
    expect(fk?.definition).toContain('orders_orders');
    expect(fk?.definition).toContain('order_id');
  });

  it('AC1: FK store_id references pos_stores(id)', async () => {
    const fk = await fkInfo('reporting_payment_lines_store_id_fkey');
    expect(fk?.conname).toBe('reporting_payment_lines_store_id_fkey');
    expect(fk?.definition).toContain('pos_stores');
    expect(fk?.definition).toContain('store_id');
  });

  it('AC1: INSERT fails without valid order_id FK', async () => {
    await expect(
      pool.query(
        `INSERT INTO reporting_payment_lines
           (order_id, store_id, provider, amount_cents, currency, status)
         VALUES ($1, $2, $3, 500, 'EUR', 'payment')`,
        ['99999999-9999-9999-9999-999999999999', DEFAULT_STORE_ID, 'stripe'],
      ),
    ).rejects.toThrow();
  });

  // ── AC2: Valid INSERT ───────────────────────────────────────────────────

  it('AC2: valid INSERT succeeds and returns all columns', async () => {
    const row = await insertPaymentLine();
    expect(row.id).toBeTruthy();
    expect(row.order_id).toBe('00000000-0000-0000-0000-000000000002');
    expect(row.store_id).toBe(DEFAULT_STORE_ID);
    expect(row.provider).toBe('stripe');
    expect(row.amount_cents).toBe(1000);
    expect(row.currency).toBe('EUR');
    expect(row.status).toBe('payment');
  });

  it('AC2: all three status values accepted', async () => {
    for (const status of ['payment', 'refund', 'partial_refund']) {
      await pool.query(
        `DELETE FROM reporting_payment_lines WHERE order_id = $1`,
        ['00000000-0000-0000-0000-000000000002'],
      );
      const row = await insertPaymentLine({ amountCents: 500, status });
      expect(row.status).toBe(status);
    }
  });

  // ── AC4: Indexes ───────────────────────────────────────────────────────

  it('AC4: reporting_payment_lines_store_created_idx exists', async () => {
    await expect(indexExists('reporting_payment_lines_store_created_idx')).resolves.toBe(true);
  });

  it('AC4: reporting_payment_lines_order_idx exists', async () => {
    await expect(indexExists('reporting_payment_lines_order_idx')).resolves.toBe(true);
  });

  it('AC4: reporting_payment_lines_session_idx (partial) exists', async () => {
    await expect(indexExists('reporting_payment_lines_session_idx')).resolves.toBe(true);
  });

  // ── AC5: Immutability / refund-as-new-row ───────────────────────────────

  it('AC5: refund is a new row with status=refund (immutable pattern)', async () => {
    await pool.query(
      `DELETE FROM reporting_payment_lines WHERE order_id = $1`,
      ['00000000-0000-0000-0000-000000000002'],
    );
    const paymentRow = await insertPaymentLine({ amountCents: 1000, status: 'payment' });
    const refundRow = await insertPaymentLine({
      status: 'refund',
      amountCents: -1000,
      providerRef: 'pi_test_123_refund',
    });
    expect(paymentRow.status).toBe('payment');
    expect(refundRow.status).toBe('refund');
    expect(paymentRow.amount_cents).toBe(1000);
    expect(refundRow.amount_cents).toBe(-1000);
  });

  // ── PG version ─────────────────────────────────────────────────────────

  it('runs against PG version', () => {
    expect(dbVersion).toBeTruthy();
  });
});
