import { IPresenterRegistry, ITransactionManager } from "@erp/core/api";
import { UniqueID, UtcDate } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import {
  IssueCustomerInvoiceDomainService,
  ProformaCustomerInvoiceDomainService,
} from "../../domain";
import { CustomerInvoiceFullPresenter } from "../presenters";
import { CustomerInvoiceApplicationService } from "../services";

type IssueCustomerInvoiceUseCaseInput = {
  companyId: UniqueID;
  proforma_id: string;
};

/**
 * Caso de uso: Conversión de una proforma a factura definitiva.
 *
 * - Recupera la proforma
 * - Valida su estado ("approved")
 * - Genera la factura definitiva (nueva entidad)
 * - Marca la proforma como "issued"
 * - Persiste ambas dentro de la misma transacción
 */
export class IssueCustomerInvoiceUseCase {
  private readonly issueDomainService: IssueCustomerInvoiceDomainService;
  private readonly proformaDomainService: ProformaCustomerInvoiceDomainService;

  constructor(
    private readonly service: CustomerInvoiceApplicationService,
    private readonly transactionManager: ITransactionManager,
    private readonly presenterRegistry: IPresenterRegistry
  ) {
    this.issueDomainService = new IssueCustomerInvoiceDomainService();
    this.proformaDomainService = new ProformaCustomerInvoiceDomainService();
  }

  public execute(params: IssueCustomerInvoiceUseCaseInput) {
    const { proforma_id, companyId } = params;
    const idOrError = UniqueID.create(proforma_id);
    if (idOrError.isFailure) return Result.fail(idOrError.error);

    const proformaId = idOrError.data;
    const presenter = this.presenterRegistry.getPresenter({
      resource: "customer-invoice",
      projection: "FULL",
    }) as CustomerInvoiceFullPresenter;

    return this.transactionManager.complete(async (transaction) => {
      try {
        /** 1. Recuperamos la proforma */
        const proformaResult = await this.service.getInvoiceByIdInCompany(
          companyId,
          proformaId,
          transaction
        );
        if (proformaResult.isFailure) return Result.fail(proformaResult.error);
        const proforma = proformaResult.data;

        /** 2. Generar nueva factura */
        const nextNumberResult = await this.service.getNextIssueInvoiceNumber(
          companyId,
          proforma.series,
          transaction
        );
        if (nextNumberResult.isFailure) return Result.fail(nextNumberResult.error);

        /** 4. Crear factura definitiva (dominio) */
        const issuedInvoiceOrError = await this.issueDomainService.issueFromProforma(proforma, {
          issueNumber: nextNumberResult.data,
          issueDate: UtcDate.today(),
        });
        if (issuedInvoiceOrError.isFailure) return Result.fail(issuedInvoiceOrError.error);

        /** 5. Guardar la nueva factura */
        const saveInvoiceResult = await this.service.createInvoiceInCompany(
          companyId,
          issuedInvoiceOrError.data,
          transaction
        );
        if (saveInvoiceResult.isFailure) return Result.fail(saveInvoiceResult.error);

        /** 6. Actualizar la proforma */
        const closedProformaResult = await this.proformaDomainService.markAsIssued(proforma);
        if (closedProformaResult.isFailure) return Result.fail(closedProformaResult.error);
        const closedProforma = closedProformaResult.data;

        /** 7. Guardar la proforma */
        await this.service.updateProformaStatusByIdInCompany(
          companyId,
          proformaId,
          closedProforma.status,
          transaction
        );

        const dto = presenter.toOutput(saveInvoiceResult.data);
        return Result.ok(dto);
      } catch (error: unknown) {
        return Result.fail(error as Error);
      }
    });
  }
}
