import type pg from 'pg';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { CreatePosSaleUseCase } from '../../modules/pos/index.js';
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);
const STORE_ID = '00000000-0000-0000-0000-000000000001';
const USER_ID = '10000000-0000-4000-8000-000000000001';
const TERMINAL_ID = '20000000-0000-4000-8000-000000000001';
const SESSION_ID = '30000000-0000-4000-8000-000000000001';
const PRODUCT_ID = '40000000-0000-4000-8000-000000000001';
const VARIANT_ID = '50000000-0000-4000-8000-000000000001';

describe.skipIf(!hasDb)('F-186 POS checkout and receipts (real PostgreSQL)', () => {
  const url = hasDb ? getTestDbUrl() : '';
  let pool: pg.Pool;

  beforeAll(async () => {
    await recreateDatabase(url);
    await runMigrations(url, 'up');
    pool = createPool(url);
    await pool.query(
      `INSERT INTO backoffice_users (id, email, password_hash, role)
       VALUES ($1, 'cashier@example.test', 'test-hash', 'pos_cashier')`,
      [USER_ID],
    );
    await pool.query(
      `INSERT INTO pos_terminals (id, store_id, name, settings)
       VALUES ($1, $2, 'Caja test', '{"lineDiscountsEnabled":true}'::jsonb)`,
      [TERMINAL_ID, STORE_ID],
    );
    await pool.query(
      `INSERT INTO pos_cash_sessions (
         id, terminal_id, store_id, user_id, opening_cash_cents, expected_cash_cents
       ) VALUES ($1, $2, $3, $4, 2000, NULL)`,
      [SESSION_ID, TERMINAL_ID, STORE_ID, USER_ID],
    );
    await pool.query(
      `INSERT INTO catalog_products (id, name, slug, state)
       VALUES ($1, 'Producto test', 'producto-test', 'active')`,
      [PRODUCT_ID],
    );
    await pool.query(
      `INSERT INTO catalog_product_variants (id, product_id, sku, ean)
       VALUES ($1, $2, 'SKU-TEST', '8412345678901')`,
      [VARIANT_ID, PRODUCT_ID],
    );
    await pool.query(
      `INSERT INTO pricing_variant_prices (variant_id, net_unit_amount_cents, vat_rate)
       VALUES ($1, 700, 'reduced')`,
      [VARIANT_ID],
    );
    await pool.query(
      `INSERT INTO inventory_stock (variant_id, store_id, available)
       VALUES ($1, $2, 3)`,
      [VARIANT_ID, STORE_ID],
    );
  });

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

  it('atomically records mixed payment, change, free item, reporting and receipt', async () => {
    const useCase = new CreatePosSaleUseCase(pool);
    const input = {
      idempotencyKey: 'f186-sale-1',
      cashSessionId: SESSION_ID,
      terminalId: TERMINAL_ID,
      userId: USER_ID,
      items: [
        { kind: 'stock' as const, variantId: VARIANT_ID, quantity: 1, discountCents: 0 },
        { kind: 'free' as const, name: 'Servicio libre', unitPriceCents: 500, quantity: 1 },
      ],
      payments: [
        { methodCode: 'cash', amountCents: 500, tenderedCents: 1000 },
        { methodCode: 'card', amountCents: 770 },
      ],
    };

    const result = await useCase.execute(input);

    expect(result.totalCents).toBe(1270);
    expect(result.changeCents).toBe(500);
    expect(result.receiptNumber).toBe('TPV-000001');
    expect(result.receipt.items).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ name: 'Producto test', freeItem: false, totalCents: 770, taxCents: 70 }),
        expect.objectContaining({ name: 'Servicio libre', freeItem: true, totalCents: 500 }),
      ]),
    );
    expect(result.receipt.payments).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ methodCode: 'cash', amountCents: 500, changeCents: 500 }),
        expect.objectContaining({ methodCode: 'card', amountCents: 770 }),
      ]),
    );

    const order = await pool.query<{ state: string; receipt_number: string }>(
      'SELECT state, receipt_number FROM orders_orders WHERE id = $1',
      [result.orderId],
    );
    expect(order.rows[0]).toEqual({ state: 'COMPLETED', receipt_number: 'TPV-000001' });

    const freeLine = await pool.query<{
      product_id: string | null;
      variant_id: string | null;
      is_free_item: boolean;
    }>(
      `SELECT product_id, variant_id, is_free_item FROM orders_items
        WHERE order_id = $1 AND is_free_item = true`,
      [result.orderId],
    );
    expect(freeLine.rows[0]).toEqual({ product_id: null, variant_id: null, is_free_item: true });

    const stock = await pool.query<{ available: number; sold: number }>(
      'SELECT available, sold FROM inventory_stock WHERE variant_id = $1 AND store_id = $2',
      [VARIANT_ID, STORE_ID],
    );
    expect(stock.rows[0]).toEqual({ available: 2, sold: 1 });

    const reporting = await pool.query<{ count: number }>(
      'SELECT COUNT(*)::int AS count FROM reporting_payment_lines WHERE order_id = $1',
      [result.orderId],
    );
    expect(reporting.rows[0]?.count).toBe(2);

    const session = await pool.query<{ expected_cash_cents: number }>(
      'SELECT expected_cash_cents FROM pos_cash_sessions WHERE id = $1',
      [SESSION_ID],
    );
    expect(session.rows[0]?.expected_cash_cents).toBe(2500);

    const replay = await useCase.execute(input);
    expect(replay.orderId).toBe(result.orderId);
    expect(replay.receiptNumber).toBe(result.receiptNumber);
    const afterReplay = await pool.query<{ available: number }>(
      'SELECT available FROM inventory_stock WHERE variant_id = $1 AND store_id = $2',
      [VARIANT_ID, STORE_ID],
    );
    expect(afterReplay.rows[0]?.available).toBe(2);
  });
});
