import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } from "@erp/core/api";
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
import { UniqueID } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize";
import { Customer, ICustomerRepository } from "../../../domain";
import { CustomerListDTO, ICustomerDomainMapper, ICustomerListMapper } from "../../mappers";
import { CustomerModel } from "../models/customer.model";

export class CustomerRepository
  extends SequelizeRepository<Customer>
  implements ICustomerRepository
{
  /**
   *
   * Guarda un nuevo cliente o actualiza uno existente.
   *
   * @param customer - El cliente a guardar.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<Customer, Error>
   */
  async save(customer: Customer, transaction: Transaction): Promise<Result<Customer, Error>> {
    try {
      const mapper: ICustomerDomainMapper = this._registry.getDomainMapper({
        resource: "customer",
      });

      const mapperData = mapper.mapToPersistence(customer);

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

      const { data } = mapperData;

      const [instance] = await CustomerModel.upsert(data, { transaction, returning: true });
      const savedCustomer = mapper.mapToDomain(instance);
      return savedCustomer;
    } catch (err: unknown) {
      return Result.fail(translateSequelizeError(err));
    }
  }

  /**
   * Comprueba si existe un Customer con un `id` dentro de una `company`.
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
   * @param id - Identificador UUID del cliente.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<boolean, Error>
   */
  async existsByIdInCompany(
    companyId: UniqueID,
    id: UniqueID,
    transaction?: Transaction
  ): Promise<Result<boolean, Error>> {
    try {
      const count = await CustomerModel.count({
        where: { id: id.toString(), company_id: companyId.toString() },
        transaction,
      });
      return Result.ok(Boolean(count > 0));
    } catch (error: any) {
      return Result.fail(translateSequelizeError(error));
    }
  }

  /**
   * Recupera un cliente por su ID y companyId.
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
   * @param id - Identificador UUID del cliente.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<Customer, Error>
   */
  async getByIdInCompany(
    companyId: UniqueID,
    id: UniqueID,
    transaction?: Transaction
  ): Promise<Result<Customer, Error>> {
    try {
      const mapper: ICustomerDomainMapper = this._registry.getDomainMapper({
        resource: "customer",
      });

      const row = await CustomerModel.findOne({
        where: { id: id.toString(), company_id: companyId.toString() },
        transaction,
      });

      if (!row) {
        return Result.fail(new EntityNotFoundError("Customer", "id", id.toString()));
      }

      const customer = mapper.mapToDomain(row);
      return customer;
    } catch (error: any) {
      return Result.fail(translateSequelizeError(error));
    }
  }

  /**
   * Recupera múltiples customers dentro de una empresa según un criterio dinámico (búsqueda, paginación, etc.).
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
   * @param criteria - Criterios de búsqueda.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<Collection<Customer>, Error>
   *
   * @see Criteria
   */
  async findByCriteriaInCompany(
    companyId: UniqueID,
    criteria: Criteria,
    transaction?: Transaction
  ): Promise<Result<Collection<CustomerListDTO>, Error>> {
    try {
      const mapper: ICustomerListMapper = this._registry.getQueryMapper({
        resource: "customer",
        query: "LIST",
      });

      const converter = new CriteriaToSequelizeConverter();
      const query = converter.convert(criteria);

      query.where = {
        ...query.where,
        company_id: companyId.toString(),
      };

      const { rows, count } = await CustomerModel.findAndCountAll({
        ...query,
        transaction,
      });

      return mapper.mapToDTOCollection(rows, count);
    } catch (err: unknown) {
      return Result.fail(translateSequelizeError(err));
    }
  }

  /**
   *
   * Elimina o marca como eliminado un cliente.
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
   * @param id - UUID del cliente a eliminar.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<void, Error>
   */
  async deleteByIdInCompany(
    companyId: UniqueID,
    id: UniqueID,
    transaction: Transaction
  ): Promise<Result<void>> {
    try {
      const deleted = await CustomerModel.destroy({
        where: { id: id.toString(), company_id: companyId.toString() },
        transaction,
      });

      return Result.ok<void>();
    } catch (err: unknown) {
      return Result.fail(translateSequelizeError(err));
    }
  }
}
