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

export type RfqStatus = 'draft' | 'sent' | 'responded' | 'accepted' | 'rejected' | 'cancelled';

export interface RfqLine {
  id: string;
  rfq_id: string;
  product_id?: string;
  product_name?: string;
  product_code?: string;
  description: string;
  quantity: number;
  uom_id: string;
  uom_name?: string;
  created_at: Date;
}

export interface Rfq {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  name: string;
  partner_ids: string[];
  partner_names?: string[];
  request_date: Date;
  deadline_date?: Date;
  response_date?: Date;
  status: RfqStatus;
  description?: string;
  notes?: string;
  lines?: RfqLine[];
  created_at: Date;
}

export interface CreateRfqLineDto {
  product_id?: string;
  description: string;
  quantity: number;
  uom_id: string;
}

export interface CreateRfqDto {
  company_id: string;
  partner_ids: string[];
  request_date?: string;
  deadline_date?: string;
  description?: string;
  notes?: string;
  lines: CreateRfqLineDto[];
}

export interface UpdateRfqDto {
  partner_ids?: string[];
  deadline_date?: string | null;
  description?: string | null;
  notes?: string | null;
}

export interface UpdateRfqLineDto {
  product_id?: string | null;
  description?: string;
  quantity?: number;
  uom_id?: string;
}

export interface RfqFilters {
  company_id?: string;
  status?: RfqStatus;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;
  limit?: number;
}

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

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

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

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

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

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

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

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

    params.push(limit, offset);
    const data = await query<Rfq>(
      `SELECT r.*,
              c.name as company_name
       FROM purchase.rfqs r
       LEFT JOIN auth.companies c ON r.company_id = c.id
       ${whereClause}
       ORDER BY r.request_date DESC, r.created_at DESC
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

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

  async findById(id: string, tenantId: string): Promise<Rfq> {
    const rfq = await queryOne<Rfq>(
      `SELECT r.*,
              c.name as company_name
       FROM purchase.rfqs r
       LEFT JOIN auth.companies c ON r.company_id = c.id
       WHERE r.id = $1 AND r.tenant_id = $2`,
      [id, tenantId]
    );

    if (!rfq) {
      throw new NotFoundError('Solicitud de cotización no encontrada');
    }

    // Get partner names
    if (rfq.partner_ids && rfq.partner_ids.length > 0) {
      const partners = await query<{ id: string; name: string }>(
        `SELECT id, name FROM core.partners WHERE id = ANY($1)`,
        [rfq.partner_ids]
      );
      rfq.partner_names = partners.map(p => p.name);
    }

    // Get lines
    const lines = await query<RfqLine>(
      `SELECT rl.*,
              pr.name as product_name,
              pr.code as product_code,
              u.name as uom_name
       FROM purchase.rfq_lines rl
       LEFT JOIN inventory.products pr ON rl.product_id = pr.id
       LEFT JOIN core.uom u ON rl.uom_id = u.id
       WHERE rl.rfq_id = $1
       ORDER BY rl.created_at`,
      [id]
    );

    rfq.lines = lines;

    return rfq;
  }

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

    if (dto.partner_ids.length === 0) {
      throw new ValidationError('Debe especificar al menos un proveedor');
    }

    const client = await getClient();

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

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

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

      // Create RFQ
      const rfqResult = await client.query(
        `INSERT INTO purchase.rfqs (
          tenant_id, company_id, name, partner_ids, request_date, deadline_date,
          description, notes, created_by
        )
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
         RETURNING *`,
        [
          tenantId, dto.company_id, rfqName, dto.partner_ids, requestDate,
          dto.deadline_date, dto.description, dto.notes, userId
        ]
      );
      const rfq = rfqResult.rows[0];

      // Create lines
      for (const line of dto.lines) {
        await client.query(
          `INSERT INTO purchase.rfq_lines (rfq_id, tenant_id, product_id, description, quantity, uom_id)
           VALUES ($1, $2, $3, $4, $5, $6)`,
          [rfq.id, tenantId, line.product_id, line.description, line.quantity, line.uom_id]
        );
      }

      await client.query('COMMIT');

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

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

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

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

    if (dto.partner_ids !== undefined) {
      updateFields.push(`partner_ids = $${paramIndex++}`);
      values.push(dto.partner_ids);
    }
    if (dto.deadline_date !== undefined) {
      updateFields.push(`deadline_date = $${paramIndex++}`);
      values.push(dto.deadline_date);
    }
    if (dto.description !== undefined) {
      updateFields.push(`description = $${paramIndex++}`);
      values.push(dto.description);
    }
    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 purchase.rfqs SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex}`,
      values
    );

    return this.findById(id, tenantId);
  }

  async addLine(rfqId: string, dto: CreateRfqLineDto, tenantId: string): Promise<RfqLine> {
    const rfq = await this.findById(rfqId, tenantId);

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

    const line = await queryOne<RfqLine>(
      `INSERT INTO purchase.rfq_lines (rfq_id, tenant_id, product_id, description, quantity, uom_id)
       VALUES ($1, $2, $3, $4, $5, $6)
       RETURNING *`,
      [rfqId, tenantId, dto.product_id, dto.description, dto.quantity, dto.uom_id]
    );

    return line!;
  }

  async updateLine(rfqId: string, lineId: string, dto: UpdateRfqLineDto, tenantId: string): Promise<RfqLine> {
    const rfq = await this.findById(rfqId, tenantId);

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

    const existingLine = rfq.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.product_id !== undefined) {
      updateFields.push(`product_id = $${paramIndex++}`);
      values.push(dto.product_id);
    }
    if (dto.description !== undefined) {
      updateFields.push(`description = $${paramIndex++}`);
      values.push(dto.description);
    }
    if (dto.quantity !== undefined) {
      updateFields.push(`quantity = $${paramIndex++}`);
      values.push(dto.quantity);
    }
    if (dto.uom_id !== undefined) {
      updateFields.push(`uom_id = $${paramIndex++}`);
      values.push(dto.uom_id);
    }

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

    values.push(lineId);

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

    return line!;
  }

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

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

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

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

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

  async send(id: string, tenantId: string, userId: string): Promise<Rfq> {
    const rfq = await this.findById(id, tenantId);

    if (rfq.status !== 'draft') {
      throw new ValidationError('Solo se pueden enviar solicitudes en estado borrador');
    }

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

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

    return this.findById(id, tenantId);
  }

  async markResponded(id: string, tenantId: string, userId: string): Promise<Rfq> {
    const rfq = await this.findById(id, tenantId);

    if (rfq.status !== 'sent') {
      throw new ValidationError('Solo se pueden marcar como respondidas solicitudes enviadas');
    }

    await query(
      `UPDATE purchase.rfqs SET
         status = 'responded',
         response_date = CURRENT_DATE,
         updated_by = $1,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $2 AND tenant_id = $3`,
      [userId, id, tenantId]
    );

    return this.findById(id, tenantId);
  }

  async accept(id: string, tenantId: string, userId: string): Promise<Rfq> {
    const rfq = await this.findById(id, tenantId);

    if (rfq.status !== 'responded' && rfq.status !== 'sent') {
      throw new ValidationError('Solo se pueden aceptar solicitudes enviadas o respondidas');
    }

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

    return this.findById(id, tenantId);
  }

  async reject(id: string, tenantId: string, userId: string): Promise<Rfq> {
    const rfq = await this.findById(id, tenantId);

    if (rfq.status !== 'responded' && rfq.status !== 'sent') {
      throw new ValidationError('Solo se pueden rechazar solicitudes enviadas o respondidas');
    }

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

    return this.findById(id, tenantId);
  }

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

    if (rfq.status === 'cancelled') {
      throw new ValidationError('La solicitud ya está cancelada');
    }

    if (rfq.status === 'accepted') {
      throw new ValidationError('No se puede cancelar una solicitud aceptada');
    }

    await query(
      `UPDATE purchase.rfqs 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 rfq = await this.findById(id, tenantId);

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

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

export const rfqsService = new RfqsService();
