import {
  EntityNotFoundError,
  InfrastructureRepositoryError,
  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
{
  /**
   *
   * Crea un nuevo cliente
   *
   * @param customer - El cliente nuevo a guardar.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<void, Error>
   */
  async create(customer: Customer, transaction?: Transaction): Promise<Result<void, Error>> {
    try {
      const mapper: ICustomerDomainMapper = this._registry.getDomainMapper({
        resource: "customer",
      });
      const dto = mapper.mapToPersistence(customer);

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

      const { data } = dto;

      await CustomerModel.create(data, {
        include: [{ all: true }],
        transaction,
      });

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

  /**
   * Actualiza un cliente existente.
   *
   * @param customer - El cliente a actualizar.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<void, Error>
   */
  async update(customer: Customer, transaction?: Transaction): Promise<Result<void, Error>> {
    try {
      const mapper: ICustomerDomainMapper = this._registry.getDomainMapper({
        resource: "customer-invoice",
      });
      const dto = mapper.mapToPersistence(customer);

      const { id, ...updatePayload } = dto.data;
      const [affected] = await CustomerModel.update(updatePayload, {
        where: { id /*, version */ },
        //fields: Object.keys(updatePayload),
        transaction,
        individualHooks: true,
      });

      if (affected === 0) {
        return Result.fail(
          new InfrastructureRepositoryError("Concurrency conflict or not found update customer")
        );
      }

      return Result.ok();
    } 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: unknown) {
      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: unknown) {
      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, {
        searchableFields: [
          "name",
          "trade_name",
          "reference",
          "tin",
          "email_primary",
          "mobile_primary",
        ],
        database: this._database,
      });

      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<boolean, Error>
   */
  async deleteByIdInCompany(
    companyId: UniqueID,
    id: UniqueID,
    transaction: Transaction
  ): Promise<Result<boolean, Error>> {
    try {
      const deleted = await CustomerModel.destroy({
        where: { id: id.toString(), company_id: companyId.toString() },
        transaction,
      });

      if (deleted === 0) {
        return Result.fail(new EntityNotFoundError("Customer", "id", id.toString()));
      }

      return Result.ok(true);
    } catch (err: unknown) {
      return Result.fail(translateSequelizeError(err));
    }
  }
}
