import { ValidationErrorCollection, ValidationErrorDetail } from "@repo/rdx-ddd";

import {
  City,
  Country,
  CurrencyCode,
  EmailAddress,
  LanguageCode,
  Name,
  PhoneNumber,
  PostalAddress,
  PostalCode,
  Province,
  Street,
  TINNumber,
  TextValue,
  URLAddress,
  UniqueID,
  extractOrPushError,
  maybeFromNullableVO,
} from "@repo/rdx-ddd";

import { ISequelizeQueryMapper, MapperParamsType, SequelizeQueryMapper } from "@erp/core/api";

import { Maybe, Result } from "@repo/rdx-utils";
import { CustomerStatus } from "../../../domain";
import { CustomerModel } from "../../sequelize";

export type CustomerListDTO = {
  id: UniqueID;
  companyId: UniqueID;
  status: CustomerStatus;
  reference: Maybe<Name>;

  isCompany: boolean;
  name: Name;
  tradeName: Maybe<Name>;
  tin: Maybe<TINNumber>;

  address: PostalAddress;

  emailPrimary: Maybe<EmailAddress>;
  emailSecondary: Maybe<EmailAddress>;
  phonePrimary: Maybe<PhoneNumber>;
  phoneSecondary: Maybe<PhoneNumber>;
  mobilePrimary: Maybe<PhoneNumber>;
  mobileSecondary: Maybe<PhoneNumber>;

  fax: Maybe<PhoneNumber>;
  website: Maybe<URLAddress>;

  languageCode: LanguageCode;
  currencyCode: CurrencyCode;
};

export interface ICustomerListMapper
  extends ISequelizeQueryMapper<CustomerModel, CustomerListDTO> {}

export class CustomerListMapper
  extends SequelizeQueryMapper<CustomerModel, CustomerListDTO>
  implements ICustomerListMapper
{
  public mapToDTO(raw: CustomerModel, params?: MapperParamsType): Result<CustomerListDTO, Error> {
    const errors: ValidationErrorDetail[] = [];

    // 1) Valores escalares (atributos generales)
    const customerId = extractOrPushError(UniqueID.create(raw.id), "id", errors);
    const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);

    const isCompany = raw.is_company;
    const status = extractOrPushError(CustomerStatus.create(raw.status), "status", errors);
    const reference = extractOrPushError(
      maybeFromNullableVO(raw.reference, (value) => Name.create(value)),
      "reference",
      errors
    );

    const name = extractOrPushError(Name.create(raw.name), "name", errors);

    const tradeName = extractOrPushError(
      maybeFromNullableVO(raw.trade_name, (value) => Name.create(value)),
      "trade_name",
      errors
    );

    const tinNumber = extractOrPushError(
      maybeFromNullableVO(raw.tin, (value) => TINNumber.create(value)),
      "tin",
      errors
    );

    const street = extractOrPushError(
      maybeFromNullableVO(raw.street, (value) => Street.create(value)),
      "street",
      errors
    );

    const street2 = extractOrPushError(
      maybeFromNullableVO(raw.street2, (value) => Street.create(value)),
      "street2",
      errors
    );

    const city = extractOrPushError(
      maybeFromNullableVO(raw.city, (value) => City.create(value)),
      "city",
      errors
    );

    const province = extractOrPushError(
      maybeFromNullableVO(raw.province, (value) => Province.create(value)),
      "province",
      errors
    );

    const postalCode = extractOrPushError(
      maybeFromNullableVO(raw.postal_code, (value) => PostalCode.create(value)),
      "postal_code",
      errors
    );

    const country = extractOrPushError(
      maybeFromNullableVO(raw.country, (value) => Country.create(value)),
      "country",
      errors
    );

    const emailPrimaryAddress = extractOrPushError(
      maybeFromNullableVO(raw.email_primary, (value) => EmailAddress.create(value)),
      "email_primary",
      errors
    );

    const emailSecondaryAddress = extractOrPushError(
      maybeFromNullableVO(raw.email_secondary, (value) => EmailAddress.create(value)),
      "email_secondary",
      errors
    );

    const phonePrimaryNumber = extractOrPushError(
      maybeFromNullableVO(raw.phone_primary, (value) => PhoneNumber.create(value)),
      "phone_primary",
      errors
    );

    const phoneSecondaryNumber = extractOrPushError(
      maybeFromNullableVO(raw.phone_secondary, (value) => PhoneNumber.create(value)),
      "phone_secondary",
      errors
    );

    const mobilePrimaryNumber = extractOrPushError(
      maybeFromNullableVO(raw.mobile_primary, (value) => PhoneNumber.create(value)),
      "mobile_primary",
      errors
    );

    const mobileSecondaryNumber = extractOrPushError(
      maybeFromNullableVO(raw.mobile_secondary, (value) => PhoneNumber.create(value)),
      "mobile_secondary",
      errors
    );

    const faxNumber = extractOrPushError(
      maybeFromNullableVO(raw.fax, (value) => PhoneNumber.create(value)),
      "fax",
      errors
    );

    const website = extractOrPushError(
      maybeFromNullableVO(raw.website, (value) => URLAddress.create(value)),
      "website",
      errors
    );

    const legalRecord = extractOrPushError(
      maybeFromNullableVO(raw.legal_record, (value) => TextValue.create(value)),
      "legal_record",
      errors
    );

    const languageCode = extractOrPushError(
      LanguageCode.create(raw.language_code),
      "language_code",
      errors
    );

    const currencyCode = extractOrPushError(
      CurrencyCode.create(raw.currency_code),
      "currency_code",
      errors
    );

    // Valores compuestos (objetos de valor / agregados)
    const postalAddressProps = {
      street: street!,
      street2: street2!,
      city: city!,
      postalCode: postalCode!,
      province: province!,
      country: country!,
    };

    const postalAddress = extractOrPushError(
      PostalAddress.create(postalAddressProps),
      "address",
      errors
    );

    // Si hubo errores de mapeo, devolvemos colección de validación
    if (errors.length > 0) {
      return Result.fail(
        new ValidationErrorCollection("Customer mapping failed [mapToDTO]", errors)
      );
    }

    return Result.ok<CustomerListDTO>({
      id: customerId!,
      companyId: companyId!,
      status: status!,
      reference: reference!,

      isCompany: isCompany,
      name: name!,
      tradeName: tradeName!,
      tin: tinNumber!,

      address: postalAddress!,

      emailPrimary: emailPrimaryAddress!,
      emailSecondary: emailSecondaryAddress!,
      phonePrimary: phonePrimaryNumber!,
      phoneSecondary: phoneSecondaryNumber!,
      mobilePrimary: mobilePrimaryNumber!,
      mobileSecondary: mobileSecondaryNumber!,
      fax: faxNumber!,
      website: website!,

      languageCode: languageCode!,
      currencyCode: currencyCode!,
    });
  }
}
