import { Criteria } from "@repo/rdx-criteria/server";
import { UniqueID } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize";
import {
  CustomerInvoice,
  CustomerInvoicePatchProps,
  CustomerInvoiceProps,
} from "../domain/aggregates";
import { ICustomerInvoiceRepository } from "../domain/repositories";
import { CustomerInvoiceListDTO } from "../infrastructure";

export class CustomerInvoiceApplicationService {
  constructor(private readonly repository: ICustomerInvoiceRepository) {}

  /**
   * Construye un nuevo agregado CustomerInvoice a partir de props validadas.
   *
   * @param companyId - Identificador de la empresa a la que pertenece el cliente.
   * @param props - Las propiedades ya validadas para crear la factura.
   * @param invoiceId - Identificador UUID de la factura (opcional).
   * @returns Result<CustomerInvoice, Error> - El agregado construido o un error si falla la creación.
   */
  buildInvoiceInCompany(
    companyId: UniqueID,
    props: Omit<CustomerInvoiceProps, "companyId">,
    invoiceId?: UniqueID
  ): Result<CustomerInvoice, Error> {
    return CustomerInvoice.create({ ...props, companyId }, invoiceId);
  }

  /**
   * Guarda una nueva factura y devuelve la factura guardada.
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
   * @param invoice - El agregado a guardar.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<CustomerInvoice, Error> - El agregado guardado o un error si falla la operación.
   */
  async createInvoiceInCompany(
    companyId: UniqueID,
    invoice: CustomerInvoice,
    transaction: Transaction
  ): Promise<Result<CustomerInvoice, Error>> {
    const result = await this.repository.create(invoice, transaction);
    if (result.isFailure) {
      return Result.fail(result.error);
    }

    return this.getInvoiceByIdInCompany(companyId, invoice.id, transaction);
  }

  /**
   * Actualiza una factura existente y devuelve la factura actualizada.
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
   * @param invoice - El agregado a guardar.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<CustomerInvoice, Error> - El agregado guardado o un error si falla la operación.
   */
  async updateInvoiceInCompany(
    companyId: UniqueID,
    invoice: CustomerInvoice,
    transaction: Transaction
  ): Promise<Result<CustomerInvoice, Error>> {
    const result = await this.repository.update(invoice, transaction);
    if (result.isFailure) {
      return Result.fail(result.error);
    }

    return this.getInvoiceByIdInCompany(companyId, invoice.id, transaction);
  }

  /**
   *
   * Comprueba si existe o no en persistencia una factura con el ID proporcionado
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
   * @param invoiceId - Identificador UUID de la factura.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<Boolean, Error> - Existe la factura o no.
   */

  async existsByIdInCompany(
    companyId: UniqueID,
    invoiceId: UniqueID,
    transaction?: Transaction
  ): Promise<Result<boolean, Error>> {
    return this.repository.existsByIdInCompany(companyId, invoiceId, transaction);
  }

  /**
   * Obtiene una colección de facturas que cumplen con los filtros definidos en un objeto Criteria.
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
   * @param criteria - Objeto con condiciones de filtro, paginación y orden.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<Collection<CustomerInvoiceListDTO>, Error> - Colección de facturas o error.
   */
  async findInvoiceByCriteriaInCompany(
    companyId: UniqueID,
    criteria: Criteria,
    transaction?: Transaction
  ): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>> {
    return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
  }

  /**
   * Recupera una factura por su identificador único.
   *
   * @param invoiceId - Identificador UUID de la factura.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<CustomerInvoice, Error> - Factura encontrada o error.
   */
  async getInvoiceByIdInCompany(
    companyId: UniqueID,
    invoiceId: UniqueID,
    transaction?: Transaction
  ): Promise<Result<CustomerInvoice>> {
    return await this.repository.getByIdInCompany(companyId, invoiceId, transaction);
  }

  /**
   * Actualiza parcialmente una factura existente con nuevos datos.
   * No lo guarda en el repositorio.
   *
   * @param companyId - Identificador de la empresa a la que pertenece el cliente.
   * @param invoiceId - Identificador de la factura a actualizar.
   * @param changes - Subconjunto de props válidas para aplicar.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<CustomerInvoice, Error> - Factura actualizada o error.
   */
  async patchInvoiceByIdInCompany(
    companyId: UniqueID,
    invoiceId: UniqueID,
    changes: CustomerInvoicePatchProps,
    transaction?: Transaction
  ): Promise<Result<CustomerInvoice, Error>> {
    const invoiceResult = await this.getInvoiceByIdInCompany(companyId, invoiceId, transaction);

    if (invoiceResult.isFailure) {
      return Result.fail(invoiceResult.error);
    }

    const updated = invoiceResult.data.update(changes);

    if (updated.isFailure) {
      return Result.fail(updated.error);
    }

    return Result.ok(updated.data);
  }

  /**
   * Elimina (o marca como eliminada) una factura según su ID.
   *
   * @param companyId - Identificador de la empresa a la que pertenece el cliente.
   * @param invoiceId - Identificador UUID de la factura.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<boolean, Error> - Resultado de la operación.
   */
  async deleteInvoiceByIdInCompany(
    companyId: UniqueID,
    invoiceId: UniqueID,
    transaction?: Transaction
  ): Promise<Result<boolean, Error>> {
    return this.repository.deleteByIdInCompany(companyId, invoiceId, transaction);
  }
}
