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

export interface InvoiceLine {
  id: string;
  invoice_id: string;
  product_id?: string;
  product_name?: string;
  description: string;
  quantity: number;
  uom_id?: string;
  uom_name?: string;
  price_unit: number;
  tax_ids: string[];
  amount_untaxed: number;
  amount_tax: number;
  amount_total: number;
  account_id?: string;
  account_name?: string;
}

export interface Invoice {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  partner_id: string;
  partner_name?: string;
  invoice_type: 'customer' | 'supplier';
  number?: string;
  ref?: string;
  invoice_date: Date;
  due_date?: Date;
  currency_id: string;
  currency_code?: string;
  amount_untaxed: number;
  amount_tax: number;
  amount_total: number;
  amount_paid: number;
  amount_residual: number;
  status: 'draft' | 'open' | 'paid' | 'cancelled';
  payment_term_id?: string;
  journal_id?: string;
  journal_entry_id?: string;
  notes?: string;
  lines?: InvoiceLine[];
  created_at: Date;
  validated_at?: Date;
}

export interface CreateInvoiceDto {
  company_id: string;
  partner_id: string;
  invoice_type: 'customer' | 'supplier';
  ref?: string;
  invoice_date?: string;
  due_date?: string;
  currency_id: string;
  payment_term_id?: string;
  journal_id?: string;
  notes?: string;
}

export interface UpdateInvoiceDto {
  partner_id?: string;
  ref?: string | null;
  invoice_date?: string;
  due_date?: string | null;
  currency_id?: string;
  payment_term_id?: string | null;
  journal_id?: string | null;
  notes?: string | null;
}

export interface CreateInvoiceLineDto {
  product_id?: string;
  description: string;
  quantity: number;
  uom_id?: string;
  price_unit: number;
  tax_ids?: string[];
  account_id?: string;
}

export interface UpdateInvoiceLineDto {
  product_id?: string | null;
  description?: string;
  quantity?: number;
  uom_id?: string | null;
  price_unit?: number;
  tax_ids?: string[];
  account_id?: string | null;
}

export interface InvoiceFilters {
  company_id?: string;
  partner_id?: string;
  invoice_type?: string;
  status?: string;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;
  limit?: number;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

    if (!invoice) {
      throw new NotFoundError('Factura no encontrada');
    }

    // Get lines
    const lines = await query<InvoiceLine>(
      `SELECT il.*,
              pr.name as product_name,
              um.name as uom_name,
              a.name as account_name
       FROM financial.invoice_lines il
       LEFT JOIN inventory.products pr ON il.product_id = pr.id
       LEFT JOIN core.uom um ON il.uom_id = um.id
       LEFT JOIN financial.accounts a ON il.account_id = a.id
       WHERE il.invoice_id = $1
       ORDER BY il.created_at`,
      [id]
    );

    invoice.lines = lines;

    return invoice;
  }

  async create(dto: CreateInvoiceDto, tenantId: string, userId: string): Promise<Invoice> {
    const invoiceDate = dto.invoice_date || new Date().toISOString().split('T')[0];

    const invoice = await queryOne<Invoice>(
      `INSERT INTO financial.invoices (
        tenant_id, company_id, partner_id, invoice_type, ref, invoice_date,
        due_date, currency_id, payment_term_id, journal_id, notes,
        amount_untaxed, amount_tax, amount_total, amount_paid, amount_residual, created_by
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 0, 0, 0, 0, 0, $12)
       RETURNING *`,
      [
        tenantId, dto.company_id, dto.partner_id, dto.invoice_type, dto.ref,
        invoiceDate, dto.due_date, dto.currency_id, dto.payment_term_id,
        dto.journal_id, dto.notes, userId
      ]
    );

    return invoice!;
  }

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

    if (existing.status !== 'draft') {
      throw new ValidationError('Solo se pueden editar facturas 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.ref !== undefined) {
      updateFields.push(`ref = $${paramIndex++}`);
      values.push(dto.ref);
    }
    if (dto.invoice_date !== undefined) {
      updateFields.push(`invoice_date = $${paramIndex++}`);
      values.push(dto.invoice_date);
    }
    if (dto.due_date !== undefined) {
      updateFields.push(`due_date = $${paramIndex++}`);
      values.push(dto.due_date);
    }
    if (dto.currency_id !== undefined) {
      updateFields.push(`currency_id = $${paramIndex++}`);
      values.push(dto.currency_id);
    }
    if (dto.payment_term_id !== undefined) {
      updateFields.push(`payment_term_id = $${paramIndex++}`);
      values.push(dto.payment_term_id);
    }
    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.invoices 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 facturas en estado borrador');
    }

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

  async addLine(invoiceId: string, dto: CreateInvoiceLineDto, tenantId: string): Promise<InvoiceLine> {
    const invoice = await this.findById(invoiceId, tenantId);

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

    // Calculate amounts with taxes using taxesService
    // Determine transaction type based on invoice type
    const transactionType = invoice.invoice_type === 'customer'
      ? 'sales'
      : 'purchase';

    const taxResult = await taxesService.calculateTaxes(
      {
        quantity: dto.quantity,
        priceUnit: dto.price_unit,
        discount: 0, // Invoices don't have line discounts by default
        taxIds: dto.tax_ids || [],
      },
      tenantId,
      transactionType
    );
    const amountUntaxed = taxResult.amountUntaxed;
    const amountTax = taxResult.amountTax;
    const amountTotal = taxResult.amountTotal;

    const line = await queryOne<InvoiceLine>(
      `INSERT INTO financial.invoice_lines (
        invoice_id, tenant_id, product_id, description, quantity, uom_id,
        price_unit, tax_ids, amount_untaxed, amount_tax, amount_total, account_id
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
       RETURNING *`,
      [
        invoiceId, tenantId, dto.product_id, dto.description, dto.quantity, dto.uom_id,
        dto.price_unit, dto.tax_ids || [], amountUntaxed, amountTax, amountTotal, dto.account_id
      ]
    );

    // Update invoice totals
    await this.updateTotals(invoiceId);

    return line!;
  }

  async updateLine(invoiceId: string, lineId: string, dto: UpdateInvoiceLineDto, tenantId: string): Promise<InvoiceLine> {
    const invoice = await this.findById(invoiceId, tenantId);

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

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

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

    const quantity = dto.quantity ?? existingLine.quantity;
    const priceUnit = dto.price_unit ?? existingLine.price_unit;

    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 (dto.price_unit !== undefined) {
      updateFields.push(`price_unit = $${paramIndex++}`);
      values.push(dto.price_unit);
    }
    if (dto.tax_ids !== undefined) {
      updateFields.push(`tax_ids = $${paramIndex++}`);
      values.push(dto.tax_ids);
    }
    if (dto.account_id !== undefined) {
      updateFields.push(`account_id = $${paramIndex++}`);
      values.push(dto.account_id);
    }

    // Recalculate amounts
    const amountUntaxed = quantity * priceUnit;
    const amountTax = 0; // TODO: Calculate taxes
    const amountTotal = amountUntaxed + amountTax;

    updateFields.push(`amount_untaxed = $${paramIndex++}`);
    values.push(amountUntaxed);
    updateFields.push(`amount_tax = $${paramIndex++}`);
    values.push(amountTax);
    updateFields.push(`amount_total = $${paramIndex++}`);
    values.push(amountTotal);
    updateFields.push(`updated_at = CURRENT_TIMESTAMP`);

    values.push(lineId, invoiceId);

    await query(
      `UPDATE financial.invoice_lines SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND invoice_id = $${paramIndex}`,
      values
    );

    // Update invoice totals
    await this.updateTotals(invoiceId);

    const updated = await queryOne<InvoiceLine>(
      `SELECT * FROM financial.invoice_lines WHERE id = $1`,
      [lineId]
    );

    return updated!;
  }

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

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

    await query(
      `DELETE FROM financial.invoice_lines WHERE id = $1 AND invoice_id = $2`,
      [lineId, invoiceId]
    );

    // Update invoice totals
    await this.updateTotals(invoiceId);
  }

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

    if (invoice.status !== 'draft') {
      throw new ValidationError('Solo se pueden validar facturas en estado borrador');
    }

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

    // Generate invoice number
    const prefix = invoice.invoice_type === 'customer' ? 'INV' : 'BILL';
    const seqResult = await queryOne<{ next_num: number }>(
      `SELECT COALESCE(MAX(CAST(SUBSTRING(number FROM 5) AS INTEGER)), 0) + 1 as next_num
       FROM financial.invoices WHERE tenant_id = $1 AND number LIKE '${prefix}-%'`,
      [tenantId]
    );
    const invoiceNumber = `${prefix}-${String(seqResult?.next_num || 1).padStart(6, '0')}`;

    await query(
      `UPDATE financial.invoices SET
         number = $1,
         status = 'open',
         amount_residual = amount_total,
         validated_at = CURRENT_TIMESTAMP,
         validated_by = $2,
         updated_by = $2,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $3 AND tenant_id = $4`,
      [invoiceNumber, userId, id, tenantId]
    );

    return this.findById(id, tenantId);
  }

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

    if (invoice.status === 'paid') {
      throw new ValidationError('No se pueden cancelar facturas pagadas');
    }

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

    if (invoice.amount_paid > 0) {
      throw new ValidationError('No se puede cancelar: la factura tiene pagos asociados');
    }

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

    return this.findById(id, tenantId);
  }

  private async updateTotals(invoiceId: string): Promise<void> {
    const totals = await queryOne<{ amount_untaxed: number; amount_tax: number; amount_total: number }>(
      `SELECT
         COALESCE(SUM(amount_untaxed), 0) as amount_untaxed,
         COALESCE(SUM(amount_tax), 0) as amount_tax,
         COALESCE(SUM(amount_total), 0) as amount_total
       FROM financial.invoice_lines WHERE invoice_id = $1`,
      [invoiceId]
    );

    await query(
      `UPDATE financial.invoices SET
         amount_untaxed = $1,
         amount_tax = $2,
         amount_total = $3,
         amount_residual = $3 - amount_paid
       WHERE id = $4`,
      [totals?.amount_untaxed || 0, totals?.amount_tax || 0, totals?.amount_total || 0, invoiceId]
    );
  }
}

export const invoicesService = new InvoicesService();
