import {
  DomainError,
  ValidationErrorCollection,
  ValidationErrorDetail,
  extractOrPushError,
} from "@repo/rdx-ddd";
import { UpdateCustomerByIdRequestDTO } from "../../../../common";
import { CustomerPatchProps } from "../../../domain";

import {
  City,
  Country,
  CurrencyCode,
  EmailAddress,
  LanguageCode,
  Name,
  PhoneNumber,
  PostalAddressPatchProps,
  PostalCode,
  Province,
  Street,
  TINNumber,
  TaxCode,
  TextValue,
  URLAddress,
  maybeFromNullableVO,
} from "@repo/rdx-ddd";
import { Collection, Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils";

/**
 * mapDTOToUpdateCustomerPatchProps
 * Convierte el DTO a las props validadas (CustomerProps).
 * No construye directamente el agregado.
 * Tri-estado:
 *  - campo omitido → no se cambia
 *  - campo con valor null/"" → se quita el valor -> set(None()),
 *  - campo con valor no-vacío → se pone el nuevo valor -> set(Some(VO)).
 *
 * @param dto - DTO con los datos a cambiar en el cliente
 * @returns Cambios en las propiedades del cliente
 *
 */

export function mapDTOToUpdateCustomerPatchProps(dto: UpdateCustomerByIdRequestDTO) {
  try {
    const errors: ValidationErrorDetail[] = [];
    const customerPatchProps: CustomerPatchProps = {};

    toPatchField(dto.reference).ifSet((reference) => {
      customerPatchProps.reference = extractOrPushError(
        maybeFromNullableVO(reference, (value) => Name.create(value)),
        "reference",
        errors
      );
    });

    toPatchField(dto.is_company).ifSet((is_company) => {
      if (isNullishOrEmpty(is_company)) {
        errors.push({ path: "is_company", message: "is_company cannot be empty" });
        return;
      }
      customerPatchProps.isCompany = extractOrPushError(
        Result.ok(Boolean(is_company!)),
        "is_company",
        errors
      );
    });

    toPatchField(dto.name).ifSet((name) => {
      if (isNullishOrEmpty(name)) {
        errors.push({ path: "name", message: "Name cannot be empty" });
        return;
      }
      customerPatchProps.name = extractOrPushError(Name.create(name!), "name", errors);
    });

    toPatchField(dto.trade_name).ifSet((trade_name) => {
      customerPatchProps.tradeName = extractOrPushError(
        maybeFromNullableVO(trade_name, (value) => Name.create(value)),
        "trade_name",
        errors
      );
    });

    toPatchField(dto.tin).ifSet((tin) => {
      customerPatchProps.tin = extractOrPushError(
        maybeFromNullableVO(tin, (value) => TINNumber.create(value)),
        "tin",
        errors
      );
    });

    toPatchField(dto.email_primary).ifSet((email_primary) => {
      customerPatchProps.emailPrimary = extractOrPushError(
        maybeFromNullableVO(email_primary, (value) => EmailAddress.create(value)),
        "email_primary",
        errors
      );
    });

    toPatchField(dto.email_secondary).ifSet((email_secondary) => {
      customerPatchProps.emailSecondary = extractOrPushError(
        maybeFromNullableVO(email_secondary, (value) => EmailAddress.create(value)),
        "email_secondary",
        errors
      );
    });

    toPatchField(dto.mobile_primary).ifSet((mobile_primary) => {
      customerPatchProps.mobilePrimary = extractOrPushError(
        maybeFromNullableVO(mobile_primary, (value) => PhoneNumber.create(value)),
        "mobile_primary",
        errors
      );
    });

    toPatchField(dto.mobile_secondary).ifSet((mobile_secondary) => {
      customerPatchProps.mobilePrimary = extractOrPushError(
        maybeFromNullableVO(mobile_secondary, (value) => PhoneNumber.create(value)),
        "mobile_secondary",
        errors
      );
    });

    toPatchField(dto.phone_primary).ifSet((phone_primary) => {
      customerPatchProps.phonePrimary = extractOrPushError(
        maybeFromNullableVO(phone_primary, (value) => PhoneNumber.create(value)),
        "phone_primary",
        errors
      );
    });

    toPatchField(dto.phone_secondary).ifSet((phone_secondary) => {
      customerPatchProps.phoneSecondary = extractOrPushError(
        maybeFromNullableVO(phone_secondary, (value) => PhoneNumber.create(value)),
        "phone_secondary",
        errors
      );
    });

    toPatchField(dto.fax).ifSet((fax) => {
      customerPatchProps.fax = extractOrPushError(
        maybeFromNullableVO(fax, (value) => PhoneNumber.create(value)),
        "fax",
        errors
      );
    });

    toPatchField(dto.website).ifSet((website) => {
      customerPatchProps.website = extractOrPushError(
        maybeFromNullableVO(website, (value) => URLAddress.create(value)),
        "website",
        errors
      );
    });

    toPatchField(dto.legal_record).ifSet((legalRecord) => {
      customerPatchProps.legalRecord = extractOrPushError(
        maybeFromNullableVO(legalRecord, (value) => TextValue.create(value)),
        "legal_record",
        errors
      );
    });

    toPatchField(dto.language_code).ifSet((languageCode) => {
      if (isNullishOrEmpty(languageCode)) {
        errors.push({ path: "language_code", message: "Language code cannot be empty" });
        return;
      }

      customerPatchProps.languageCode = extractOrPushError(
        LanguageCode.create(languageCode!),
        "language_code",
        errors
      );
    });

    toPatchField(dto.currency_code).ifSet((currencyCode) => {
      if (isNullishOrEmpty(currencyCode)) {
        errors.push({ path: "currency_code", message: "Currency code cannot be empty" });
        return;
      }

      customerPatchProps.currencyCode = extractOrPushError(
        CurrencyCode.create(currencyCode!),
        "currency_code",
        errors
      );
    });

    // Default taxes
    const defaultTaxesCollection = new Collection<TaxCode>();
    toPatchField(dto.default_taxes).ifSet((defaultTaxes) => {
      customerPatchProps.defaultTaxes = defaultTaxesCollection;

      if (isNullishOrEmpty(defaultTaxes)) {
        return;
      }

      defaultTaxes!.forEach((taxCode, index) => {
        const tax = extractOrPushError(TaxCode.create(taxCode), `default_taxes.${index}`, errors);
        if (tax && customerPatchProps.defaultTaxes) {
          customerPatchProps.defaultTaxes.add(tax);
        }
      });
    });

    // PostalAddress
    const addressPatchProps = mapDTOToUpdatePostalAddressPatchProps(dto, errors);
    if (addressPatchProps) {
      customerPatchProps.address = addressPatchProps;
    }

    if (errors.length > 0) {
      return Result.fail(
        new ValidationErrorCollection("Customer props mapping failed (update)", errors)
      );
    }

    return Result.ok(customerPatchProps);
  } catch (err: unknown) {
    return Result.fail(new DomainError("Customer props mapping failed", { cause: err }));
  }
}

function mapDTOToUpdatePostalAddressPatchProps(
  dto: UpdateCustomerByIdRequestDTO,
  errors: ValidationErrorDetail[]
): PostalAddressPatchProps | undefined {
  const postalAddressPatchProps: PostalAddressPatchProps = {};

  toPatchField(dto.street).ifSet((street) => {
    postalAddressPatchProps.street = extractOrPushError(
      maybeFromNullableVO(street, (value) => Street.create(value)),
      "street",
      errors
    );
  });

  toPatchField(dto.street2).ifSet((street2) => {
    postalAddressPatchProps.street2 = extractOrPushError(
      maybeFromNullableVO(street2, (value) => Street.create(value)),
      "street2",
      errors
    );
  });

  toPatchField(dto.city).ifSet((city) => {
    postalAddressPatchProps.city = extractOrPushError(
      maybeFromNullableVO(city, (value) => City.create(value)),
      "city",
      errors
    );
  });

  toPatchField(dto.province).ifSet((province) => {
    postalAddressPatchProps.province = extractOrPushError(
      maybeFromNullableVO(province, (value) => Province.create(value)),
      "province",
      errors
    );
  });

  toPatchField(dto.postal_code).ifSet((postalCode) => {
    postalAddressPatchProps.postalCode = extractOrPushError(
      maybeFromNullableVO(postalCode, (value) => PostalCode.create(value)),
      "postal_code",
      errors
    );
  });

  toPatchField(dto.country).ifSet((country) => {
    postalAddressPatchProps.country = extractOrPushError(
      maybeFromNullableVO(country, (value) => Country.create(value)),
      "country",
      errors
    );
  });

  return Object.keys(postalAddressPatchProps).length > 0 ? postalAddressPatchProps : undefined;
}
