import { Repository } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Invoice, InvoiceLine, InvoiceType, InvoiceStatus } from './entities/index.js';
import { NotFoundError, ValidationError } from '../../shared/errors/index.js';
import { logger } from '../../shared/utils/logger.js';
import { taxesService } from './taxes.service.js';

// ===== Interfaces =====

export interface InvoiceLineWithRelations extends Omit<InvoiceLine, 'invoice' | 'account'> {
  productName?: string | null;
  uomName?: string | null;
  accountName?: string | null;
}

export interface InvoiceWithRelations extends Omit<Invoice, 'lines' | 'company' | 'journal' | 'journalEntry'> {
  companyName?: string | null;
  partnerName?: string | null;
  currencyCode?: string | null;
  lines?: InvoiceLineWithRelations[];
}

export interface CreateInvoiceDto {
  companyId: string;
  partnerId: string;
  invoiceType: 'customer' | 'supplier';
  ref?: string;
  invoiceDate?: string;
  dueDate?: string;
  currencyId: string;
  paymentTermId?: string;
  journalId?: string;
  notes?: string;
}

export interface UpdateInvoiceDto {
  partnerId?: string;
  ref?: string | null;
  invoiceDate?: string;
  dueDate?: string | null;
  currencyId?: string;
  paymentTermId?: string | null;
  journalId?: string | null;
  notes?: string | null;
}

export interface CreateInvoiceLineDto {
  productId?: string;
  description: string;
  quantity: number;
  uomId?: string;
  priceUnit: number;
  taxIds?: string[];
  accountId?: string;
}

export interface UpdateInvoiceLineDto {
  productId?: string | null;
  description?: string;
  quantity?: number;
  uomId?: string | null;
  priceUnit?: number;
  taxIds?: string[];
  accountId?: string | null;
}

export interface InvoiceFilters {
  companyId?: string;
  partnerId?: string;
  invoiceType?: string;
  status?: string;
  dateFrom?: string;
  dateTo?: string;
  search?: string;
  page?: number;
  limit?: number;
}

// ===== InvoicesService Class =====

class InvoicesService {
  private invoiceRepository: Repository<Invoice>;
  private lineRepository: Repository<InvoiceLine>;

  constructor() {
    this.invoiceRepository = AppDataSource.getRepository(Invoice);
    this.lineRepository = AppDataSource.getRepository(InvoiceLine);
  }

  /**
   * Get all invoices with filters and pagination
   */
  async findAll(
    tenantId: string,
    filters: InvoiceFilters = {}
  ): Promise<{ data: InvoiceWithRelations[]; total: number }> {
    try {
      const {
        companyId,
        partnerId,
        invoiceType,
        status,
        dateFrom,
        dateTo,
        search,
        page = 1,
        limit = 20
      } = filters;
      const skip = (page - 1) * limit;

      const queryBuilder = this.invoiceRepository
        .createQueryBuilder('invoice')
        .leftJoin('invoice.company', 'company')
        .addSelect(['company.name'])
        .leftJoin('core.partners', 'partner', 'invoice.partnerId = partner.id')
        .addSelect(['partner.name'])
        .leftJoin('core.currencies', 'currency', 'invoice.currencyId = currency.id')
        .addSelect(['currency.code'])
        .where('invoice.tenantId = :tenantId', { tenantId });

      // Apply filters
      if (companyId) {
        queryBuilder.andWhere('invoice.companyId = :companyId', { companyId });
      }

      if (partnerId) {
        queryBuilder.andWhere('invoice.partnerId = :partnerId', { partnerId });
      }

      if (invoiceType) {
        queryBuilder.andWhere('invoice.invoiceType = :invoiceType', { invoiceType });
      }

      if (status) {
        queryBuilder.andWhere('invoice.status = :status', { status });
      }

      if (dateFrom) {
        queryBuilder.andWhere('invoice.invoiceDate >= :dateFrom', { dateFrom });
      }

      if (dateTo) {
        queryBuilder.andWhere('invoice.invoiceDate <= :dateTo', { dateTo });
      }

      if (search) {
        queryBuilder.andWhere(
          '(invoice.number ILIKE :search OR invoice.ref ILIKE :search OR partner.name ILIKE :search)',
          { search: `%${search}%` }
        );
      }

      // Get total count
      const total = await queryBuilder.getCount();

      // Get paginated results
      const invoices = await queryBuilder
        .orderBy('invoice.invoiceDate', 'DESC')
        .addOrderBy('invoice.createdAt', 'DESC')
        .skip(skip)
        .take(limit)
        .getMany();

      // Use raw query to get related data since partner and currency are from different schemas
      const invoiceIds = invoices.map(i => i.id);
      let relationsMap: Map<string, { companyName?: string; partnerName?: string; currencyCode?: string }> = new Map();

      if (invoiceIds.length > 0) {
        const relations = await this.invoiceRepository.query(
          `SELECT i.id,
                  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 = ANY($1)`,
          [invoiceIds]
        );

        relations.forEach((rel: any) => {
          relationsMap.set(rel.id, {
            companyName: rel.company_name,
            partnerName: rel.partner_name,
            currencyCode: rel.currency_code,
          });
        });
      }

      // Map to include relation names
      const data: InvoiceWithRelations[] = invoices.map(invoice => {
        const relations = relationsMap.get(invoice.id) || {};
        return {
          ...invoice,
          companyName: relations.companyName,
          partnerName: relations.partnerName,
          currencyCode: relations.currencyCode,
        };
      });

      logger.debug('Invoices retrieved', { tenantId, count: data.length, total });

      return { data, total };
    } catch (error) {
      logger.error('Error retrieving invoices', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get invoice by ID with lines
   */
  async findById(id: string, tenantId: string): Promise<InvoiceWithRelations> {
    try {
      const invoice = await this.invoiceRepository.findOne({
        where: { id, tenantId },
      });

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

      // Get related data using raw query (cross-schema joins)
      const invoiceData = await this.invoiceRepository.query(
        `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 (!invoiceData || invoiceData.length === 0) {
        throw new NotFoundError('Factura no encontrada');
      }

      const invoiceWithRelations = invoiceData[0];

      // Get lines with relations
      const linesData = await this.lineRepository.query(
        `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]
      );

      // Map lines to camelCase
      const lines: InvoiceLineWithRelations[] = linesData.map((line: any) => ({
        id: line.id,
        invoiceId: line.invoice_id,
        tenantId: line.tenant_id,
        productId: line.product_id,
        description: line.description,
        quantity: parseFloat(line.quantity),
        uomId: line.uom_id,
        priceUnit: parseFloat(line.price_unit),
        taxIds: line.tax_ids,
        amountUntaxed: parseFloat(line.amount_untaxed),
        amountTax: parseFloat(line.amount_tax),
        amountTotal: parseFloat(line.amount_total),
        accountId: line.account_id,
        createdAt: line.created_at,
        updatedAt: line.updated_at,
        productName: line.product_name,
        uomName: line.uom_name,
        accountName: line.account_name,
      }));

      return {
        id: invoiceWithRelations.id,
        tenantId: invoiceWithRelations.tenant_id,
        companyId: invoiceWithRelations.company_id,
        partnerId: invoiceWithRelations.partner_id,
        invoiceType: invoiceWithRelations.invoice_type as InvoiceType,
        number: invoiceWithRelations.number,
        ref: invoiceWithRelations.ref,
        invoiceDate: invoiceWithRelations.invoice_date,
        dueDate: invoiceWithRelations.due_date,
        currencyId: invoiceWithRelations.currency_id,
        amountUntaxed: parseFloat(invoiceWithRelations.amount_untaxed),
        amountTax: parseFloat(invoiceWithRelations.amount_tax),
        amountTotal: parseFloat(invoiceWithRelations.amount_total),
        amountPaid: parseFloat(invoiceWithRelations.amount_paid),
        amountResidual: parseFloat(invoiceWithRelations.amount_residual),
        status: invoiceWithRelations.status as InvoiceStatus,
        paymentTermId: invoiceWithRelations.payment_term_id,
        journalId: invoiceWithRelations.journal_id,
        journalEntryId: invoiceWithRelations.journal_entry_id,
        notes: invoiceWithRelations.notes,
        createdAt: invoiceWithRelations.created_at,
        createdBy: invoiceWithRelations.created_by,
        updatedAt: invoiceWithRelations.updated_at,
        updatedBy: invoiceWithRelations.updated_by,
        validatedAt: invoiceWithRelations.validated_at,
        validatedBy: invoiceWithRelations.validated_by,
        cancelledAt: invoiceWithRelations.cancelled_at,
        cancelledBy: invoiceWithRelations.cancelled_by,
        companyName: invoiceWithRelations.company_name,
        partnerName: invoiceWithRelations.partner_name,
        currencyCode: invoiceWithRelations.currency_code,
        lines,
      };
    } catch (error) {
      logger.error('Error finding invoice', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Create a new invoice
   */
  async create(
    dto: CreateInvoiceDto,
    tenantId: string,
    userId: string
  ): Promise<InvoiceWithRelations> {
    try {
      const invoiceDate = dto.invoiceDate || new Date().toISOString().split('T')[0];

      const invoice = this.invoiceRepository.create({
        tenantId,
        companyId: dto.companyId,
        partnerId: dto.partnerId,
        invoiceType: dto.invoiceType as InvoiceType,
        ref: dto.ref || null,
        invoiceDate: new Date(invoiceDate),
        dueDate: dto.dueDate ? new Date(dto.dueDate) : null,
        currencyId: dto.currencyId,
        paymentTermId: dto.paymentTermId || null,
        journalId: dto.journalId || null,
        notes: dto.notes || null,
        amountUntaxed: 0,
        amountTax: 0,
        amountTotal: 0,
        amountPaid: 0,
        amountResidual: 0,
        status: InvoiceStatus.DRAFT,
        createdBy: userId,
      });

      const savedInvoice = await this.invoiceRepository.save(invoice);

      logger.info('Invoice created', {
        invoiceId: savedInvoice.id,
        tenantId,
        invoiceType: savedInvoice.invoiceType,
        createdBy: userId,
      });

      return this.findById(savedInvoice.id, tenantId);
    } catch (error) {
      logger.error('Error creating invoice', {
        error: (error as Error).message,
        tenantId,
        dto,
      });
      throw error;
    }
  }

  /**
   * Update an invoice (only draft invoices)
   */
  async update(
    id: string,
    dto: UpdateInvoiceDto,
    tenantId: string,
    userId: string
  ): Promise<InvoiceWithRelations> {
    try {
      const existing = await this.findById(id, tenantId);

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

      // Update allowed fields
      const updateData: Partial<Invoice> = {
        updatedBy: userId,
        updatedAt: new Date(),
      };

      if (dto.partnerId !== undefined) updateData.partnerId = dto.partnerId;
      if (dto.ref !== undefined) updateData.ref = dto.ref;
      if (dto.invoiceDate !== undefined) updateData.invoiceDate = new Date(dto.invoiceDate);
      if (dto.dueDate !== undefined) updateData.dueDate = dto.dueDate ? new Date(dto.dueDate) : null;
      if (dto.currencyId !== undefined) updateData.currencyId = dto.currencyId;
      if (dto.paymentTermId !== undefined) updateData.paymentTermId = dto.paymentTermId;
      if (dto.journalId !== undefined) updateData.journalId = dto.journalId;
      if (dto.notes !== undefined) updateData.notes = dto.notes;

      await this.invoiceRepository.update({ id, tenantId }, updateData);

      logger.info('Invoice updated', {
        invoiceId: id,
        tenantId,
        updatedBy: userId,
      });

      return this.findById(id, tenantId);
    } catch (error) {
      logger.error('Error updating invoice', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Delete an invoice (only draft invoices)
   */
  async delete(id: string, tenantId: string): Promise<void> {
    try {
      const existing = await this.findById(id, tenantId);

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

      // Lines will be deleted automatically due to CASCADE on the relation
      await this.invoiceRepository.delete({ id, tenantId });

      logger.info('Invoice deleted', {
        invoiceId: id,
        tenantId,
      });
    } catch (error) {
      logger.error('Error deleting invoice', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Add a line to an invoice
   */
  async addLine(
    invoiceId: string,
    dto: CreateInvoiceLineDto,
    tenantId: string
  ): Promise<InvoiceLineWithRelations> {
    try {
      const invoice = await this.findById(invoiceId, tenantId);

      if (invoice.status !== InvoiceStatus.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.invoiceType === InvoiceType.CUSTOMER
        ? 'sales'
        : 'purchase';

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

      const amountUntaxed = taxResult.amountUntaxed;
      const amountTax = taxResult.amountTax;
      const amountTotal = taxResult.amountTotal;

      const line = this.lineRepository.create({
        invoiceId,
        tenantId,
        productId: dto.productId || null,
        description: dto.description,
        quantity: dto.quantity,
        uomId: dto.uomId || null,
        priceUnit: dto.priceUnit,
        taxIds: dto.taxIds || [],
        amountUntaxed,
        amountTax,
        amountTotal,
        accountId: dto.accountId || null,
      });

      const savedLine = await this.lineRepository.save(line);

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

      logger.info('Invoice line added', {
        invoiceId,
        lineId: savedLine.id,
        tenantId,
      });

      // Get line with relations
      const lineData = await this.lineRepository.query(
        `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.id = $1`,
        [savedLine.id]
      );

      const lineWithRelations = lineData[0];

      return {
        id: lineWithRelations.id,
        invoiceId: lineWithRelations.invoice_id,
        tenantId: lineWithRelations.tenant_id,
        productId: lineWithRelations.product_id,
        description: lineWithRelations.description,
        quantity: parseFloat(lineWithRelations.quantity),
        uomId: lineWithRelations.uom_id,
        priceUnit: parseFloat(lineWithRelations.price_unit),
        taxIds: lineWithRelations.tax_ids,
        amountUntaxed: parseFloat(lineWithRelations.amount_untaxed),
        amountTax: parseFloat(lineWithRelations.amount_tax),
        amountTotal: parseFloat(lineWithRelations.amount_total),
        accountId: lineWithRelations.account_id,
        createdAt: lineWithRelations.created_at,
        updatedAt: lineWithRelations.updated_at,
        productName: lineWithRelations.product_name,
        uomName: lineWithRelations.uom_name,
        accountName: lineWithRelations.account_name,
      };
    } catch (error) {
      logger.error('Error adding invoice line', {
        error: (error as Error).message,
        invoiceId,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Update an invoice line
   */
  async updateLine(
    invoiceId: string,
    lineId: string,
    dto: UpdateInvoiceLineDto,
    tenantId: string
  ): Promise<InvoiceLineWithRelations> {
    try {
      const invoice = await this.findById(invoiceId, tenantId);

      if (invoice.status !== InvoiceStatus.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');
      }

      // Calculate new amounts
      const quantity = dto.quantity ?? existingLine.quantity;
      const priceUnit = dto.priceUnit ?? existingLine.priceUnit;
      const taxIds = dto.taxIds ?? existingLine.taxIds;

      // Determine transaction type based on invoice type
      const transactionType = invoice.invoiceType === InvoiceType.CUSTOMER
        ? 'sales'
        : 'purchase';

      const taxResult = await taxesService.calculateTaxes(
        {
          quantity,
          priceUnit,
          discount: 0,
          taxIds,
        },
        tenantId,
        transactionType
      );

      // Update line fields
      const updateData: Partial<InvoiceLine> = {
        amountUntaxed: taxResult.amountUntaxed,
        amountTax: taxResult.amountTax,
        amountTotal: taxResult.amountTotal,
        updatedAt: new Date(),
      };

      if (dto.productId !== undefined) updateData.productId = dto.productId;
      if (dto.description !== undefined) updateData.description = dto.description;
      if (dto.quantity !== undefined) updateData.quantity = dto.quantity;
      if (dto.uomId !== undefined) updateData.uomId = dto.uomId;
      if (dto.priceUnit !== undefined) updateData.priceUnit = dto.priceUnit;
      if (dto.taxIds !== undefined) updateData.taxIds = dto.taxIds;
      if (dto.accountId !== undefined) updateData.accountId = dto.accountId;

      await this.lineRepository.update(
        { id: lineId, invoiceId },
        updateData
      );

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

      logger.info('Invoice line updated', {
        invoiceId,
        lineId,
        tenantId,
      });

      // Get updated line with relations
      const lineData = await this.lineRepository.query(
        `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.id = $1`,
        [lineId]
      );

      const lineWithRelations = lineData[0];

      return {
        id: lineWithRelations.id,
        invoiceId: lineWithRelations.invoice_id,
        tenantId: lineWithRelations.tenant_id,
        productId: lineWithRelations.product_id,
        description: lineWithRelations.description,
        quantity: parseFloat(lineWithRelations.quantity),
        uomId: lineWithRelations.uom_id,
        priceUnit: parseFloat(lineWithRelations.price_unit),
        taxIds: lineWithRelations.tax_ids,
        amountUntaxed: parseFloat(lineWithRelations.amount_untaxed),
        amountTax: parseFloat(lineWithRelations.amount_tax),
        amountTotal: parseFloat(lineWithRelations.amount_total),
        accountId: lineWithRelations.account_id,
        createdAt: lineWithRelations.created_at,
        updatedAt: lineWithRelations.updated_at,
        productName: lineWithRelations.product_name,
        uomName: lineWithRelations.uom_name,
        accountName: lineWithRelations.account_name,
      };
    } catch (error) {
      logger.error('Error updating invoice line', {
        error: (error as Error).message,
        invoiceId,
        lineId,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Remove a line from an invoice
   */
  async removeLine(
    invoiceId: string,
    lineId: string,
    tenantId: string
  ): Promise<void> {
    try {
      const invoice = await this.findById(invoiceId, tenantId);

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

      await this.lineRepository.delete({ id: lineId, invoiceId });

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

      logger.info('Invoice line removed', {
        invoiceId,
        lineId,
        tenantId,
      });
    } catch (error) {
      logger.error('Error removing invoice line', {
        error: (error as Error).message,
        invoiceId,
        lineId,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Validate an invoice (draft -> open)
   */
  async validate(
    id: string,
    tenantId: string,
    userId: string
  ): Promise<InvoiceWithRelations> {
    try {
      const invoice = await this.findById(id, tenantId);

      if (invoice.status !== InvoiceStatus.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.invoiceType === InvoiceType.CUSTOMER ? 'INV' : 'BILL';
      const seqResult = await this.invoiceRepository.query(
        `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[0]?.next_num || 1).padStart(6, '0')}`;

      await this.invoiceRepository.update(
        { id, tenantId },
        {
          number: invoiceNumber,
          status: InvoiceStatus.OPEN,
          amountResidual: invoice.amountTotal,
          validatedAt: new Date(),
          validatedBy: userId,
          updatedBy: userId,
          updatedAt: new Date(),
        }
      );

      logger.info('Invoice validated', {
        invoiceId: id,
        invoiceNumber,
        tenantId,
        validatedBy: userId,
      });

      return this.findById(id, tenantId);
    } catch (error) {
      logger.error('Error validating invoice', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Cancel an invoice
   */
  async cancel(
    id: string,
    tenantId: string,
    userId: string
  ): Promise<InvoiceWithRelations> {
    try {
      const invoice = await this.findById(id, tenantId);

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

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

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

      await this.invoiceRepository.update(
        { id, tenantId },
        {
          status: InvoiceStatus.CANCELLED,
          cancelledAt: new Date(),
          cancelledBy: userId,
          updatedBy: userId,
          updatedAt: new Date(),
        }
      );

      logger.info('Invoice cancelled', {
        invoiceId: id,
        tenantId,
        cancelledBy: userId,
      });

      return this.findById(id, tenantId);
    } catch (error) {
      logger.error('Error cancelling invoice', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Update invoice totals from lines
   * Private helper method
   */
  private async updateTotals(invoiceId: string): Promise<void> {
    try {
      const totals = await this.lineRepository.query(
        `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]
      );

      const amountUntaxed = parseFloat(totals[0]?.amount_untaxed || '0');
      const amountTax = parseFloat(totals[0]?.amount_tax || '0');
      const amountTotal = parseFloat(totals[0]?.amount_total || '0');

      // Get current invoice to calculate residual
      const invoice = await this.invoiceRepository.findOne({
        where: { id: invoiceId },
        select: ['amountPaid'],
      });

      const amountPaid = invoice?.amountPaid || 0;
      const amountResidual = amountTotal - amountPaid;

      await this.invoiceRepository.update(
        { id: invoiceId },
        {
          amountUntaxed,
          amountTax,
          amountTotal,
          amountResidual,
        }
      );

      logger.debug('Invoice totals updated', {
        invoiceId,
        amountUntaxed,
        amountTax,
        amountTotal,
      });
    } catch (error) {
      logger.error('Error updating invoice totals', {
        error: (error as Error).message,
        invoiceId,
      });
      throw error;
    }
  }
}

// ===== Export Singleton Instance =====

export const invoicesService = new InvoicesService();

// Re-export enums for backwards compatibility
export { InvoiceType, InvoiceStatus };
