// application/customer-application-service.ts
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 { Customer, CustomerPatchProps, ICustomerRepository } from "../domain";
import { CustomerListDTO } from "../infrastructure";

export class CustomerApplicationService {
  constructor(private readonly repository: ICustomerRepository) {}

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

    return this.getCustomerByIdInCompany(companyId, customer.id, transaction);
  }

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

    return this.getCustomerByIdInCompany(companyId, customer.id, transaction);
  }

  /**
   * Actualiza parcialmente un cliente existente con nuevos datos.
   * Solo en memoria. No lo guarda en el repositorio.
   *
   * @param companyId - Identificador de la empresa a la que pertenece el cliente.
   * @param customerId - Identificador del cliente a actualizar.
   * @param changes - Subconjunto de props válidas para aplicar.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<Customer, Error> - Cliente actualizado o error.
   */
  async patchCustomerByIdInCompany(
    companyId: UniqueID,
    customerId: UniqueID,
    changes: CustomerPatchProps,
    transaction?: Transaction
  ): Promise<Result<Customer, Error>> {
    const customerResult = await this.getCustomerByIdInCompany(companyId, customerId, transaction);
    if (customerResult.isFailure) {
      return Result.fail(customerResult.error);
    }

    const updated = customerResult.data.update(changes);
    if (updated.isFailure) {
      return Result.fail(updated.error);
    }

    return Result.ok(updated.data);
  }

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

  /**
   *
   * Comprueba si existe o no en persistencia un cliente con el ID proporcionado
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
   * @param customerId - Identificador UUID del cliente
   * @param transaction - Transacción activa para la operación.
   * @returns Result<Boolean, Error> - Existe el cliente o no.
   */
  async existsByIdInCompany(
    companyId: UniqueID,
    customerId: UniqueID,
    transaction?: Transaction
  ): Promise<Result<boolean, Error>> {
    return this.repository.existsByIdInCompany(companyId, customerId, transaction);
  }

  /**
   * Recupera un cliente por su identificador único.
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
   * @param customerId - Identificador UUID del cliente.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<Customer, Error> - Cliente encontrado o error.
   */
  async getCustomerByIdInCompany(
    companyId: UniqueID,
    customerId: UniqueID,
    transaction?: Transaction
  ): Promise<Result<Customer, Error>> {
    return this.repository.getByIdInCompany(companyId, customerId, transaction);
  }

  /**
   * Obtiene una colección de clientes 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<Customer>, Error> - Colección de clientes o error.
   */
  async findCustomerByCriteriaInCompany(
    companyId: UniqueID,
    criteria: Criteria,
    transaction?: Transaction
  ): Promise<Result<Collection<CustomerListDTO>, Error>> {
    return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
  }
}
