import { query, queryOne, getClient, PoolClient } from '../../config/database.js';
import { ValidationError } from '../../shared/errors/index.js';
import { logger } from '../../shared/utils/logger.js';

/**
 * Stock Reservation Service
 *
 * Manages soft reservations for stock. Reservations don't move stock,
 * they mark quantities as committed to specific orders/documents.
 *
 * Key concepts:
 * - quantity: Total physical stock at location
 * - reserved_quantity: Stock committed to orders but not yet picked
 * - available = quantity - reserved_quantity
 *
 * Used by:
 * - Sales Orders: Reserve on confirm, release on cancel
 * - Transfers: Reserve on confirm, release on complete/cancel
 */

export interface ReservationLine {
  productId: string;
  locationId: string;
  quantity: number;
  lotId?: string;
}

export interface ReservationResult {
  success: boolean;
  lines: ReservationLineResult[];
  errors: string[];
}

export interface ReservationLineResult {
  productId: string;
  locationId: string;
  lotId?: string;
  requestedQty: number;
  reservedQty: number;
  availableQty: number;
  success: boolean;
  error?: string;
}

export interface StockAvailability {
  productId: string;
  locationId: string;
  lotId?: string;
  quantity: number;
  reservedQuantity: number;
  availableQuantity: number;
}

class StockReservationService {
  /**
   * Check stock availability for a list of products at locations
   */
  async checkAvailability(
    lines: ReservationLine[],
    tenantId: string
  ): Promise<StockAvailability[]> {
    const results: StockAvailability[] = [];

    for (const line of lines) {
      const lotCondition = line.lotId
        ? 'AND sq.lot_id = $4'
        : 'AND sq.lot_id IS NULL';

      const params = line.lotId
        ? [tenantId, line.productId, line.locationId, line.lotId]
        : [tenantId, line.productId, line.locationId];

      const quant = await queryOne<{
        quantity: string;
        reserved_quantity: string;
      }>(
        `SELECT
           COALESCE(SUM(sq.quantity), 0) as quantity,
           COALESCE(SUM(sq.reserved_quantity), 0) as reserved_quantity
         FROM inventory.stock_quants sq
         WHERE sq.tenant_id = $1
           AND sq.product_id = $2
           AND sq.location_id = $3
           ${lotCondition}`,
        params
      );

      const quantity = parseFloat(quant?.quantity || '0');
      const reservedQuantity = parseFloat(quant?.reserved_quantity || '0');

      results.push({
        productId: line.productId,
        locationId: line.locationId,
        lotId: line.lotId,
        quantity,
        reservedQuantity,
        availableQuantity: quantity - reservedQuantity,
      });
    }

    return results;
  }

  /**
   * Reserve stock for an order/document
   *
   * @param lines - Lines to reserve
   * @param tenantId - Tenant ID
   * @param sourceDocument - Reference to source document (e.g., "SO-000001")
   * @param allowPartial - If true, reserve what's available even if less than requested
   * @returns Reservation result with details per line
   */
  async reserve(
    lines: ReservationLine[],
    tenantId: string,
    sourceDocument: string,
    allowPartial: boolean = false
  ): Promise<ReservationResult> {
    const results: ReservationLineResult[] = [];
    const errors: string[] = [];

    // First check availability
    const availability = await this.checkAvailability(lines, tenantId);

    // Validate all lines have sufficient stock (if partial not allowed)
    if (!allowPartial) {
      for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        const avail = availability[i];

        if (avail.availableQuantity < line.quantity) {
          errors.push(
            `Producto ${line.productId}: disponible ${avail.availableQuantity}, solicitado ${line.quantity}`
          );
        }
      }

      if (errors.length > 0) {
        return {
          success: false,
          lines: [],
          errors,
        };
      }
    }

    // Reserve stock
    const client = await getClient();
    try {
      await client.query('BEGIN');

      for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        const avail = availability[i];
        const qtyToReserve = allowPartial
          ? Math.min(line.quantity, avail.availableQuantity)
          : line.quantity;

        if (qtyToReserve <= 0) {
          results.push({
            productId: line.productId,
            locationId: line.locationId,
            lotId: line.lotId,
            requestedQty: line.quantity,
            reservedQty: 0,
            availableQty: avail.availableQuantity,
            success: false,
            error: 'Sin stock disponible',
          });
          continue;
        }

        // Update reserved_quantity
        const lotCondition = line.lotId
          ? 'AND lot_id = $5'
          : 'AND lot_id IS NULL';

        const params = line.lotId
          ? [qtyToReserve, tenantId, line.productId, line.locationId, line.lotId]
          : [qtyToReserve, tenantId, line.productId, line.locationId];

        await client.query(
          `UPDATE inventory.stock_quants
           SET reserved_quantity = reserved_quantity + $1,
               updated_at = CURRENT_TIMESTAMP
           WHERE tenant_id = $2
             AND product_id = $3
             AND location_id = $4
             ${lotCondition}`,
          params
        );

        results.push({
          productId: line.productId,
          locationId: line.locationId,
          lotId: line.lotId,
          requestedQty: line.quantity,
          reservedQty: qtyToReserve,
          availableQty: avail.availableQuantity - qtyToReserve,
          success: true,
        });
      }

      await client.query('COMMIT');

      logger.info('Stock reserved', {
        sourceDocument,
        tenantId,
        linesReserved: results.filter(r => r.success).length,
      });

      return {
        success: results.every(r => r.success),
        lines: results,
        errors,
      };
    } catch (error) {
      await client.query('ROLLBACK');
      logger.error('Error reserving stock', {
        error: (error as Error).message,
        sourceDocument,
        tenantId,
      });
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * Release previously reserved stock
   *
   * @param lines - Lines to release
   * @param tenantId - Tenant ID
   * @param sourceDocument - Reference to source document
   */
  async release(
    lines: ReservationLine[],
    tenantId: string,
    sourceDocument: string
  ): Promise<void> {
    const client = await getClient();
    try {
      await client.query('BEGIN');

      for (const line of lines) {
        const lotCondition = line.lotId
          ? 'AND lot_id = $5'
          : 'AND lot_id IS NULL';

        const params = line.lotId
          ? [line.quantity, tenantId, line.productId, line.locationId, line.lotId]
          : [line.quantity, tenantId, line.productId, line.locationId];

        // Decrease reserved_quantity (don't go below 0)
        await client.query(
          `UPDATE inventory.stock_quants
           SET reserved_quantity = GREATEST(reserved_quantity - $1, 0),
               updated_at = CURRENT_TIMESTAMP
           WHERE tenant_id = $2
             AND product_id = $3
             AND location_id = $4
             ${lotCondition}`,
          params
        );
      }

      await client.query('COMMIT');

      logger.info('Stock reservation released', {
        sourceDocument,
        tenantId,
        linesReleased: lines.length,
      });
    } catch (error) {
      await client.query('ROLLBACK');
      logger.error('Error releasing stock reservation', {
        error: (error as Error).message,
        sourceDocument,
        tenantId,
      });
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * Reserve stock within an existing transaction
   * Used when reservation is part of a larger transaction (e.g., confirm order)
   */
  async reserveWithClient(
    client: PoolClient,
    lines: ReservationLine[],
    tenantId: string,
    sourceDocument: string,
    allowPartial: boolean = false
  ): Promise<ReservationResult> {
    const results: ReservationLineResult[] = [];
    const errors: string[] = [];

    // Check availability
    for (const line of lines) {
      const lotCondition = line.lotId
        ? 'AND sq.lot_id = $4'
        : 'AND sq.lot_id IS NULL';

      const params = line.lotId
        ? [tenantId, line.productId, line.locationId, line.lotId]
        : [tenantId, line.productId, line.locationId];

      const quantResult = await client.query(
        `SELECT
           COALESCE(SUM(sq.quantity), 0) as quantity,
           COALESCE(SUM(sq.reserved_quantity), 0) as reserved_quantity
         FROM inventory.stock_quants sq
         WHERE sq.tenant_id = $1
           AND sq.product_id = $2
           AND sq.location_id = $3
           ${lotCondition}`,
        params
      );

      const quantity = parseFloat(quantResult.rows[0]?.quantity || '0');
      const reservedQuantity = parseFloat(quantResult.rows[0]?.reserved_quantity || '0');
      const availableQuantity = quantity - reservedQuantity;
      const qtyToReserve = allowPartial
        ? Math.min(line.quantity, availableQuantity)
        : line.quantity;

      if (!allowPartial && availableQuantity < line.quantity) {
        errors.push(
          `Producto ${line.productId}: disponible ${availableQuantity}, solicitado ${line.quantity}`
        );
        results.push({
          productId: line.productId,
          locationId: line.locationId,
          lotId: line.lotId,
          requestedQty: line.quantity,
          reservedQty: 0,
          availableQty: availableQuantity,
          success: false,
          error: 'Stock insuficiente',
        });
        continue;
      }

      if (qtyToReserve > 0) {
        // Update reserved_quantity
        const updateLotCondition = line.lotId
          ? 'AND lot_id = $5'
          : 'AND lot_id IS NULL';

        const updateParams = line.lotId
          ? [qtyToReserve, tenantId, line.productId, line.locationId, line.lotId]
          : [qtyToReserve, tenantId, line.productId, line.locationId];

        await client.query(
          `UPDATE inventory.stock_quants
           SET reserved_quantity = reserved_quantity + $1,
               updated_at = CURRENT_TIMESTAMP
           WHERE tenant_id = $2
             AND product_id = $3
             AND location_id = $4
             ${updateLotCondition}`,
          updateParams
        );
      }

      results.push({
        productId: line.productId,
        locationId: line.locationId,
        lotId: line.lotId,
        requestedQty: line.quantity,
        reservedQty: qtyToReserve,
        availableQty: availableQuantity - qtyToReserve,
        success: qtyToReserve > 0 || line.quantity === 0,
      });
    }

    return {
      success: errors.length === 0,
      lines: results,
      errors,
    };
  }

  /**
   * Release stock within an existing transaction
   */
  async releaseWithClient(
    client: PoolClient,
    lines: ReservationLine[],
    tenantId: string
  ): Promise<void> {
    for (const line of lines) {
      const lotCondition = line.lotId
        ? 'AND lot_id = $5'
        : 'AND lot_id IS NULL';

      const params = line.lotId
        ? [line.quantity, tenantId, line.productId, line.locationId, line.lotId]
        : [line.quantity, tenantId, line.productId, line.locationId];

      await client.query(
        `UPDATE inventory.stock_quants
         SET reserved_quantity = GREATEST(reserved_quantity - $1, 0),
             updated_at = CURRENT_TIMESTAMP
         WHERE tenant_id = $2
           AND product_id = $3
           AND location_id = $4
           ${lotCondition}`,
        params
      );
    }
  }

  /**
   * Get total available stock for a product across all locations
   */
  async getProductAvailability(
    productId: string,
    tenantId: string,
    warehouseId?: string
  ): Promise<{
    totalQuantity: number;
    totalReserved: number;
    totalAvailable: number;
    byLocation: StockAvailability[];
  }> {
    let whereClause = 'WHERE sq.tenant_id = $1 AND sq.product_id = $2';
    const params: any[] = [tenantId, productId];

    if (warehouseId) {
      whereClause += ' AND l.warehouse_id = $3';
      params.push(warehouseId);
    }

    const result = await query<{
      location_id: string;
      lot_id: string | null;
      quantity: string;
      reserved_quantity: string;
    }>(
      `SELECT sq.location_id, sq.lot_id, sq.quantity, sq.reserved_quantity
       FROM inventory.stock_quants sq
       LEFT JOIN inventory.locations l ON sq.location_id = l.id
       ${whereClause}`,
      params
    );

    const byLocation: StockAvailability[] = result.map(row => ({
      productId,
      locationId: row.location_id,
      lotId: row.lot_id || undefined,
      quantity: parseFloat(row.quantity),
      reservedQuantity: parseFloat(row.reserved_quantity),
      availableQuantity: parseFloat(row.quantity) - parseFloat(row.reserved_quantity),
    }));

    const totalQuantity = byLocation.reduce((sum, l) => sum + l.quantity, 0);
    const totalReserved = byLocation.reduce((sum, l) => sum + l.reservedQuantity, 0);

    return {
      totalQuantity,
      totalReserved,
      totalAvailable: totalQuantity - totalReserved,
      byLocation,
    };
  }
}

export const stockReservationService = new StockReservationService();
