import { query, queryOne, getClient } from '../../config/database.js';
import { NotFoundError, ValidationError, ConflictError } from '../../shared/errors/index.js';

export type AdjustmentStatus = 'draft' | 'confirmed' | 'done' | 'cancelled';

export interface AdjustmentLine {
  id: string;
  adjustment_id: string;
  product_id: string;
  product_name?: string;
  product_code?: string;
  location_id: string;
  location_name?: string;
  lot_id?: string;
  lot_name?: string;
  theoretical_qty: number;
  counted_qty: number;
  difference_qty: number;
  uom_id: string;
  uom_name?: string;
  notes?: string;
  created_at: Date;
}

export interface Adjustment {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  name: string;
  location_id: string;
  location_name?: string;
  date: Date;
  status: AdjustmentStatus;
  notes?: string;
  lines?: AdjustmentLine[];
  created_at: Date;
}

export interface CreateAdjustmentLineDto {
  product_id: string;
  location_id: string;
  lot_id?: string;
  counted_qty: number;
  uom_id: string;
  notes?: string;
}

export interface CreateAdjustmentDto {
  company_id: string;
  location_id: string;
  date?: string;
  notes?: string;
  lines: CreateAdjustmentLineDto[];
}

export interface UpdateAdjustmentDto {
  location_id?: string;
  date?: string;
  notes?: string | null;
}

export interface UpdateAdjustmentLineDto {
  counted_qty?: number;
  notes?: string | null;
}

export interface AdjustmentFilters {
  company_id?: string;
  location_id?: string;
  status?: AdjustmentStatus;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;
  limit?: number;
}

class AdjustmentsService {
  async findAll(tenantId: string, filters: AdjustmentFilters = {}): Promise<{ data: Adjustment[]; total: number }> {
    const { company_id, location_id, status, date_from, date_to, search, page = 1, limit = 20 } = filters;
    const offset = (page - 1) * limit;

    let whereClause = 'WHERE a.tenant_id = $1';
    const params: any[] = [tenantId];
    let paramIndex = 2;

    if (company_id) {
      whereClause += ` AND a.company_id = $${paramIndex++}`;
      params.push(company_id);
    }

    if (location_id) {
      whereClause += ` AND a.location_id = $${paramIndex++}`;
      params.push(location_id);
    }

    if (status) {
      whereClause += ` AND a.status = $${paramIndex++}`;
      params.push(status);
    }

    if (date_from) {
      whereClause += ` AND a.date >= $${paramIndex++}`;
      params.push(date_from);
    }

    if (date_to) {
      whereClause += ` AND a.date <= $${paramIndex++}`;
      params.push(date_to);
    }

    if (search) {
      whereClause += ` AND (a.name ILIKE $${paramIndex} OR a.notes ILIKE $${paramIndex})`;
      params.push(`%${search}%`);
      paramIndex++;
    }

    const countResult = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM inventory.inventory_adjustments a ${whereClause}`,
      params
    );

    params.push(limit, offset);
    const data = await query<Adjustment>(
      `SELECT a.*,
              c.name as company_name,
              l.name as location_name
       FROM inventory.inventory_adjustments a
       LEFT JOIN auth.companies c ON a.company_id = c.id
       LEFT JOIN inventory.locations l ON a.location_id = l.id
       ${whereClause}
       ORDER BY a.date DESC, a.created_at DESC
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

    return {
      data,
      total: parseInt(countResult?.count || '0', 10),
    };
  }

  async findById(id: string, tenantId: string): Promise<Adjustment> {
    const adjustment = await queryOne<Adjustment>(
      `SELECT a.*,
              c.name as company_name,
              l.name as location_name
       FROM inventory.inventory_adjustments a
       LEFT JOIN auth.companies c ON a.company_id = c.id
       LEFT JOIN inventory.locations l ON a.location_id = l.id
       WHERE a.id = $1 AND a.tenant_id = $2`,
      [id, tenantId]
    );

    if (!adjustment) {
      throw new NotFoundError('Ajuste de inventario no encontrado');
    }

    // Get lines
    const lines = await query<AdjustmentLine>(
      `SELECT al.*,
              p.name as product_name,
              p.code as product_code,
              l.name as location_name,
              lot.name as lot_name,
              u.name as uom_name
       FROM inventory.inventory_adjustment_lines al
       LEFT JOIN inventory.products p ON al.product_id = p.id
       LEFT JOIN inventory.locations l ON al.location_id = l.id
       LEFT JOIN inventory.lots lot ON al.lot_id = lot.id
       LEFT JOIN core.uom u ON al.uom_id = u.id
       WHERE al.adjustment_id = $1
       ORDER BY al.created_at`,
      [id]
    );

    adjustment.lines = lines;

    return adjustment;
  }

  async create(dto: CreateAdjustmentDto, tenantId: string, userId: string): Promise<Adjustment> {
    if (dto.lines.length === 0) {
      throw new ValidationError('El ajuste debe tener al menos una línea');
    }

    const client = await getClient();

    try {
      await client.query('BEGIN');

      // Generate adjustment name
      const seqResult = await client.query(
        `SELECT COALESCE(MAX(CAST(SUBSTRING(name FROM 5) AS INTEGER)), 0) + 1 as next_num
         FROM inventory.inventory_adjustments WHERE tenant_id = $1 AND name LIKE 'ADJ-%'`,
        [tenantId]
      );
      const nextNum = seqResult.rows[0]?.next_num || 1;
      const adjustmentName = `ADJ-${String(nextNum).padStart(6, '0')}`;

      const adjustmentDate = dto.date || new Date().toISOString().split('T')[0];

      // Create adjustment
      const adjustmentResult = await client.query(
        `INSERT INTO inventory.inventory_adjustments (
          tenant_id, company_id, name, location_id, date, notes, created_by
        )
         VALUES ($1, $2, $3, $4, $5, $6, $7)
         RETURNING *`,
        [tenantId, dto.company_id, adjustmentName, dto.location_id, adjustmentDate, dto.notes, userId]
      );
      const adjustment = adjustmentResult.rows[0];

      // Create lines with theoretical qty from stock_quants
      for (const line of dto.lines) {
        // Get theoretical quantity from stock_quants
        const stockResult = await client.query(
          `SELECT COALESCE(SUM(quantity), 0) as qty
           FROM inventory.stock_quants
           WHERE product_id = $1 AND location_id = $2
             AND ($3::uuid IS NULL OR lot_id = $3)`,
          [line.product_id, line.location_id, line.lot_id || null]
        );
        const theoreticalQty = parseFloat(stockResult.rows[0]?.qty || '0');

        await client.query(
          `INSERT INTO inventory.inventory_adjustment_lines (
            adjustment_id, tenant_id, product_id, location_id, lot_id, theoretical_qty,
            counted_qty
          )
           VALUES ($1, $2, $3, $4, $5, $6, $7)`,
          [
            adjustment.id, tenantId, line.product_id, line.location_id, line.lot_id,
            theoreticalQty, line.counted_qty
          ]
        );
      }

      await client.query('COMMIT');

      return this.findById(adjustment.id, tenantId);
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  async update(id: string, dto: UpdateAdjustmentDto, tenantId: string, userId: string): Promise<Adjustment> {
    const existing = await this.findById(id, tenantId);

    if (existing.status !== 'draft') {
      throw new ValidationError('Solo se pueden editar ajustes en estado borrador');
    }

    const updateFields: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    if (dto.location_id !== undefined) {
      updateFields.push(`location_id = $${paramIndex++}`);
      values.push(dto.location_id);
    }
    if (dto.date !== undefined) {
      updateFields.push(`date = $${paramIndex++}`);
      values.push(dto.date);
    }
    if (dto.notes !== undefined) {
      updateFields.push(`notes = $${paramIndex++}`);
      values.push(dto.notes);
    }

    if (updateFields.length === 0) {
      return existing;
    }

    updateFields.push(`updated_by = $${paramIndex++}`);
    values.push(userId);
    updateFields.push(`updated_at = CURRENT_TIMESTAMP`);

    values.push(id, tenantId);

    await query(
      `UPDATE inventory.inventory_adjustments SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex}`,
      values
    );

    return this.findById(id, tenantId);
  }

  async addLine(adjustmentId: string, dto: CreateAdjustmentLineDto, tenantId: string): Promise<AdjustmentLine> {
    const adjustment = await this.findById(adjustmentId, tenantId);

    if (adjustment.status !== 'draft') {
      throw new ValidationError('Solo se pueden agregar líneas a ajustes en estado borrador');
    }

    // Get theoretical quantity
    const stockResult = await queryOne<{ qty: string }>(
      `SELECT COALESCE(SUM(quantity), 0) as qty
       FROM inventory.stock_quants
       WHERE product_id = $1 AND location_id = $2
         AND ($3::uuid IS NULL OR lot_id = $3)`,
      [dto.product_id, dto.location_id, dto.lot_id || null]
    );
    const theoreticalQty = parseFloat(stockResult?.qty || '0');

    const line = await queryOne<AdjustmentLine>(
      `INSERT INTO inventory.inventory_adjustment_lines (
        adjustment_id, tenant_id, product_id, location_id, lot_id, theoretical_qty,
        counted_qty
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7)
       RETURNING *`,
      [
        adjustmentId, tenantId, dto.product_id, dto.location_id, dto.lot_id,
        theoreticalQty, dto.counted_qty
      ]
    );

    return line!;
  }

  async updateLine(adjustmentId: string, lineId: string, dto: UpdateAdjustmentLineDto, tenantId: string): Promise<AdjustmentLine> {
    const adjustment = await this.findById(adjustmentId, tenantId);

    if (adjustment.status !== 'draft') {
      throw new ValidationError('Solo se pueden editar líneas en ajustes en estado borrador');
    }

    const existingLine = adjustment.lines?.find(l => l.id === lineId);
    if (!existingLine) {
      throw new NotFoundError('Línea no encontrada');
    }

    const updateFields: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    if (dto.counted_qty !== undefined) {
      updateFields.push(`counted_qty = $${paramIndex++}`);
      values.push(dto.counted_qty);
    }
    if (dto.notes !== undefined) {
      updateFields.push(`notes = $${paramIndex++}`);
      values.push(dto.notes);
    }

    if (updateFields.length === 0) {
      return existingLine;
    }

    values.push(lineId);

    const line = await queryOne<AdjustmentLine>(
      `UPDATE inventory.inventory_adjustment_lines SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex}
       RETURNING *`,
      values
    );

    return line!;
  }

  async removeLine(adjustmentId: string, lineId: string, tenantId: string): Promise<void> {
    const adjustment = await this.findById(adjustmentId, tenantId);

    if (adjustment.status !== 'draft') {
      throw new ValidationError('Solo se pueden eliminar líneas en ajustes en estado borrador');
    }

    const existingLine = adjustment.lines?.find(l => l.id === lineId);
    if (!existingLine) {
      throw new NotFoundError('Línea no encontrada');
    }

    if (adjustment.lines && adjustment.lines.length <= 1) {
      throw new ValidationError('El ajuste debe tener al menos una línea');
    }

    await query(`DELETE FROM inventory.inventory_adjustment_lines WHERE id = $1`, [lineId]);
  }

  async confirm(id: string, tenantId: string, userId: string): Promise<Adjustment> {
    const adjustment = await this.findById(id, tenantId);

    if (adjustment.status !== 'draft') {
      throw new ValidationError('Solo se pueden confirmar ajustes en estado borrador');
    }

    if (!adjustment.lines || adjustment.lines.length === 0) {
      throw new ValidationError('El ajuste debe tener al menos una línea');
    }

    await query(
      `UPDATE inventory.inventory_adjustments SET
         status = 'confirmed',
         updated_by = $1,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $2 AND tenant_id = $3`,
      [userId, id, tenantId]
    );

    return this.findById(id, tenantId);
  }

  async validate(id: string, tenantId: string, userId: string): Promise<Adjustment> {
    const adjustment = await this.findById(id, tenantId);

    if (adjustment.status !== 'confirmed') {
      throw new ValidationError('Solo se pueden validar ajustes confirmados');
    }

    const client = await getClient();

    try {
      await client.query('BEGIN');

      // Update status to done
      await client.query(
        `UPDATE inventory.inventory_adjustments SET
           status = 'done',
           updated_by = $1,
           updated_at = CURRENT_TIMESTAMP
         WHERE id = $2 AND tenant_id = $3`,
        [userId, id, tenantId]
      );

      // Apply stock adjustments
      for (const line of adjustment.lines!) {
        const difference = line.counted_qty - line.theoretical_qty;

        if (difference !== 0) {
          // Check if quant exists
          const existingQuant = await client.query(
            `SELECT id, quantity FROM inventory.stock_quants
             WHERE product_id = $1 AND location_id = $2
               AND ($3::uuid IS NULL OR lot_id = $3)`,
            [line.product_id, line.location_id, line.lot_id || null]
          );

          if (existingQuant.rows.length > 0) {
            // Update existing quant
            await client.query(
              `UPDATE inventory.stock_quants SET
                 quantity = $1,
                 updated_at = CURRENT_TIMESTAMP
               WHERE id = $2`,
              [line.counted_qty, existingQuant.rows[0].id]
            );
          } else if (line.counted_qty > 0) {
            // Create new quant if counted > 0
            await client.query(
              `INSERT INTO inventory.stock_quants (
                tenant_id, product_id, location_id, lot_id, quantity
              )
               VALUES ($1, $2, $3, $4, $5)`,
              [tenantId, line.product_id, line.location_id, line.lot_id, line.counted_qty]
            );
          }
        }
      }

      await client.query('COMMIT');

      return this.findById(id, tenantId);
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  async cancel(id: string, tenantId: string, userId: string): Promise<Adjustment> {
    const adjustment = await this.findById(id, tenantId);

    if (adjustment.status === 'done') {
      throw new ValidationError('No se puede cancelar un ajuste validado');
    }

    if (adjustment.status === 'cancelled') {
      throw new ValidationError('El ajuste ya está cancelado');
    }

    await query(
      `UPDATE inventory.inventory_adjustments SET
         status = 'cancelled',
         updated_by = $1,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $2 AND tenant_id = $3`,
      [userId, id, tenantId]
    );

    return this.findById(id, tenantId);
  }

  async delete(id: string, tenantId: string): Promise<void> {
    const adjustment = await this.findById(id, tenantId);

    if (adjustment.status !== 'draft') {
      throw new ValidationError('Solo se pueden eliminar ajustes en estado borrador');
    }

    await query(`DELETE FROM inventory.inventory_adjustments WHERE id = $1 AND tenant_id = $2`, [id, tenantId]);
  }
}

export const adjustmentsService = new AdjustmentsService();
