import type pg from 'pg';
import type { PosSaleInput, PosSaleResult, PosPaymentResult } from '../domain/pos-sale.js';
import { AppError } from '../../../shared/errors.js';

interface PaymentRow { id: string; amount_cents: number; kind: string; }
interface OrderRow { id: string; idempotency_key: string; total_cents: number; created_at: Date; }

export class CreatePosSaleUseCase {
  constructor(private readonly pool: pg.Pool) {}

  async execute(input: PosSaleInput): Promise<PosSaleResult> {
    const client = await this.pool.connect();
    try {
      await client.query('BEGIN');

      // 1. Idempotency: return existing sale if key already used
      const existing = await client.query<{ id: string }>(
        'SELECT id FROM orders_orders WHERE idempotency_key = $1',
        [input.idempotencyKey],
      );
      if (existing.rows[0]) {
        const existId = existing.rows[0].id;
        await client.query('ROLLBACK');
        const order = await this.pool.query<OrderRow>(
          'SELECT id, idempotency_key, total_cents, created_at FROM orders_orders WHERE idempotency_key = $1',
          [input.idempotencyKey],
        );
        const items = await this.pool.query('SELECT * FROM orders_items WHERE order_id = $1', [existId]);
        const payments = await this.pool.query<PaymentRow>(
          'SELECT id, amount_cents, kind FROM payments_transactions WHERE order_id = $1',
          [existId],
        );
        const totalCents = (order.rows[0]?.total_cents ?? 0) as number;
        const mappedItems: PosSaleResult['items'] = items.rows as PosSaleResult['items'];
        return {
          orderId: existId,
          idempotencyKey: input.idempotencyKey,
          totalCents,
          items: mappedItems,
          payments: payments.rows.map(r => ({
            id: r.id, kind: r.kind as PosPaymentResult['kind'], amountCents: r.amount_cents,
          })),
          createdAt: order.rows[0]?.created_at ?? new Date(),
        };
      }

      // 2. Verify cash session is open (FOR UPDATE lock)
      const session = await client.query<{ id: string; status: string }>(
        `SELECT id, status FROM pos_cash_sessions WHERE id = $1 FOR UPDATE`,
        [input.cashSessionId],
      );
      if (!session.rows[0]) { await client.query('ROLLBACK'); throw new AppError(404, 'SESSION_NOT_FOUND', 'Cash session not found'); }
      if (session.rows[0].status !== 'OPEN') { await client.query('ROLLBACK'); throw new AppError(409, 'SESSION_CLOSED', 'Cash session is not open'); }

      // 3. Compute totals
      const subtotalCents = input.items.reduce((s, i) => s + (i.unitPriceCents - i.discountCents) * i.quantity, 0);
      const taxCents = input.items.reduce((s, i) => s + i.taxCents * i.quantity, 0);
      const discountCents = input.items.reduce((s, i) => s + i.discountCents * i.quantity, 0);
      const totalCents = subtotalCents + taxCents;
      const paymentTotal = input.payments.reduce((s, p) => s + p.amountCents, 0);
      if (paymentTotal < totalCents) { await client.query('ROLLBACK'); throw new AppError(400, 'INSUFFICIENT_PAYMENT', 'Payments less than total'); }

      // 4. Create order
      const orderResult = await client.query<OrderRow>(
        `INSERT INTO orders_orders (user_id, idempotency_key, subtotal_cents, discount_cents, tax_cents, total_cents)
         VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, idempotency_key, total_cents, created_at`,
        [input.userId, input.idempotencyKey, subtotalCents, discountCents, taxCents, totalCents],
      );
      const orderRow = orderResult.rows[0];
      if (!orderRow) { await client.query('ROLLBACK'); throw new AppError(500, 'ORDER_CREATE_FAILED', 'Order insert returned no row'); }
      const orderId = orderRow.id;

      // 5. Insert line items
      for (const item of input.items) {
        await client.query(
          `INSERT INTO orders_items (order_id, product_id, variant_id, sku, ean, name, unit_price_cents, discount_cents, tax_cents, quantity)
           VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
          [orderId, item.productId, item.variantId, item.sku, item.ean, item.name, item.unitPriceCents, item.discountCents, item.taxCents, item.quantity],
        );
      }

      // 6. Process payments + update cash session
      const paymentResults: PosPaymentResult[] = [];
      let cashPaymentCents = 0;
      for (const p of input.payments) {
        const kind = p.kind === 'cash' ? 'pos_cash' : p.kind === 'card' ? 'pos_card' : 'pos_other';
        const payResult = await client.query<{ id: string }>(
          `INSERT INTO payments_transactions (provider, provider_event_id, provider_payment_id, order_id, amount_cents, currency, status, raw)
           VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`,
          [kind, `pos-${orderId}-${p.kind}`, `pos-${Date.now()}`, orderId, p.amountCents, 'EUR', 'COMPLETED', JSON.stringify(p)],
        );
        const payRow = payResult.rows[0];
        if (!payRow) { await client.query('ROLLBACK'); throw new AppError(500, 'PAYMENT_INSERT_FAILED', 'Payment insert returned no row'); }
        paymentResults.push({ id: payRow.id, kind: p.kind, amountCents: p.amountCents });
        if (p.kind === 'cash') cashPaymentCents += p.tenderedCents ?? p.amountCents;
      }

      if (cashPaymentCents > 0) {
        await client.query(
          `UPDATE pos_cash_sessions SET expected_cash_cents = COALESCE(expected_cash_cents, 0) + $1, updated_at = now() WHERE id = $2`,
          [cashPaymentCents, input.cashSessionId],
        );
      }

      await client.query('COMMIT');
      return {
        orderId,
        idempotencyKey: input.idempotencyKey,
        totalCents,
        items: input.items,
        payments: paymentResults,
        createdAt: orderRow.created_at,
      };
    } catch (err) {
      await client.query('ROLLBACK');
      throw err;
    } finally {
      client.release();
    }
  }
}
