import { Repository, IsNull } from 'typeorm';
import { AppDataSource } from '../../../config/typeorm.js';
import { CfdiInvoice } from '../entities/cfdi-invoice.entity.js';
import { CfdiCertificate, CertificateStatus } from '../entities/cfdi-certificate.entity.js';
import { CfdiCancellation, CancellationRequestStatus } from '../entities/cfdi-cancellation.entity.js';
import { CfdiLog, CfdiOperationType } from '../entities/cfdi-log.entity.js';
import { CfdiStampQueue } from '../entities/cfdi-stamp-queue.entity.js';
import { CfdiStatus } from '../enums/cfdi-status.enum.js';
import { CancellationReason } from '../enums/cancellation-reason.enum.js';
import { NotFoundError, ValidationError, ConflictError } from '../../../shared/types/index.js';
import { logger } from '../../../shared/utils/logger.js';

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

export interface CreateCfdiInvoiceDto {
  invoiceId?: string;
  certificateId?: string;
  serie?: string;
  folio?: string;
  cfdiVersion?: string;
  voucherType?: string;
  issuerRfc: string;
  issuerName: string;
  issuerFiscalRegime: string;
  receiverRfc: string;
  receiverName: string;
  receiverFiscalRegime?: string;
  receiverZipCode?: string;
  receiverTaxResidence?: string;
  receiverTaxId?: string;
  cfdiUse: string;
  subtotal: number;
  discount?: number;
  total: number;
  totalTransferredTaxes?: number;
  totalWithheldTaxes?: number;
  currency?: string;
  exchangeRate?: number;
  exportation?: string;
  paymentForm: string;
  paymentMethod: string;
  paymentConditions?: string;
  expeditionPlace: string;
  confirmationCode?: string;
  relatedCfdiType?: string;
  globalInfoPeriodicity?: string;
  globalInfoMonths?: string;
  globalInfoYear?: string;
}

export interface UpdateCfdiInvoiceDto {
  invoiceId?: string | null;
  certificateId?: string | null;
  serie?: string | null;
  folio?: string | null;
  voucherType?: string;
  issuerRfc?: string;
  issuerName?: string;
  issuerFiscalRegime?: string;
  receiverRfc?: string;
  receiverName?: string;
  receiverFiscalRegime?: string | null;
  receiverZipCode?: string | null;
  receiverTaxResidence?: string | null;
  receiverTaxId?: string | null;
  cfdiUse?: string;
  subtotal?: number;
  discount?: number | null;
  total?: number;
  totalTransferredTaxes?: number | null;
  totalWithheldTaxes?: number | null;
  currency?: string;
  exchangeRate?: number | null;
  exportation?: string | null;
  paymentForm?: string;
  paymentMethod?: string;
  paymentConditions?: string | null;
  expeditionPlace?: string;
  confirmationCode?: string | null;
  relatedCfdiType?: string | null;
  globalInfoPeriodicity?: string | null;
  globalInfoMonths?: string | null;
  globalInfoYear?: string | null;
}

export interface CfdiFilters {
  search?: string;
  status?: CfdiStatus;
  voucherType?: string;
  issuerRfc?: string;
  receiverRfc?: string;
  serie?: string;
  folio?: string;
  dateFrom?: string;
  dateTo?: string;
  page?: number;
  limit?: number;
}

export interface CertificateFilters {
  rfc?: string;
  status?: CertificateStatus;
  isDefault?: boolean;
  page?: number;
  limit?: number;
}

export interface UploadCertificateDto {
  rfc: string;
  certificateNumber: string;
  certificatePem: string;
  privateKeyPemEncrypted: string;
  issuedAt: string;
  expiresAt: string;
  serialNumber: string;
  isDefault?: boolean;
  issuerName?: string;
  subjectName?: string;
  description?: string;
  notes?: string;
}

export interface CancellationRequestDto {
  cancellationReason: CancellationReason;
  substituteUuid?: string;
  substituteCfdiId?: string;
  reasonNotes?: string;
  internalNotes?: string;
}

export interface CfdiStatusResponse {
  id: string;
  status: CfdiStatus;
  uuid: string | null;
  stampDate: Date | null;
  cancellationStatus: string | null;
  cancellationDate: Date | null;
  lastSatValidation: Date | null;
  satValidationStatus: string | null;
  satValidationResponse: Record<string, any> | null;
  lastError: string | null;
}

// ===== CfdiService Class =====

class CfdiService {
  private invoiceRepository: Repository<CfdiInvoice>;
  private certificateRepository: Repository<CfdiCertificate>;
  private cancellationRepository: Repository<CfdiCancellation>;
  private logRepository: Repository<CfdiLog>;
  private stampQueueRepository: Repository<CfdiStampQueue>;

  constructor() {
    this.invoiceRepository = AppDataSource.getRepository(CfdiInvoice);
    this.certificateRepository = AppDataSource.getRepository(CfdiCertificate);
    this.cancellationRepository = AppDataSource.getRepository(CfdiCancellation);
    this.logRepository = AppDataSource.getRepository(CfdiLog);
    this.stampQueueRepository = AppDataSource.getRepository(CfdiStampQueue);
  }

  // ===== CFDI Invoice Operations =====

  /**
   * Get all CFDI invoices for a tenant with filters and pagination
   */
  async findAll(
    tenantId: string,
    filters: CfdiFilters = {}
  ): Promise<{ data: CfdiInvoice[]; total: number }> {
    try {
      const {
        search,
        status,
        voucherType,
        issuerRfc,
        receiverRfc,
        serie,
        folio,
        dateFrom,
        dateTo,
        page = 1,
        limit = 20,
      } = filters;
      const skip = (page - 1) * limit;

      const queryBuilder = this.invoiceRepository
        .createQueryBuilder('cfdi')
        .where('cfdi.tenantId = :tenantId', { tenantId })
        .andWhere('cfdi.deletedAt IS NULL');

      // Apply search filter across key fields
      if (search) {
        queryBuilder.andWhere(
          '(cfdi.uuid ILIKE :search OR cfdi.serie ILIKE :search OR cfdi.folio ILIKE :search OR cfdi.issuerRfc ILIKE :search OR cfdi.receiverRfc ILIKE :search OR cfdi.issuerName ILIKE :search OR cfdi.receiverName ILIKE :search)',
          { search: `%${search}%` }
        );
      }

      if (status !== undefined) {
        queryBuilder.andWhere('cfdi.status = :status', { status });
      }

      if (voucherType !== undefined) {
        queryBuilder.andWhere('cfdi.voucherType = :voucherType', { voucherType });
      }

      if (issuerRfc) {
        queryBuilder.andWhere('cfdi.issuerRfc = :issuerRfc', { issuerRfc });
      }

      if (receiverRfc) {
        queryBuilder.andWhere('cfdi.receiverRfc = :receiverRfc', { receiverRfc });
      }

      if (serie) {
        queryBuilder.andWhere('cfdi.serie = :serie', { serie });
      }

      if (folio) {
        queryBuilder.andWhere('cfdi.folio = :folio', { folio });
      }

      if (dateFrom) {
        queryBuilder.andWhere('cfdi.createdAt >= :dateFrom', { dateFrom });
      }

      if (dateTo) {
        queryBuilder.andWhere('cfdi.createdAt <= :dateTo', { dateTo });
      }

      const total = await queryBuilder.getCount();

      const data = await queryBuilder
        .orderBy('cfdi.createdAt', 'DESC')
        .skip(skip)
        .take(limit)
        .getMany();

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

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

  /**
   * Get CFDI invoice by ID
   */
  async findById(tenantId: string, id: string): Promise<CfdiInvoice> {
    try {
      const invoice = await this.invoiceRepository.findOne({
        where: {
          id,
          tenantId,
          deletedAt: IsNull(),
        },
        relations: ['logs', 'cancellations'],
      });

      if (!invoice) {
        throw new NotFoundError('CFDI no encontrado');
      }

      return invoice;
    } catch (error) {
      if (error instanceof NotFoundError) throw error;
      logger.error('Error finding CFDI invoice', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Create a new CFDI invoice in draft status
   */
  async create(
    tenantId: string,
    dto: CreateCfdiInvoiceDto,
    userId: string
  ): Promise<CfdiInvoice> {
    try {
      const invoiceData: Partial<CfdiInvoice> = {
        tenantId,
        status: CfdiStatus.DRAFT,
        cfdiVersion: dto.cfdiVersion || '4.0',
        issuerRfc: dto.issuerRfc,
        issuerName: dto.issuerName,
        issuerFiscalRegime: dto.issuerFiscalRegime,
        receiverRfc: dto.receiverRfc,
        receiverName: dto.receiverName,
        cfdiUse: dto.cfdiUse,
        subtotal: dto.subtotal,
        total: dto.total,
        currency: dto.currency || 'MXN',
        paymentForm: dto.paymentForm,
        paymentMethod: dto.paymentMethod,
        expeditionPlace: dto.expeditionPlace,
        createdBy: userId,
      };

      // Add optional fields only if provided
      if (dto.invoiceId !== undefined) invoiceData.invoiceId = dto.invoiceId;
      if (dto.certificateId !== undefined) invoiceData.certificateId = dto.certificateId;
      if (dto.serie !== undefined) invoiceData.serie = dto.serie;
      if (dto.folio !== undefined) invoiceData.folio = dto.folio;
      if (dto.voucherType !== undefined) invoiceData.voucherType = dto.voucherType as any;
      if (dto.receiverFiscalRegime !== undefined) invoiceData.receiverFiscalRegime = dto.receiverFiscalRegime;
      if (dto.receiverZipCode !== undefined) invoiceData.receiverZipCode = dto.receiverZipCode;
      if (dto.receiverTaxResidence !== undefined) invoiceData.receiverTaxResidence = dto.receiverTaxResidence;
      if (dto.receiverTaxId !== undefined) invoiceData.receiverTaxId = dto.receiverTaxId;
      if (dto.discount !== undefined) invoiceData.discount = dto.discount;
      if (dto.totalTransferredTaxes !== undefined) invoiceData.totalTransferredTaxes = dto.totalTransferredTaxes;
      if (dto.totalWithheldTaxes !== undefined) invoiceData.totalWithheldTaxes = dto.totalWithheldTaxes;
      if (dto.exchangeRate !== undefined) invoiceData.exchangeRate = dto.exchangeRate;
      if (dto.exportation !== undefined) invoiceData.exportation = dto.exportation;
      if (dto.paymentConditions !== undefined) invoiceData.paymentConditions = dto.paymentConditions;
      if (dto.confirmationCode !== undefined) invoiceData.confirmationCode = dto.confirmationCode;
      if (dto.relatedCfdiType !== undefined) invoiceData.relatedCfdiType = dto.relatedCfdiType;
      if (dto.globalInfoPeriodicity !== undefined) invoiceData.globalInfoPeriodicity = dto.globalInfoPeriodicity;
      if (dto.globalInfoMonths !== undefined) invoiceData.globalInfoMonths = dto.globalInfoMonths;
      if (dto.globalInfoYear !== undefined) invoiceData.globalInfoYear = dto.globalInfoYear;

      const invoice = this.invoiceRepository.create(invoiceData);
      await this.invoiceRepository.save(invoice);

      // Log the creation operation
      await this.createLog(tenantId, invoice.id, null, CfdiOperationType.CREATE, null, CfdiStatus.DRAFT, true, userId);

      logger.info('CFDI invoice created', {
        cfdiId: invoice.id,
        tenantId,
        issuerRfc: invoice.issuerRfc,
        receiverRfc: invoice.receiverRfc,
        createdBy: userId,
      });

      return invoice;
    } catch (error) {
      logger.error('Error creating CFDI invoice', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Update a CFDI invoice (only allowed in draft status)
   */
  async update(
    tenantId: string,
    id: string,
    dto: UpdateCfdiInvoiceDto,
    userId: string
  ): Promise<CfdiInvoice> {
    try {
      const existing = await this.findById(tenantId, id);

      if (existing.status !== CfdiStatus.DRAFT) {
        throw new ValidationError(
          `No se puede modificar un CFDI con estado '${existing.status}'. Solo se permiten cambios en estado 'draft'.`
        );
      }

      // Update allowed fields
      if (dto.invoiceId !== undefined) existing.invoiceId = dto.invoiceId;
      if (dto.certificateId !== undefined) existing.certificateId = dto.certificateId;
      if (dto.serie !== undefined) existing.serie = dto.serie;
      if (dto.folio !== undefined) existing.folio = dto.folio;
      if (dto.voucherType !== undefined) existing.voucherType = dto.voucherType as any;
      if (dto.issuerRfc !== undefined) existing.issuerRfc = dto.issuerRfc;
      if (dto.issuerName !== undefined) existing.issuerName = dto.issuerName;
      if (dto.issuerFiscalRegime !== undefined) existing.issuerFiscalRegime = dto.issuerFiscalRegime;
      if (dto.receiverRfc !== undefined) existing.receiverRfc = dto.receiverRfc;
      if (dto.receiverName !== undefined) existing.receiverName = dto.receiverName;
      if (dto.receiverFiscalRegime !== undefined) existing.receiverFiscalRegime = dto.receiverFiscalRegime;
      if (dto.receiverZipCode !== undefined) existing.receiverZipCode = dto.receiverZipCode;
      if (dto.receiverTaxResidence !== undefined) existing.receiverTaxResidence = dto.receiverTaxResidence;
      if (dto.receiverTaxId !== undefined) existing.receiverTaxId = dto.receiverTaxId;
      if (dto.cfdiUse !== undefined) existing.cfdiUse = dto.cfdiUse;
      if (dto.subtotal !== undefined) existing.subtotal = dto.subtotal;
      if (dto.discount !== undefined) existing.discount = dto.discount;
      if (dto.total !== undefined) existing.total = dto.total;
      if (dto.totalTransferredTaxes !== undefined) existing.totalTransferredTaxes = dto.totalTransferredTaxes;
      if (dto.totalWithheldTaxes !== undefined) existing.totalWithheldTaxes = dto.totalWithheldTaxes;
      if (dto.currency !== undefined) existing.currency = dto.currency;
      if (dto.exchangeRate !== undefined) existing.exchangeRate = dto.exchangeRate;
      if (dto.exportation !== undefined) existing.exportation = dto.exportation;
      if (dto.paymentForm !== undefined) existing.paymentForm = dto.paymentForm;
      if (dto.paymentMethod !== undefined) existing.paymentMethod = dto.paymentMethod;
      if (dto.paymentConditions !== undefined) existing.paymentConditions = dto.paymentConditions;
      if (dto.expeditionPlace !== undefined) existing.expeditionPlace = dto.expeditionPlace;
      if (dto.confirmationCode !== undefined) existing.confirmationCode = dto.confirmationCode;
      if (dto.relatedCfdiType !== undefined) existing.relatedCfdiType = dto.relatedCfdiType;
      if (dto.globalInfoPeriodicity !== undefined) existing.globalInfoPeriodicity = dto.globalInfoPeriodicity;
      if (dto.globalInfoMonths !== undefined) existing.globalInfoMonths = dto.globalInfoMonths;
      if (dto.globalInfoYear !== undefined) existing.globalInfoYear = dto.globalInfoYear;

      existing.updatedBy = userId;

      await this.invoiceRepository.save(existing);

      logger.info('CFDI invoice updated', {
        cfdiId: id,
        tenantId,
        updatedBy: userId,
      });

      return await this.findById(tenantId, id);
    } catch (error) {
      if (error instanceof NotFoundError || error instanceof ValidationError) throw error;
      logger.error('Error updating CFDI invoice', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Request stamping for a CFDI invoice.
   * Changes status from 'draft' to 'pending' and adds it to the stamp queue.
   */
  async requestStamp(tenantId: string, id: string, userId: string): Promise<CfdiInvoice> {
    try {
      const invoice = await this.findById(tenantId, id);

      if (invoice.status !== CfdiStatus.DRAFT && invoice.status !== CfdiStatus.ERROR) {
        throw new ValidationError(
          `No se puede solicitar timbrado para un CFDI con estado '${invoice.status}'. Solo se permite desde 'draft' o 'error'.`
        );
      }

      // Validate required fields before allowing stamp request
      this.validateInvoiceForStamping(invoice);

      const previousStatus = invoice.status;

      // Update status to pending
      invoice.status = CfdiStatus.PENDING;
      invoice.lastError = null;
      invoice.errorDetails = null;
      invoice.updatedBy = userId;
      await this.invoiceRepository.save(invoice);

      // Add to stamp queue
      const queueEntry = this.stampQueueRepository.create({
        tenantId,
        documentType: 'invoice',
        documentId: invoice.id,
        priority: 5,
        queueStatus: 'pending',
        attempts: 0,
        maxAttempts: 3,
        createdBy: userId,
      });
      await this.stampQueueRepository.save(queueEntry);

      // Log the stamp request
      await this.createLog(
        tenantId,
        invoice.id,
        invoice.uuid,
        CfdiOperationType.STAMP,
        previousStatus,
        CfdiStatus.PENDING,
        true,
        userId
      );

      logger.info('CFDI stamp requested', {
        cfdiId: id,
        tenantId,
        queueId: queueEntry.id,
        requestedBy: userId,
      });

      return invoice;
    } catch (error) {
      if (error instanceof NotFoundError || error instanceof ValidationError) throw error;
      logger.error('Error requesting CFDI stamp', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Request cancellation for a stamped CFDI invoice.
   * Creates a cancellation request record and updates invoice status.
   */
  async requestCancellation(
    tenantId: string,
    id: string,
    dto: CancellationRequestDto,
    userId: string
  ): Promise<CfdiCancellation> {
    try {
      const invoice = await this.findById(tenantId, id);

      if (invoice.status !== CfdiStatus.STAMPED) {
        throw new ValidationError(
          `No se puede cancelar un CFDI con estado '${invoice.status}'. Solo se permite cancelar CFDIs timbrados.`
        );
      }

      if (!invoice.uuid) {
        throw new ValidationError('El CFDI no tiene UUID asignado. No se puede solicitar cancelacion.');
      }

      // Check for reason '01' requiring a substitute UUID
      if (dto.cancellationReason === CancellationReason.ERROR_WITH_RELATION && !dto.substituteUuid) {
        throw new ValidationError(
          'El motivo de cancelacion "01" (errores con relacion) requiere un UUID de CFDI sustituto.'
        );
      }

      // Check if there's already an active cancellation request
      const existingRequest = await this.cancellationRepository.findOne({
        where: {
          cfdiInvoiceId: id,
          tenantId,
          status: CancellationRequestStatus.PENDING,
        },
      });

      if (existingRequest) {
        throw new ConflictError('Ya existe una solicitud de cancelacion pendiente para este CFDI.');
      }

      // Create cancellation request
      const cancellation = this.cancellationRepository.create({
        tenantId,
        cfdiInvoiceId: id,
        cfdiUuid: invoice.uuid,
        cancellationReason: dto.cancellationReason,
        substituteUuid: dto.substituteUuid || null,
        substituteCfdiId: dto.substituteCfdiId || null,
        status: CancellationRequestStatus.PENDING,
        reasonNotes: dto.reasonNotes || null,
        internalNotes: dto.internalNotes || null,
        requestedAt: new Date(),
        retryCount: 0,
        createdBy: userId,
      });

      await this.cancellationRepository.save(cancellation);

      // Update invoice status
      const previousStatus = invoice.status;
      invoice.status = CfdiStatus.CANCELLATION_PENDING;
      invoice.cancellationStatus = 'pending';
      invoice.updatedBy = userId;
      await this.invoiceRepository.save(invoice);

      // Log the cancellation request
      await this.createLog(
        tenantId,
        invoice.id,
        invoice.uuid,
        CfdiOperationType.CANCEL_REQUEST,
        previousStatus,
        CfdiStatus.CANCELLATION_PENDING,
        true,
        userId
      );

      logger.info('CFDI cancellation requested', {
        cfdiId: id,
        cfdiUuid: invoice.uuid,
        cancellationId: cancellation.id,
        reason: dto.cancellationReason,
        tenantId,
        requestedBy: userId,
      });

      return cancellation;
    } catch (error) {
      if (
        error instanceof NotFoundError ||
        error instanceof ValidationError ||
        error instanceof ConflictError
      ) {
        throw error;
      }
      logger.error('Error requesting CFDI cancellation', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get CFDI status including SAT validation info
   */
  async getStatus(tenantId: string, id: string): Promise<CfdiStatusResponse> {
    try {
      const invoice = await this.findById(tenantId, id);

      return {
        id: invoice.id,
        status: invoice.status,
        uuid: invoice.uuid,
        stampDate: invoice.stampDate,
        cancellationStatus: invoice.cancellationStatus,
        cancellationDate: invoice.cancellationDate,
        lastSatValidation: invoice.lastSatValidation,
        satValidationStatus: invoice.satValidationStatus,
        satValidationResponse: invoice.satValidationResponse,
        lastError: invoice.lastError,
      };
    } catch (error) {
      if (error instanceof NotFoundError) throw error;
      logger.error('Error getting CFDI status', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  // ===== Certificate Management =====

  /**
   * List certificates for a tenant with filters and pagination
   */
  async listCertificates(
    tenantId: string,
    filters: CertificateFilters = {}
  ): Promise<{ data: CfdiCertificate[]; total: number }> {
    try {
      const { rfc, status, isDefault, page = 1, limit = 20 } = filters;
      const skip = (page - 1) * limit;

      const queryBuilder = this.certificateRepository
        .createQueryBuilder('cert')
        .where('cert.tenantId = :tenantId', { tenantId })
        .andWhere('cert.deletedAt IS NULL');

      if (rfc) {
        queryBuilder.andWhere('cert.rfc = :rfc', { rfc });
      }

      if (status !== undefined) {
        queryBuilder.andWhere('cert.status = :status', { status });
      }

      if (isDefault !== undefined) {
        queryBuilder.andWhere('cert.isDefault = :isDefault', { isDefault });
      }

      const total = await queryBuilder.getCount();

      const data = await queryBuilder
        .orderBy('cert.createdAt', 'DESC')
        .skip(skip)
        .take(limit)
        .getMany();

      // Remove sensitive data before returning
      const sanitized = data.map((cert) => {
        const { privateKeyPemEncrypted, ...safe } = cert;
        return safe as CfdiCertificate;
      });

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

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

  /**
   * Upload/register a new certificate
   */
  async uploadCertificate(
    tenantId: string,
    dto: UploadCertificateDto,
    userId: string
  ): Promise<CfdiCertificate> {
    try {
      // Check for duplicate serial number
      const existingBySerial = await this.certificateRepository.findOne({
        where: { serialNumber: dto.serialNumber },
      });

      if (existingBySerial) {
        throw new ConflictError(
          `Ya existe un certificado con el numero de serie '${dto.serialNumber}'.`
        );
      }

      // If this certificate is marked as default, unset other defaults for same tenant+rfc
      if (dto.isDefault) {
        await this.certificateRepository
          .createQueryBuilder()
          .update(CfdiCertificate)
          .set({ isDefault: false, updatedBy: userId })
          .where('tenantId = :tenantId', { tenantId })
          .andWhere('rfc = :rfc', { rfc: dto.rfc })
          .andWhere('isDefault = true')
          .execute();
      }

      const certificate = this.certificateRepository.create({
        tenantId,
        rfc: dto.rfc,
        certificateNumber: dto.certificateNumber,
        serialNumber: dto.serialNumber,
        certificatePem: dto.certificatePem,
        privateKeyPemEncrypted: dto.privateKeyPemEncrypted,
        issuedAt: new Date(dto.issuedAt),
        expiresAt: new Date(dto.expiresAt),
        status: CertificateStatus.ACTIVE,
        issuerName: dto.issuerName || null,
        subjectName: dto.subjectName || null,
        description: dto.description || null,
        notes: dto.notes || null,
        isDefault: dto.isDefault || false,
        createdBy: userId,
      });

      await this.certificateRepository.save(certificate);

      logger.info('Certificate uploaded', {
        certificateId: certificate.id,
        tenantId,
        rfc: dto.rfc,
        serialNumber: dto.serialNumber,
        createdBy: userId,
      });

      // Remove sensitive data before returning
      const { privateKeyPemEncrypted, ...safeCertificate } = certificate;
      return safeCertificate as CfdiCertificate;
    } catch (error) {
      if (error instanceof ConflictError) throw error;
      logger.error('Error uploading certificate', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  // ===== Private Helpers =====

  /**
   * Validate that an invoice has all required fields for stamping
   */
  private validateInvoiceForStamping(invoice: CfdiInvoice): void {
    const missingFields: string[] = [];

    if (!invoice.issuerRfc) missingFields.push('issuerRfc');
    if (!invoice.issuerName) missingFields.push('issuerName');
    if (!invoice.issuerFiscalRegime) missingFields.push('issuerFiscalRegime');
    if (!invoice.receiverRfc) missingFields.push('receiverRfc');
    if (!invoice.receiverName) missingFields.push('receiverName');
    if (!invoice.cfdiUse) missingFields.push('cfdiUse');
    if (!invoice.paymentForm) missingFields.push('paymentForm');
    if (!invoice.paymentMethod) missingFields.push('paymentMethod');
    if (!invoice.expeditionPlace) missingFields.push('expeditionPlace');
    if (invoice.subtotal === undefined || invoice.subtotal === null) missingFields.push('subtotal');
    if (invoice.total === undefined || invoice.total === null) missingFields.push('total');

    if (missingFields.length > 0) {
      throw new ValidationError(
        `El CFDI no tiene todos los campos requeridos para timbrado. Campos faltantes: ${missingFields.join(', ')}`
      );
    }
  }

  /**
   * Create an operation log entry
   */
  private async createLog(
    tenantId: string,
    cfdiInvoiceId: string,
    cfdiUuid: string | null,
    operationType: CfdiOperationType,
    statusBefore: string | null,
    statusAfter: string,
    success: boolean,
    userId: string,
    errorCode?: string,
    errorMessage?: string
  ): Promise<CfdiLog> {
    const log = this.logRepository.create({
      tenantId,
      cfdiInvoiceId,
      cfdiUuid: cfdiUuid || null,
      operationType,
      statusBefore: statusBefore || null,
      statusAfter,
      success,
      errorCode: errorCode || null,
      errorMessage: errorMessage || null,
      createdBy: userId,
    });

    await this.logRepository.save(log);
    return log;
  }
}

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

export const cfdiService = new CfdiService();
