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

import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import {
  City,
  Country,
  CurrencyCode,
  EmailAddress,
  LanguageCode,
  Name,
  PhoneNumber,
  PostalAddress,
  PostalCode,
  Province,
  Street,
  TINNumber,
  TaxCode,
  TextValue,
  URLAddress,
  UniqueID,
  extractOrPushError,
  maybeFromNullableVO,
  toNullable,
} from "@repo/rdx-ddd";
import { Collection, Result, isNullishOrEmpty } from "@repo/rdx-utils";
import { Customer, CustomerProps, CustomerStatus } from "../../../domain";
import { CustomerCreationAttributes, CustomerModel } from "../../sequelize";

export interface ICustomerDomainMapper
  extends ISequelizeDomainMapper<CustomerModel, CustomerCreationAttributes, Customer> {}

export class CustomerDomainMapper
  extends SequelizeDomainMapper<CustomerModel, CustomerCreationAttributes, Customer>
  implements ICustomerDomainMapper
{
  public mapToDomain(source: CustomerModel, params?: MapperParamsType): Result<Customer, Error> {
    try {
      const errors: ValidationErrorDetail[] = [];

      const customerId = extractOrPushError(UniqueID.create(source.id), "id", errors);
      const companyId = extractOrPushError(
        UniqueID.create(source.company_id),
        "company_id",
        errors
      );

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      // source.default_taxes is stored as a comma-separated string
      const defaultTaxes = new Collection<TaxCode>();
      if (!isNullishOrEmpty(source.default_taxes)) {
        source.default_taxes!.split(",").map((taxCode, index) => {
          const tax = extractOrPushError(TaxCode.create(taxCode), `default_taxes.${index}`, errors);
          if (tax) {
            defaultTaxes.add(tax!);
          }
        });
      }

      // Now, create the PostalAddress VO
      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 props mapping failed", errors));
      }

      const customerProps: CustomerProps = {
        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!,

        legalRecord: legalRecord!,
        defaultTaxes: defaultTaxes!,
        languageCode: languageCode!,
        currencyCode: currencyCode!,
      };

      return Customer.create(customerProps, customerId);
    } catch (err: unknown) {
      return Result.fail(err as Error);
    }
  }

  public mapToPersistence(
    source: Customer,
    params?: MapperParamsType
  ): Result<CustomerCreationAttributes, Error> {
    const customerValues: Partial<CustomerCreationAttributes> = {
      id: source.id.toPrimitive(),
      company_id: source.companyId.toPrimitive(),

      reference: toNullable(source.reference, (reference) => reference.toPrimitive()),
      is_company: source.isCompany,
      name: source.name.toPrimitive(),
      trade_name: toNullable(source.tradeName, (tradeName) => tradeName.toPrimitive()),
      tin: toNullable(source.tin, (tin) => tin.toPrimitive()),

      email_primary: toNullable(source.emailPrimary, (email) => email.toPrimitive()),
      email_secondary: toNullable(source.emailSecondary, (email) => email.toPrimitive()),
      phone_primary: toNullable(source.phonePrimary, (phone) => phone.toPrimitive()),
      phone_secondary: toNullable(source.phoneSecondary, (phone) => phone.toPrimitive()),
      mobile_primary: toNullable(source.mobilePrimary, (mobile) => mobile.toPrimitive()),
      mobile_secondary: toNullable(source.mobileSecondary, (mobile) => mobile.toPrimitive()),
      fax: toNullable(source.fax, (fax) => fax.toPrimitive()),
      website: toNullable(source.website, (website) => website.toPrimitive()),

      legal_record: toNullable(source.legalRecord, (legalRecord) => legalRecord.toPrimitive()),
      default_taxes: source.defaultTaxes.map((taxItem) => taxItem.toPrimitive()).join(", "),

      status: source.isActive ? "active" : "inactive",
      language_code: source.languageCode.toPrimitive(),
      currency_code: source.currencyCode.toPrimitive(),
    };

    if (source.address) {
      Object.assign(customerValues, {
        street: toNullable(source.address.street, (street) => street.toPrimitive()),
        street2: toNullable(source.address.street2, (street2) => street2.toPrimitive()),
        city: toNullable(source.address.city, (city) => city.toPrimitive()),
        province: toNullable(source.address.province, (province) => province.toPrimitive()),
        postal_code: toNullable(source.address.postalCode, (postalCode) =>
          postalCode.toPrimitive()
        ),
        country: toNullable(source.address.country, (country) => country.toPrimitive()),
      });
    }

    return Result.ok<CustomerCreationAttributes>(customerValues as CustomerCreationAttributes);
  }
}
