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

export interface PaymentInvoice {
  invoice_id: string;
  invoice_number?: string;
  amount: number;
}

export interface Payment {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  partner_id: string;
  partner_name?: string;
  payment_type: 'inbound' | 'outbound';
  payment_method: 'cash' | 'bank_transfer' | 'check' | 'card' | 'other';
  amount: number;
  currency_id: string;
  currency_code?: string;
  payment_date: Date;
  ref?: string;
  status: 'draft' | 'posted' | 'reconciled' | 'cancelled';
  journal_id: string;
  journal_name?: string;
  journal_entry_id?: string;
  notes?: string;
  invoices?: PaymentInvoice[];
  created_at: Date;
  posted_at?: Date;
}

export interface CreatePaymentDto {
  company_id: string;
  partner_id: string;
  payment_type: 'inbound' | 'outbound';
  payment_method: 'cash' | 'bank_transfer' | 'check' | 'card' | 'other';
  amount: number;
  currency_id: string;
  payment_date?: string;
  ref?: string;
  journal_id: string;
  notes?: string;
}

export interface UpdatePaymentDto {
  partner_id?: string;
  payment_method?: 'cash' | 'bank_transfer' | 'check' | 'card' | 'other';
  amount?: number;
  currency_id?: string;
  payment_date?: string;
  ref?: string | null;
  journal_id?: string;
  notes?: string | null;
}

export interface ReconcileDto {
  invoices: { invoice_id: string; amount: number }[];
}

export interface PaymentFilters {
  company_id?: string;
  partner_id?: string;
  payment_type?: string;
  payment_method?: string;
  status?: string;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;
  limit?: number;
}

class PaymentsService {
  async findAll(tenantId: string, filters: PaymentFilters = {}): Promise<{ data: Payment[]; total: number }> {
    const { company_id, partner_id, payment_type, payment_method, status, date_from, date_to, search, page = 1, limit = 20 } = filters;
    const offset = (page - 1) * limit;

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

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

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

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

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

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

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

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

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

    const countResult = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count
       FROM financial.payments p
       LEFT JOIN core.partners pr ON p.partner_id = pr.id
       ${whereClause}`,
      params
    );

    params.push(limit, offset);
    const data = await query<Payment>(
      `SELECT p.*,
              c.name as company_name,
              pr.name as partner_name,
              cu.code as currency_code,
              j.name as journal_name
       FROM financial.payments p
       LEFT JOIN auth.companies c ON p.company_id = c.id
       LEFT JOIN core.partners pr ON p.partner_id = pr.id
       LEFT JOIN core.currencies cu ON p.currency_id = cu.id
       LEFT JOIN financial.journals j ON p.journal_id = j.id
       ${whereClause}
       ORDER BY p.payment_date DESC, p.created_at DESC
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

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

  async findById(id: string, tenantId: string): Promise<Payment> {
    const payment = await queryOne<Payment>(
      `SELECT p.*,
              c.name as company_name,
              pr.name as partner_name,
              cu.code as currency_code,
              j.name as journal_name
       FROM financial.payments p
       LEFT JOIN auth.companies c ON p.company_id = c.id
       LEFT JOIN core.partners pr ON p.partner_id = pr.id
       LEFT JOIN core.currencies cu ON p.currency_id = cu.id
       LEFT JOIN financial.journals j ON p.journal_id = j.id
       WHERE p.id = $1 AND p.tenant_id = $2`,
      [id, tenantId]
    );

    if (!payment) {
      throw new NotFoundError('Pago no encontrado');
    }

    // Get reconciled invoices
    const invoices = await query<PaymentInvoice>(
      `SELECT pi.invoice_id, pi.amount, i.number as invoice_number
       FROM financial.payment_invoice pi
       LEFT JOIN financial.invoices i ON pi.invoice_id = i.id
       WHERE pi.payment_id = $1`,
      [id]
    );

    payment.invoices = invoices;

    return payment;
  }

  async create(dto: CreatePaymentDto, tenantId: string, userId: string): Promise<Payment> {
    if (dto.amount <= 0) {
      throw new ValidationError('El monto debe ser mayor a 0');
    }

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

    const payment = await queryOne<Payment>(
      `INSERT INTO financial.payments (
        tenant_id, company_id, partner_id, payment_type, payment_method,
        amount, currency_id, payment_date, ref, journal_id, notes, created_by
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
       RETURNING *`,
      [
        tenantId, dto.company_id, dto.partner_id, dto.payment_type, dto.payment_method,
        dto.amount, dto.currency_id, paymentDate, dto.ref, dto.journal_id, dto.notes, userId
      ]
    );

    return payment!;
  }

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

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

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

    if (dto.partner_id !== undefined) {
      updateFields.push(`partner_id = $${paramIndex++}`);
      values.push(dto.partner_id);
    }
    if (dto.payment_method !== undefined) {
      updateFields.push(`payment_method = $${paramIndex++}`);
      values.push(dto.payment_method);
    }
    if (dto.amount !== undefined) {
      if (dto.amount <= 0) {
        throw new ValidationError('El monto debe ser mayor a 0');
      }
      updateFields.push(`amount = $${paramIndex++}`);
      values.push(dto.amount);
    }
    if (dto.currency_id !== undefined) {
      updateFields.push(`currency_id = $${paramIndex++}`);
      values.push(dto.currency_id);
    }
    if (dto.payment_date !== undefined) {
      updateFields.push(`payment_date = $${paramIndex++}`);
      values.push(dto.payment_date);
    }
    if (dto.ref !== undefined) {
      updateFields.push(`ref = $${paramIndex++}`);
      values.push(dto.ref);
    }
    if (dto.journal_id !== undefined) {
      updateFields.push(`journal_id = $${paramIndex++}`);
      values.push(dto.journal_id);
    }
    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 financial.payments SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex}`,
      values
    );

    return this.findById(id, tenantId);
  }

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

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

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

  async post(id: string, tenantId: string, userId: string): Promise<Payment> {
    const payment = await this.findById(id, tenantId);

    if (payment.status !== 'draft') {
      throw new ValidationError('Solo se pueden publicar pagos en estado borrador');
    }

    await query(
      `UPDATE financial.payments SET
         status = 'posted',
         posted_at = CURRENT_TIMESTAMP,
         posted_by = $1,
         updated_by = $1,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $2 AND tenant_id = $3`,
      [userId, id, tenantId]
    );

    return this.findById(id, tenantId);
  }

  async reconcile(id: string, dto: ReconcileDto, tenantId: string, userId: string): Promise<Payment> {
    const payment = await this.findById(id, tenantId);

    if (payment.status === 'draft') {
      throw new ValidationError('Debe publicar el pago antes de conciliar');
    }

    if (payment.status === 'cancelled') {
      throw new ValidationError('No se puede conciliar un pago cancelado');
    }

    // Validate total amount matches
    const totalReconciled = dto.invoices.reduce((sum, inv) => sum + inv.amount, 0);
    if (totalReconciled > payment.amount) {
      throw new ValidationError('El monto total conciliado excede el monto del pago');
    }

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

      // Remove existing reconciliations
      await client.query(
        `DELETE FROM financial.payment_invoice WHERE payment_id = $1`,
        [id]
      );

      // Add new reconciliations
      for (const inv of dto.invoices) {
        // Validate invoice exists and belongs to same partner
        const invoice = await client.query(
          `SELECT id, partner_id, amount_residual, status FROM financial.invoices
           WHERE id = $1 AND tenant_id = $2`,
          [inv.invoice_id, tenantId]
        );

        if (invoice.rows.length === 0) {
          throw new ValidationError(`Factura ${inv.invoice_id} no encontrada`);
        }

        if (invoice.rows[0].partner_id !== payment.partner_id) {
          throw new ValidationError('La factura debe pertenecer al mismo cliente/proveedor');
        }

        if (invoice.rows[0].status !== 'open') {
          throw new ValidationError('Solo se pueden conciliar facturas abiertas');
        }

        if (inv.amount > invoice.rows[0].amount_residual) {
          throw new ValidationError(`El monto excede el saldo pendiente de la factura`);
        }

        await client.query(
          `INSERT INTO financial.payment_invoice (payment_id, invoice_id, amount)
           VALUES ($1, $2, $3)`,
          [id, inv.invoice_id, inv.amount]
        );

        // Update invoice amounts
        await client.query(
          `UPDATE financial.invoices SET
             amount_paid = amount_paid + $1,
             amount_residual = amount_residual - $1,
             status = CASE WHEN amount_residual - $1 <= 0 THEN 'paid'::financial.invoice_status ELSE status END
           WHERE id = $2`,
          [inv.amount, inv.invoice_id]
        );
      }

      // Update payment status
      await client.query(
        `UPDATE financial.payments SET
           status = 'reconciled',
           updated_by = $1,
           updated_at = CURRENT_TIMESTAMP
         WHERE id = $2`,
        [userId, id]
      );

      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<Payment> {
    const payment = await this.findById(id, tenantId);

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

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

      // Reverse reconciliations if any
      if (payment.invoices && payment.invoices.length > 0) {
        for (const inv of payment.invoices) {
          await client.query(
            `UPDATE financial.invoices SET
               amount_paid = amount_paid - $1,
               amount_residual = amount_residual + $1,
               status = 'open'::financial.invoice_status
             WHERE id = $2`,
            [inv.amount, inv.invoice_id]
          );
        }

        await client.query(
          `DELETE FROM financial.payment_invoice WHERE payment_id = $1`,
          [id]
        );
      }

      // Cancel payment
      await client.query(
        `UPDATE financial.payments SET
           status = 'cancelled',
           updated_by = $1,
           updated_at = CURRENT_TIMESTAMP
         WHERE id = $2`,
        [userId, id]
      );

      await client.query('COMMIT');

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

export const paymentsService = new PaymentsService();
