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 { CustomerInvoice, ICustomerInvoiceRepository } from "../../domain";
import {
  CustomerInvoiceListDTO,
  ICustomerInvoiceDomainMapper,
  ICustomerInvoiceListMapper,
} from "../mappers";
import { CustomerInvoiceItemTaxModel } from "./models/customer-invoice-item-tax.model";
import { CustomerInvoiceItemModel } from "./models/customer-invoice-item.model";
import { CustomerInvoiceTaxModel } from "./models/customer-invoice-tax.model";
import { CustomerInvoiceModel } from "./models/customer-invoice.model";

export class CustomerInvoiceRepository
  extends SequelizeRepository<CustomerInvoice>
  implements ICustomerInvoiceRepository
{
  // Listado por tenant con criteria saneada
  /* async searchInCompany(criteria: any, companyId: string): Promise<{
    rows: InvoiceListRow[];
    total: number;
    limit: number;
    offset: number;
  }> {
    const { where, order, limit, offset, attributes } = sanitizeListCriteria(criteria);

    // WHERE con scope de company
    const scopedWhere = { ...where, company_id: companyId };

    const options: FindAndCountOptions = {
      where: scopedWhere,
      order,
      limit,
      offset,
      attributes,
      raw: true,     // devolvemos objetos planos -> más rápido
      nest: false,
      distinct: true // por si en el futuro añadimos includes no duplicar count
    };

    const { rows, count } = await CustomerInvoiceModel.findAndCountAll(options);

    return {
      rows: rows as unknown as InvoiceListRow[],
      total: typeof count === "number" ? count : (count as any[]).length,
      limit,
      offset,
    };
  } */

  /**
   *
   * Crea una nueva factura.
   *
   * @param invoice - El agregado a guardar.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<void, Error>
   */
  async create(invoice: CustomerInvoice, transaction?: Transaction): Promise<Result<void, Error>> {
    try {
      const mapper: ICustomerInvoiceDomainMapper = this._registry.getDomainMapper({
        resource: "customer-invoice",
      });
      const dto = mapper.mapToPersistence(invoice);

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

      const { data } = dto;

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

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

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

      const { id, ...updatePayload } = dto.data;
      const [affected] = await CustomerInvoiceModel.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 invoice"
          )
        );
      }

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

  /**
   * Comprueba si existe una factura con un `id` dentro de una `company`.
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece la factura.
   * @param id - Identificador UUID de la factura.
   * @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 CustomerInvoiceModel.count({
        where: { id: id.toString(), company_id: companyId.toString() },
        transaction,
      });
      return Result.ok(Boolean(count > 0));
    } catch (error: unknown) {
      return Result.fail(translateSequelizeError(error));
    }
  }

  /**
   *
   * Busca una factura por su identificador único.
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece la factura.
   * @param id - UUID de la factura.
   * @param transaction - Transacción activa para la operación.
   * @returns Result<CustomerInvoice, Error>
   */
  async getByIdInCompany(
    companyId: UniqueID,
    id: UniqueID,
    transaction: Transaction
  ): Promise<Result<CustomerInvoice, Error>> {
    const { CustomerModel } = this._database.models;

    try {
      const mapper: ICustomerInvoiceDomainMapper = this._registry.getDomainMapper({
        resource: "customer-invoice",
      });

      const row = await CustomerInvoiceModel.findOne({
        where: { id: id.toString(), company_id: companyId.toString() },
        order: [[{ model: CustomerInvoiceItemModel, as: "items" }, "position", "ASC"]],
        include: [
          {
            model: CustomerModel,
            as: "current_customer",
            required: false, // false => LEFT JOIN
          },
          {
            model: CustomerInvoiceItemModel,
            as: "items",
            required: false,
            include: [
              {
                model: CustomerInvoiceItemTaxModel,
                as: "taxes",
                required: false,
              },
            ],
          },
          {
            model: CustomerInvoiceTaxModel,
            as: "taxes",
            required: false,
          },
        ],
        transaction,
      });

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

      const invoice = mapper.mapToDomain(row);
      return invoice;
    } catch (err: unknown) {
      return Result.fail(translateSequelizeError(err));
    }
  }

  /**
   *
   * Consulta facturas usando un objeto Criteria (filtros, orden, paginación).
   *
   * @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<CustomerInvoice[], Error>
   *
   * @see Criteria
   */
  public async findByCriteriaInCompany(
    companyId: UniqueID,
    criteria: Criteria,
    transaction: Transaction
  ): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>> {
    const { CustomerModel } = this._database.models;

    try {
      const mapper: ICustomerInvoiceListMapper = this._registry.getQueryMapper({
        resource: "customer-invoice",
        query: "LIST",
      });

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

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

      query.include = [
        {
          model: CustomerModel,
          as: "current_customer",
          required: false, // false => LEFT JOIN
        },

        {
          model: CustomerInvoiceTaxModel,
          as: "taxes",
          required: false,
        },
      ];

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

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

  /**
   *
   * Elimina o marca como eliminada una factura.
   *
   * @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
   * @param id - UUID de la factura 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 CustomerInvoiceModel.destroy({
        where: { id: id.toString(), company_id: companyId.toString() },
        transaction,
      });

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

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