import type { FormErrors } from "@mantine/form";
import { resolveCountryRecordID } from "@/lib/features/promo-code-campaigns/query";
import type {
  PromoCode,
  PromoCodeFormValues,
  SignupPromoCodeForm,
} from "@/lib/features/promo-codes/types";

const sanitizeList = (list: string[]) =>
  list.map((entry) => entry.trim()).filter(Boolean);

const normalizeEdmTemplateIDs = (raw: any): string[] => {
  const value = raw?.edmTemplateIDs ?? raw?.edm_template_id ?? [];
  if (Array.isArray(value)) {
    return value.map((entry) => String(entry));
  }
  if (value === null || value === undefined || value === "") return [];
  return [String(value)];
};

export const emptyValues: PromoCodeFormValues = {
  name: "",
  isActive: true,
  note: "",
  code: "",
  expiresAt: "",
  description: "",
  rewards: [
    {
      type: "POINTS",
      value: 1,
      name: "",
      ttl: null,
    },
  ],
  access_list: {
    membershipAccounts: [],
    membershipClubs: [],
    membershipAccountTypes: [],
  },
  signupPromoCodes: [
    {
      serviceCenterID: "",
      edmTemplateIDs: [],
      externalMembershipEntityID: "",
      membershipAccountTypeID: "",
      externalMembershipID: "",
      verificationEdmTemplateID: "",
      countryRecordIDs: [],
      viewpointChargeCenterID: "",
    },
  ],
};

export const emptyUpdateValues: PromoCodeFormValues = {
  name: "",
  isActive: true,
  note: "",
  code: "",
  expiresAt: "",
  description: "",
  rewards: [],
  access_list: {
    membershipAccounts: [],
    membershipClubs: [],
    membershipAccountTypes: [],
  },
  signupPromoCodes: [],
};

const getPromoIsActive = (promo: PromoCode) =>
  typeof promo.isActive === "boolean" ? promo.isActive : promo.is_active;

const getPromoExpiresAt = (promo: PromoCode) =>
  promo.expiresAt || promo.expires_at || "";

const normalizeSignupPromo = (raw: any): SignupPromoCodeForm => ({
  id: raw?.id ?? "",
  promoCodeID: raw?.promoCodeID ?? raw?.promo_code_id ?? "",
  serviceCenterID: raw?.serviceCenterID ?? raw?.service_center_id ?? "",
  edmTemplateIDs: normalizeEdmTemplateIDs(raw),
  externalMembershipEntityID:
    raw?.externalMembershipEntityID ?? raw?.external_membership_entity_id ?? "",
  membershipAccountTypeID:
    raw?.membershipAccountTypeID ?? raw?.membership_account_type_id ?? "",
  externalMembershipID:
    raw?.externalMembershipID ?? raw?.external_membership_id ?? "",
  verificationEdmTemplateID:
    raw?.verificationEdmTemplateID ?? raw?.verification_edm_template_id ?? "",
  // Write path sends country record uuids (countryRecordIDs); the read path
  // gives back resolved country NAMES under `countries`. Accept either and map
  // to uuids so the update diff compares like with like. Falls through as-is
  // until the country list is cached — PromoCodeForm re-resolves on load.
  countryRecordIDs: Array.isArray(
    raw?.countryRecordIDs ?? raw?.country_record_ids ?? raw?.countries,
  )
    ? (raw?.countryRecordIDs ?? raw?.country_record_ids ?? raw?.countries).map(
      (entry: unknown) => resolveCountryRecordID(String(entry)),
    )
    : [],
  viewpointChargeCenterID: String(
    raw?.viewpointChargeCenterID ?? raw?.viewpoint_charge_center_id ?? "",
  ),
  description: raw?.description ?? null,
  note: raw?.note ?? null,
  createdAt: raw?.createdAt ?? raw?.created_at ?? null,
  updatedAt: raw?.updatedAt ?? raw?.updated_at ?? null,
});

export const mapPromoToForm = (promo: PromoCode): PromoCodeFormValues => ({
  name: promo.name ?? "",
  isActive: getPromoIsActive(promo) ?? true,
  note: promo.note ?? "",
  code: promo.code ?? "",
  expiresAt: getPromoExpiresAt(promo) ?? "",
  description: promo.description ?? "",
  rewards:
    Array.isArray(promo.rewards) && promo.rewards.length > 0
      ? promo.rewards.map((reward) => ({
        type: String((reward as any)?.type ?? ""),
        value: Number((reward as any)?.value ?? 0),
        name: (reward as any)?.name ?? "",
        expiresAt:
          (reward as any)?.expiresAt ?? (reward as any)?.expires_at ?? null,
        currency: (reward as any)?.currency ?? null,
        slug: (reward as any)?.slug ?? null,
        // Read-only, so the form can show when an existing reward was created.
        createdAt:
          (reward as any)?.createdAt ?? (reward as any)?.created_at ?? null,
        ttl:
          typeof (reward as any)?.ttl === "number"
            ? (reward as any).ttl
            : null,
      }))
      : emptyValues.rewards,
  access_list: {
    membershipAccounts: (promo as any)?.access_list?.membershipAccounts ?? [],
    membershipClubs: (promo as any)?.access_list?.membershipClubs ?? [],
    membershipAccountTypes:
      (promo as any)?.access_list?.membershipAccountTypes ?? [],
  },
  signupPromoCodes: Array.isArray(promo.signupPromoCodes)
    ? promo.signupPromoCodes.map(normalizeSignupPromo)
    : Array.isArray((promo as any)?.signupPromos)
      ? (promo as any).signupPromos.map(normalizeSignupPromo)
      : Array.isArray((promo as any)?.signup_promo_codes)
        ? (promo as any).signup_promo_codes.map(normalizeSignupPromo)
        : [],
});

export const validateForm = (values: PromoCodeFormValues): FormErrors => {
  const errors: FormErrors = {};

  if (!values.name.trim()) errors.name = "Name is required";
  if (!values.code.trim()) errors.code = "Code is required";
  if (!values.note.trim()) errors.note = "Note is required";
  if (!values.description.trim()) {
    errors.description = "Description is required";
  }
  if (values.expiresAt && Number.isNaN(Date.parse(values.expiresAt))) {
    errors.expiresAt = "Invalid datetime format";
  }

  if (!values.rewards.length) {
    errors.rewards = "At least one reward is required";
  } else {
    values.rewards.forEach((reward, index) => {
      if (!reward.type?.trim()) {
        errors[`rewards.${index}.type`] = "Reward type is required";
      }
      if (Number.isNaN(reward.value) || reward.value < 0) {
        errors[`rewards.${index}.value`] = "Value must be 0 or greater";
      }
    });
  }

  if (!values.signupPromoCodes.length) {
    errors.signupPromoCodes = "At least one signup promo code is required";
  } else {
    values.signupPromoCodes.forEach((entry, index) => {
      if (!entry.serviceCenterID.trim()) {
        errors[`signupPromoCodes.${index}.serviceCenterID`] =
          "Service Center ID is required";
      }
      if (!entry.externalMembershipEntityID.trim()) {
        errors[`signupPromoCodes.${index}.externalMembershipEntityID`] =
          "External Membership Entity ID is required";
      }
      if (!entry.membershipAccountTypeID.trim()) {
        errors[`signupPromoCodes.${index}.membershipAccountTypeID`] =
          "Membership Account Type ID is required";
      }
      if (!sanitizeList(entry.edmTemplateIDs).length) {
        errors[`signupPromoCodes.${index}.edmTemplateIDs`] =
          "At least one EDM Template ID is required";
      }
    });
  }

  values.signupPromoCodes.forEach((entry, index) => {
    if (!entry.serviceCenterID.trim()) {
      errors[`signupPromoCodes.${index}.serviceCenterID`] =
        "Service Center ID is required";
    }
    if (!entry.externalMembershipEntityID.trim()) {
      errors[`signupPromoCodes.${index}.externalMembershipEntityID`] =
        "External Membership Entity ID is required";
    }
    if (!entry.membershipAccountTypeID.trim()) {
      errors[`signupPromoCodes.${index}.membershipAccountTypeID`] =
        "Membership Account Type ID is required";
    }
    if (!sanitizeList(entry.edmTemplateIDs).length) {
      errors[`signupPromoCodes.${index}.edmTemplateIDs`] =
        "At least one EDM Template ID is required";
    }
  });

  return errors;
};

export const validateUpdateForm = (values: PromoCodeFormValues): FormErrors => {
  const errors: FormErrors = {};

  if (!values.name?.trim()) errors.name = "Name is required";

  if (!values.note?.trim()) errors.note = "Note is required";
  if (!values.description?.trim()) {
    errors.description = "Description is required";
  }
  if (values.expiresAt && Number.isNaN(Date.parse(values.expiresAt))) {
    errors.expiresAt = "Invalid datetime format";
  }

  if (!values.rewards.length) {
    errors.rewards = "At least one reward is required";
  } else {
    values.rewards.forEach((reward, index) => {
      if (!reward.type?.trim()) {
        errors[`rewards.${index}.type`] = "Reward type is required";
      }
      if (Number.isNaN(reward.value) || reward.value < 0) {
        errors[`rewards.${index}.value`] = "Value must be 0 or greater";
      }
    });
  }

  if (!values.signupPromoCodes.length) {
    errors.signupPromoCodes = "At least one signup promo code is required";
  } else {
    values.signupPromoCodes.forEach((entry, index) => {
      if (!entry.serviceCenterID.trim()) {
        errors[`signupPromoCodes.${index}.serviceCenterID`] =
          "Service Center ID is required";
      }
      if (!entry.externalMembershipEntityID.trim()) {
        errors[`signupPromoCodes.${index}.externalMembershipEntityID`] =
          "External Membership Entity ID is required";
      }
      if (!entry.membershipAccountTypeID.trim()) {
        errors[`signupPromoCodes.${index}.membershipAccountTypeID`] =
          "Membership Account Type ID is required";
      }
      if (!sanitizeList(entry.edmTemplateIDs).length) {
        errors[`signupPromoCodes.${index}.edmTemplateIDs`] =
          "At least one EDM Template ID is required";
      }
    });
  }

  return errors;
};

const buildRewards = (rewards: PromoCodeFormValues["rewards"]) =>
  rewards
    .filter((reward) => reward.type.trim())
    .map((reward) => ({
      type: reward.type.trim(),
      value: reward.value,
      name: reward.name?.trim() || null,
      currency: reward.currency?.trim() || null,
      expiresAt: reward.expiresAt?.trim() || null,
      slug: reward.slug?.trim() || null,
      /*
       * `createdAt` is deliberately not sent. The API sets it and the schema rejects
       * it, and echoing a stored timestamp back would let an edit rewrite when the
       * reward was created.
       */
      ttl: typeof reward.ttl === "number" && !Number.isNaN(reward.ttl)
        ? reward.ttl
        : null,
    }));

const buildAccessList = (
  accessList: PromoCodeFormValues["access_list"],
) => ({
  membershipAccounts: sanitizeList(accessList.membershipAccounts),
  membershipClubs: sanitizeList(accessList.membershipClubs),
  membershipAccountTypes: sanitizeList(accessList.membershipAccountTypes),
});

const buildSignupPromoCodes = (
  list: PromoCodeFormValues["signupPromoCodes"],
) =>
  list.map((entry) => ({
    serviceCenterID: entry.serviceCenterID.trim(),
    edmTemplateIDs: sanitizeList(entry.edmTemplateIDs),
    externalMembershipEntityID: entry.externalMembershipEntityID.trim(),
    membershipAccountTypeID: entry.membershipAccountTypeID.trim(),
    externalMembershipID: (entry.externalMembershipID || "").trim(),
    verificationEdmTemplateID: (entry.verificationEdmTemplateID || "").trim(),
    countryRecordIDs: sanitizeList(entry.countryRecordIDs ?? []),
    viewpointChargeCenterID: (entry.viewpointChargeCenterID || "").trim(),
  }));

export const buildPayload = (values: PromoCodeFormValues) => {
  const payload: Record<string, any> = {
    name: values.name.trim(),
    isActive: values.isActive,
    code: values.code.trim(),
    rewards: buildRewards(values.rewards),
    access_list: buildAccessList(values.access_list),
    signupPromoCodes: buildSignupPromoCodes(values.signupPromoCodes),
  };

  if (values.note?.trim()) payload.note = values.note.trim();
  if (values.expiresAt?.trim()) payload.expiresAt = values.expiresAt.trim();
  if (values.description?.trim())
    payload.description = values.description.trim();

  return payload;
};

export const buildUpdatePayload = (
  values: PromoCodeFormValues,
  original?: PromoCodeFormValues,
) => {
  const payload: Record<string, unknown> = {};
  const differs = (a: unknown, b: unknown) =>
    JSON.stringify(a) !== JSON.stringify(b);

  const name = values.name.trim();
  if (name && (!original || name !== original.name.trim())) payload.name = name;


  /*
   * Note and description are only sent when non-empty, and that is deliberate: the
   * update schema is the create schema partialised, so if either field is present it
   * must still be at least one character. Sending "" would be rejected outright, so a
   * cleared note is dropped rather than turned into a failed save.
   */
  const note = values.note.trim();
  if (note && (!original || note !== original.note.trim())) payload.note = note;

  const description = values.description.trim();
  if (description && (!original || description !== original.description.trim()))
    payload.description = description;

  /*
   * Expiry, including its removal.
   *
   * Sent whenever it differs — as `null` when cleared, not omitted. The old condition
   * required a non-empty value, so clearing an expiry date built a payload without the
   * field and the API kept the old one: the code silently went on expiring. `expiresAt`
   * is nullable in the schema, so "never expires" is expressible and should be sent.
   */
  const expiresAt = values.expiresAt.trim();
  if (!original || expiresAt !== original.expiresAt.trim()) {
    payload.expiresAt = expiresAt || null;
  }

  if (!original || values.isActive !== original.isActive)
    payload.isActive = values.isActive;

  if (values.rewards.length) {
    const rewards = buildRewards(values.rewards);
    if (!original || differs(rewards, buildRewards(original.rewards)))
      payload.rewards = rewards;
  }

  /*
   * The access list, including emptying it.
   *
   * The previous guard also required at least one entry, which meant removing the last
   * membership restriction was dropped from the payload and the restriction stayed in
   * force — the operator saw an empty picker and a code that was still limited. All
   * three lists default to `[]` in the schema, so an empty access list is a valid state
   * and has to be sent to be applied.
   */
  const accessList = buildAccessList(values.access_list);
  if (!original || differs(accessList, buildAccessList(original.access_list))) {
    payload.access_list = accessList;
  }

  if (values.signupPromoCodes.length) {
    const signupPromoCodes = buildSignupPromoCodes(values.signupPromoCodes);
    if (
      !original ||
      differs(signupPromoCodes, buildSignupPromoCodes(original.signupPromoCodes))
    ) {
      payload.signupPromoCodes = signupPromoCodes;
    }
  }

  return payload;
};
