import type pg from 'pg';
import type { InventoryRepository } from '../domain/ports.js';
import type { SetAvailableStockCommand, StockCommand, StockItem } from '../domain/stock.js';

interface StockRow {
  id: string;
  variant_id: string;
  store_id: string;
  available: number;
  reserved: number;
  sold: number;
  incoming: number;
  created_at: Date;
  updated_at: Date;
}

type InventoryOperation = 'reserve' | 'release' | 'confirm' | 'set_available';

export class PgInventoryRepository implements InventoryRepository {
  constructor(private readonly pool: pg.Pool) {}

  async findByVariantId(variantId: string, storeId: string): Promise<StockItem | undefined> {
    const result = await this.pool.query<StockRow>(
      'SELECT * FROM inventory_stock WHERE variant_id = $1 AND store_id = $2',
      [variantId, storeId],
    );
    const row = result.rows[0];
    return row ? toStockItem(row) : undefined;
  }

  async setAvailable(input: SetAvailableStockCommand): Promise<StockItem> {
    const client = await this.pool.connect();
    try {
      await client.query('BEGIN');
      const result = await client.query<StockRow>(
        `INSERT INTO inventory_stock (variant_id, store_id, available)
         VALUES ($1, $2, $3)
         ON CONFLICT (variant_id, store_id) DO UPDATE
           SET available = EXCLUDED.available, updated_at = now()
         RETURNING *`,
        [input.variantId, input.storeId, input.quantity],
      );
      const item = rowOrThrow(result.rows[0], 'inventory_stock upsert returned no row');
      await insertMovement(client, input.variantId, input.storeId, 'set_available', input.quantity);
      await client.query('COMMIT');
      return item;
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  async reserve(input: StockCommand): Promise<StockItem | undefined> {
    return this.applyAtomicOperation(input, 'reserve', (client, command) =>
      client.query<StockRow>(
        `UPDATE inventory_stock
         SET available = available - $2, reserved = reserved + $2, updated_at = now()
         WHERE variant_id = $1 AND store_id = $3 AND available >= $2
         RETURNING *`,
        [command.variantId, command.quantity, command.storeId],
      ),
    );
  }

  async release(input: StockCommand): Promise<StockItem | undefined> {
    return this.applyAtomicOperation(input, 'release', (client, command) =>
      client.query<StockRow>(
        `UPDATE inventory_stock
         SET reserved = reserved - $2, available = available + $2, updated_at = now()
         WHERE variant_id = $1 AND store_id = $3 AND reserved >= $2
         RETURNING *`,
        [command.variantId, command.quantity, command.storeId],
      ),
    );
  }

  async confirm(input: StockCommand): Promise<StockItem | undefined> {
    return this.applyAtomicOperation(input, 'confirm', (client, command) =>
      client.query<StockRow>(
        `UPDATE inventory_stock
         SET reserved = reserved - $2, sold = sold + $2, updated_at = now()
         WHERE variant_id = $1 AND store_id = $3 AND reserved >= $2
         RETURNING *`,
        [command.variantId, command.quantity, command.storeId],
      ),
    );
  }

  private async applyAtomicOperation(
    input: StockCommand,
    operation: InventoryOperation,
    update: (client: pg.PoolClient, input: StockCommand) => Promise<pg.QueryResult<StockRow>>,
  ): Promise<StockItem | undefined> {
    const client = await this.pool.connect();
    try {
      await client.query('BEGIN');
      const result = await update(client, input);
      const row = result.rows[0];
      if (!row) {
        await client.query('ROLLBACK');
        return undefined;
      }
      await insertMovement(client, input.variantId, input.storeId, operation, input.quantity);
      await client.query('COMMIT');
      return toStockItem(row);
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }
}

async function insertMovement(
  client: pg.PoolClient,
  variantId: string,
  storeId: string,
  operation: InventoryOperation,
  quantity: number,
): Promise<void> {
  await client.query(
    `INSERT INTO inventory_movements (variant_id, store_id, operation, quantity)
     VALUES ($1, $2, $3, $4)`,
    [variantId, storeId, operation, quantity],
  );
}

function rowOrThrow(row: StockRow | undefined, message: string): StockItem {
  if (!row) {
    throw new Error(message);
  }
  return toStockItem(row);
}

function toStockItem(row: StockRow): StockItem {
  return {
    id: row.id,
    variantId: row.variant_id,
    storeId: row.store_id,
    available: row.available,
    reserved: row.reserved,
    sold: row.sold,
    incoming: row.incoming,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}
