import { JsonTaxCatalogProvider } from "@erp/core";
import { DuplicateEntityError, IPresenterRegistry, ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize";
import { CreateCustomerInvoiceRequestDTO } from "../../../../common/dto";
import { CustomerInvoiceApplicationService } from "../../../domain";
import { CustomerInvoiceFullPresenter } from "../../presenters";
import { CreateCustomerInvoicePropsMapper } from "./map-dto-to-create-customer-invoice-props";

type CreateCustomerInvoiceUseCaseInput = {
  companyId: UniqueID;
  dto: CreateCustomerInvoiceRequestDTO;
};

export class CreateCustomerInvoiceUseCase {
  constructor(
    private readonly service: CustomerInvoiceApplicationService,
    private readonly transactionManager: ITransactionManager,
    private readonly presenterRegistry: IPresenterRegistry,
    private readonly taxCatalog: JsonTaxCatalogProvider
  ) {}

  public execute(params: CreateCustomerInvoiceUseCaseInput) {
    const { dto, companyId } = params;
    const presenter = this.presenterRegistry.getPresenter({
      resource: "customer-invoice",
      projection: "FULL",
    }) as CustomerInvoiceFullPresenter;

    // 1) Mapear DTO → props de dominio
    const dtoMapper = new CreateCustomerInvoicePropsMapper({ taxCatalog: this.taxCatalog });
    const dtoResult = dtoMapper.map(dto);
    if (dtoResult.isFailure) {
      return Result.fail(dtoResult.error);
    }

    const { props, id } = dtoResult.data;

    // 2) Construir entidad de dominio
    const buildResult = this.service.buildInvoiceInCompany(companyId, props, id);
    if (buildResult.isFailure) {
      return Result.fail(buildResult.error);
    }

    const newInvoice = buildResult.data;

    // 3) Ejecutar bajo transacción: verificar duplicado → persistir → ensamblar vista
    return this.transactionManager.complete(async (transaction: Transaction) => {
      try {
        const existsGuard = await this.ensureNotExists(companyId, id, transaction);
        if (existsGuard.isFailure) {
          return Result.fail(existsGuard.error);
        }

        const saveResult = await this.service.saveInvoice(newInvoice, transaction);
        if (saveResult.isFailure) {
          return Result.fail(saveResult.error);
        }

        const invoice = saveResult.data;
        const dto = presenter.toOutput(invoice);

        return Result.ok(dto);
      } catch (error: unknown) {
        return Result.fail(error as Error);
      }
    });
  }

  /**
    Verifica que no exista uana factura con el mismo id en la companyId.
  */
  private async ensureNotExists(
    companyId: UniqueID,
    id: UniqueID,
    transaction: Transaction
  ): Promise<Result<void, Error>> {
    const existsResult = await this.service.existsByIdInCompany(companyId, id, transaction);
    if (existsResult.isFailure) {
      return Result.fail(existsResult.error);
    }

    if (existsResult.data) {
      return Result.fail(new DuplicateEntityError("Customer invoice", "id", String(id)));
    }

    return Result.ok<void>(undefined);
  }
}
