import { DomainEntity } from "@/core/common/domain";
import { Collection, Result } from "@repo/rdx-utils";
import { Model } from "sequelize";

export type MapperParamsType = Record<string, any>;

interface IDomainMapper<TModel extends Model, TEntity extends DomainEntity<any>> {
  mapToDomain(source: TModel, params?: MapperParamsType): Result<TEntity, Error>;
  mapArrayToDomain(source: TModel[], params?: MapperParamsType): Result<Collection<TEntity>, Error>;
  mapArrayAndCountToDomain(
    source: TModel[],
    totalCount: number,
    params?: MapperParamsType
  ): Result<Collection<TEntity>, Error>;
}

interface IPersistenceMapper<TModelAttributes, TEntity extends DomainEntity<any>> {
  mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes;
  mapCollectionToPersistence(
    source: Collection<TEntity>,
    params?: MapperParamsType
  ): TModelAttributes[];
}

export interface ISequelizeMapper<
  TModel extends Model,
  TModelAttributes,
  TEntity extends DomainEntity<any>,
> extends IDomainMapper<TModel, TEntity>,
    IPersistenceMapper<TModelAttributes, TEntity> {}

export abstract class SequelizeMapper<
  TModel extends Model,
  TModelAttributes,
  TEntity extends DomainEntity<any>,
> implements ISequelizeMapper<TModel, TModelAttributes, TEntity>
{
  public abstract mapToDomain(source: TModel, params?: MapperParamsType): Result<TEntity, Error>;

  public mapArrayToDomain(
    source: TModel[],
    params?: MapperParamsType
  ): Result<Collection<TEntity>, Error> {
    return this.mapArrayAndCountToDomain(source, source.length, params);
  }

  public mapArrayAndCountToDomain(
    source: TModel[],
    totalCount: number,
    params?: MapperParamsType
  ): Result<Collection<TEntity>, Error> {
    try {
      const items = source.map(
        (value, index) => this.mapToDomain(value, { index, ...params }).data
      );
      return Result.ok(new Collection(items, totalCount));
    } catch (error) {
      return Result.fail(error as Error);
    }
  }

  public abstract mapToPersistence(source: TEntity, params?: MapperParamsType): TModelAttributes;

  public mapCollectionToPersistence(
    source: Collection<TEntity>,
    params?: MapperParamsType
  ): TModelAttributes[] {
    return source.map((value, index) => this.mapToPersistence(value, { index, ...params }));
  }

  protected safeMap<T>(operation: () => T, key: string): Result<T, Error> {
    try {
      return Result.ok(operation());
    } catch (error: unknown) {
      return Result.fail(error as Error);
    }
  }

  protected mapsValue(
    row: TModel,
    key: string,
    customMapFn: (value: any, params: MapperParamsType) => Result<any, Error>,
    params: MapperParamsType = { defaultValue: null }
  ): Result<any, Error> {
    return customMapFn(row?.dataValues[key] ?? params.defaultValue, params);
  }

  protected mapsAssociation(
    row: TModel,
    associationName: string,
    customMapper: IDomainMapper<any, any>,
    params: MapperParamsType = {}
  ): Result<any, Error> {
    if (!customMapper) {
      Result.fail(Error(`Custom mapper undefined for ${associationName}`));
    }

    const { filter, ...otherParams } = params;
    let associationRows = row?.dataValues[associationName] ?? [];

    if (filter) {
      associationRows = Array.isArray(associationRows)
        ? associationRows.filter(filter)
        : filter(associationRows);
    }

    return Array.isArray(associationRows)
      ? customMapper.mapArrayToDomain(associationRows, otherParams)
      : customMapper.mapToDomain(associationRows, otherParams);
  }
}
