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

export interface Lot {
  id: string;
  tenant_id: string;
  product_id: string;
  product_name?: string;
  product_code?: string;
  name: string;
  ref?: string;
  manufacture_date?: Date;
  expiration_date?: Date;
  removal_date?: Date;
  alert_date?: Date;
  notes?: string;
  created_at: Date;
  quantity_on_hand?: number;
}

export interface CreateLotDto {
  product_id: string;
  name: string;
  ref?: string;
  manufacture_date?: string;
  expiration_date?: string;
  removal_date?: string;
  alert_date?: string;
  notes?: string;
}

export interface UpdateLotDto {
  ref?: string | null;
  manufacture_date?: string | null;
  expiration_date?: string | null;
  removal_date?: string | null;
  alert_date?: string | null;
  notes?: string | null;
}

export interface LotFilters {
  product_id?: string;
  expiring_soon?: boolean;
  expired?: boolean;
  search?: string;
  page?: number;
  limit?: number;
}

export interface LotMovement {
  id: string;
  date: Date;
  origin: string;
  location_from: string;
  location_to: string;
  quantity: number;
  status: string;
}

class LotsService {
  async findAll(tenantId: string, filters: LotFilters = {}): Promise<{ data: Lot[]; total: number }> {
    const { product_id, expiring_soon, expired, search, page = 1, limit = 50 } = filters;
    const offset = (page - 1) * limit;

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

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

    if (expiring_soon) {
      whereClause += ` AND l.expiration_date IS NOT NULL AND l.expiration_date <= CURRENT_DATE + INTERVAL '30 days' AND l.expiration_date > CURRENT_DATE`;
    }

    if (expired) {
      whereClause += ` AND l.expiration_date IS NOT NULL AND l.expiration_date < CURRENT_DATE`;
    }

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

    const countResult = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count
       FROM inventory.lots l
       LEFT JOIN inventory.products p ON l.product_id = p.id
       ${whereClause}`,
      params
    );

    params.push(limit, offset);
    const data = await query<Lot>(
      `SELECT l.*,
              p.name as product_name,
              p.code as product_code,
              COALESCE(sq.total_qty, 0) as quantity_on_hand
       FROM inventory.lots l
       LEFT JOIN inventory.products p ON l.product_id = p.id
       LEFT JOIN (
         SELECT lot_id, SUM(quantity) as total_qty
         FROM inventory.stock_quants
         GROUP BY lot_id
       ) sq ON l.id = sq.lot_id
       ${whereClause}
       ORDER BY l.expiration_date ASC NULLS LAST, l.created_at DESC
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

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

  async findById(id: string, tenantId: string): Promise<Lot> {
    const lot = await queryOne<Lot>(
      `SELECT l.*,
              p.name as product_name,
              p.code as product_code,
              COALESCE(sq.total_qty, 0) as quantity_on_hand
       FROM inventory.lots l
       LEFT JOIN inventory.products p ON l.product_id = p.id
       LEFT JOIN (
         SELECT lot_id, SUM(quantity) as total_qty
         FROM inventory.stock_quants
         GROUP BY lot_id
       ) sq ON l.id = sq.lot_id
       WHERE l.id = $1 AND l.tenant_id = $2`,
      [id, tenantId]
    );

    if (!lot) {
      throw new NotFoundError('Lote no encontrado');
    }

    return lot;
  }

  async create(dto: CreateLotDto, tenantId: string, userId: string): Promise<Lot> {
    // Check for unique lot name for product
    const existing = await queryOne(
      `SELECT id FROM inventory.lots WHERE product_id = $1 AND name = $2`,
      [dto.product_id, dto.name]
    );

    if (existing) {
      throw new ConflictError('Ya existe un lote con ese nombre para este producto');
    }

    const lot = await queryOne<Lot>(
      `INSERT INTO inventory.lots (
        tenant_id, product_id, name, ref, manufacture_date, expiration_date,
        removal_date, alert_date, notes, created_by
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
       RETURNING *`,
      [
        tenantId, dto.product_id, dto.name, dto.ref, dto.manufacture_date,
        dto.expiration_date, dto.removal_date, dto.alert_date, dto.notes, userId
      ]
    );

    return this.findById(lot!.id, tenantId);
  }

  async update(id: string, dto: UpdateLotDto, tenantId: string): Promise<Lot> {
    await this.findById(id, tenantId);

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

    if (dto.ref !== undefined) {
      updateFields.push(`ref = $${paramIndex++}`);
      values.push(dto.ref);
    }
    if (dto.manufacture_date !== undefined) {
      updateFields.push(`manufacture_date = $${paramIndex++}`);
      values.push(dto.manufacture_date);
    }
    if (dto.expiration_date !== undefined) {
      updateFields.push(`expiration_date = $${paramIndex++}`);
      values.push(dto.expiration_date);
    }
    if (dto.removal_date !== undefined) {
      updateFields.push(`removal_date = $${paramIndex++}`);
      values.push(dto.removal_date);
    }
    if (dto.alert_date !== undefined) {
      updateFields.push(`alert_date = $${paramIndex++}`);
      values.push(dto.alert_date);
    }
    if (dto.notes !== undefined) {
      updateFields.push(`notes = $${paramIndex++}`);
      values.push(dto.notes);
    }

    if (updateFields.length === 0) {
      return this.findById(id, tenantId);
    }

    values.push(id, tenantId);

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

    return this.findById(id, tenantId);
  }

  async getMovements(id: string, tenantId: string): Promise<LotMovement[]> {
    await this.findById(id, tenantId);

    const movements = await query<LotMovement>(
      `SELECT sm.id,
              sm.date,
              sm.origin,
              lo.name as location_from,
              ld.name as location_to,
              sm.quantity_done as quantity,
              sm.status
       FROM inventory.stock_moves sm
       LEFT JOIN inventory.locations lo ON sm.location_id = lo.id
       LEFT JOIN inventory.locations ld ON sm.location_dest_id = ld.id
       WHERE sm.lot_id = $1 AND sm.status = 'done'
       ORDER BY sm.date DESC`,
      [id]
    );

    return movements;
  }

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

    // Check if lot has stock
    if (lot.quantity_on_hand && lot.quantity_on_hand > 0) {
      throw new ConflictError('No se puede eliminar un lote con stock');
    }

    // Check if lot is used in moves
    const movesCheck = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM inventory.stock_moves WHERE lot_id = $1`,
      [id]
    );

    if (parseInt(movesCheck?.count || '0') > 0) {
      throw new ConflictError('No se puede eliminar: el lote tiene movimientos asociados');
    }

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

export const lotsService = new LotsService();
