import {
  orderConfigurationsSchema,
  orderSelectionsSchema,
  type OrderConfigurations,
  type OrderSelections,
  type OrderItemSummary,
  type OrderDisplayItem,
  type OrderDisplayItemCategory,
  type OrderDisplayItemCharge,
  type OrderDisplayItemChargeKind,
} from "./schema";
import { ORDER_TYPE } from "./contract";
import type { CheckoutTotals } from "./contract";
import type { SimConfigureFormData } from "../sim";
import type { WhmcsOrderItem } from "./providers/whmcs/raw.types";

export interface BuildSimOrderConfigurationsOptions {
  /**
   * Optional fallback phone number when the SIM form does not include it directly.
   * Useful for flows where the phone number is collected separately.
   */
  phoneNumber?: string | null | undefined;
}

const normalizeString = (value: unknown): string | undefined => {
  if (typeof value !== "string") return undefined;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : undefined;
};

const normalizeDate = (value: unknown): string | undefined => {
  const str = normalizeString(value);
  if (!str) return undefined;
  return str.replace(/-/g, "");
};

type BillingCycle = WhmcsOrderItem["billingCycle"];

const BILLING_CYCLE_ALIASES: Record<string, BillingCycle> = {
  monthly: "monthly",
  month: "monthly",
  onetime: "onetime",
  once: "onetime",
  singlepayment: "onetime",
  annual: "annually",
  annually: "annually",
  yearly: "annually",
  year: "annually",
  quarterly: "quarterly",
  quarter: "quarterly",
  qtr: "quarterly",
  semiannual: "semiannually",
  semiannually: "semiannually",
  semiannualy: "semiannually",
  semiannualpayment: "semiannually",
  semiannualbilling: "semiannually",
  biannual: "semiannually",
  biannually: "semiannually",
  biennial: "biennially",
  biennially: "biennially",
  triennial: "triennially",
  triennially: "triennially",
  free: "free",
};

const normalizeBillingCycleKey = (value: string): string =>
  value.trim().toLowerCase().replace(/[\s_-]+/g, "");

const DEFAULT_BILLING_CYCLE: BillingCycle = "monthly";

export interface NormalizeBillingCycleOptions {
  defaultValue?: BillingCycle;
}

/**
 * Normalize arbitrary billing cycle strings to the canonical WHMCS values.
 * Keeps mapping logic in the domain so both BFF and UI stay in sync.
 */
export function normalizeBillingCycle(
  value: unknown,
  options: NormalizeBillingCycleOptions = {}
): BillingCycle {
  if (typeof value !== "string") {
    return options.defaultValue ?? DEFAULT_BILLING_CYCLE;
  }

  const directKey = normalizeBillingCycleKey(value);
  const matched = BILLING_CYCLE_ALIASES[directKey];
  if (matched) {
    return matched;
  }

  return options.defaultValue ?? DEFAULT_BILLING_CYCLE;
}

/**
 * Build an OrderConfigurations object for SIM orders from the shared SimConfigureFormData.
 * Ensures the resulting payload conforms to the domain schema before it is sent to the BFF.
 */
export function buildSimOrderConfigurations(
  formData: SimConfigureFormData,
  options: BuildSimOrderConfigurationsOptions = {}
): OrderConfigurations {
  const base: Record<string, unknown> = {
    simType: formData.simType,
    activationType: formData.activationType,
  };

  const eid = normalizeString(formData.eid);
  if (formData.simType === "eSIM" && eid) {
    base.eid = eid;
  }

  const scheduledDate = normalizeDate(formData.scheduledActivationDate);
  if (formData.activationType === "Scheduled" && scheduledDate) {
    base.scheduledAt = scheduledDate;
  }

  const phoneCandidate =
    normalizeString(formData.mnpData?.phoneNumber) ?? normalizeString(options.phoneNumber);
  if (phoneCandidate) {
    base.mnpPhone = phoneCandidate;
  }

  if (formData.wantsMnp && formData.mnpData) {
    const mnp = formData.mnpData;
    base.isMnp = "true";
    base.mnpNumber = normalizeString(mnp.reservationNumber);
    base.mnpExpiry = normalizeDate(mnp.expiryDate);
    base.mvnoAccountNumber = normalizeString(mnp.mvnoAccountNumber);
    base.portingLastName = normalizeString(mnp.portingLastName);
    base.portingFirstName = normalizeString(mnp.portingFirstName);
    base.portingLastNameKatakana = normalizeString(mnp.portingLastNameKatakana);
    base.portingFirstNameKatakana = normalizeString(mnp.portingFirstNameKatakana);
    base.portingGender = normalizeString(mnp.portingGender);
    base.portingDateOfBirth = normalizeDate(mnp.portingDateOfBirth);
  } else if (formData.wantsMnp) {
    // When wantsMnp is true but data is missing, mark the flag so validation fails clearly downstream.
    base.isMnp = "true";
  }

  return orderConfigurationsSchema.parse(base);
}

export function normalizeOrderSelections(value: unknown): OrderSelections {
  return orderSelectionsSchema.parse(value);
}

export type OrderStatusTone = "success" | "info" | "warning" | "neutral";

export type OrderStatusState = "active" | "review" | "scheduled" | "activating" | "processing";

export interface OrderStatusDescriptor {
  label: string;
  state: OrderStatusState;
  tone: OrderStatusTone;
  description: string;
  nextAction?: string;
  timeline?: string;
  scheduledDate?: string;
}

export interface OrderStatusInput {
  status?: string;
  activationStatus?: string;
  scheduledAt?: string;
}

/**
 * Convert backend activation status into a UI-ready descriptor so messaging stays consistent.
 */
export function deriveOrderStatusDescriptor({
  status,
  activationStatus,
  scheduledAt,
}: OrderStatusInput): OrderStatusDescriptor {
  if (activationStatus === "Activated") {
    return {
      label: "Service Active",
      state: "active",
      tone: "success",
      description: "Your service is active and ready to use",
      timeline: "Service activated successfully",
    };
  }

  if (status === "Draft" || status === "Pending Review") {
    return {
      label: "Under Review",
      state: "review",
      tone: "info",
      description: "Our team is reviewing your order details",
      nextAction: "We will contact you within 1 business day with next steps",
      timeline: "Review typically takes 1 business day",
    };
  }

  if (activationStatus === "Scheduled") {
    const scheduledDate = formatScheduledDate(scheduledAt);
    return {
      label: "Installation Scheduled",
      state: "scheduled",
      tone: "warning",
      description: "Your installation has been scheduled",
      nextAction: scheduledDate
        ? `Installation scheduled for ${scheduledDate}`
        : "Installation will be scheduled shortly",
      timeline: "Please be available during the scheduled time",
      scheduledDate,
    };
  }

  if (activationStatus === "Activating") {
    return {
      label: "Setting Up Service",
      state: "activating",
      tone: "info",
      description: "We're configuring your service",
      nextAction: "Installation team will contact you to schedule",
      timeline: "Setup typically takes 3-5 business days",
    };
  }

  return {
    label: status || "Processing",
    state: "processing",
    tone: "neutral",
    description: "Your order is being processed",
    timeline: "We will update you as progress is made",
  };
}

export type OrderServiceCategory = "internet" | "sim" | "vpn" | "default";

/**
 * Normalize order type into a UI category identifier.
 */
export function getOrderServiceCategory(orderType?: string): OrderServiceCategory {
  switch (orderType) {
    case ORDER_TYPE.INTERNET:
      return "internet";
    case ORDER_TYPE.SIM:
      return "sim";
    case ORDER_TYPE.VPN:
      return "vpn";
    default:
      return "default";
  }
}

export interface OrderTotalsInputItem {
  totalPrice?: number | null | undefined;
  billingCycle?: string | null | undefined;
}

/**
 * Aggregate order item totals by billing cadence using shared normalization rules.
 */
export function calculateOrderTotals(
  items?: OrderTotalsInputItem[] | null,
  fallbackTotal?: number | null
): CheckoutTotals {
  let monthlyTotal = 0;
  let oneTimeTotal = 0;

  if (Array.isArray(items) && items.length > 0) {
    for (const item of items) {
      const total = typeof item?.totalPrice === "number" ? item.totalPrice : 0;
      const billingCycle = normalizeBillingCycle(item?.billingCycle);
      switch (billingCycle) {
        case "monthly":
          monthlyTotal += total;
          break;
        case "onetime":
        case "free":
          oneTimeTotal += total;
          break;
        default:
          monthlyTotal += total;
      }
    }
  } else if (typeof fallbackTotal === "number") {
    monthlyTotal = fallbackTotal;
  }

  return {
    monthlyTotal,
    oneTimeTotal,
  };
}

/**
 * Format scheduled activation dates consistently for display.
 */
export function formatScheduledDate(scheduledAt?: string | null): string | undefined {
  if (!scheduledAt) return undefined;
  const date = new Date(scheduledAt);
  if (Number.isNaN(date.getTime())) return undefined;
  return date.toLocaleDateString("en-US", {
    weekday: "long",
    month: "long",
    day: "numeric",
  });
}

/**
 * Categorize order item by itemClass
 */
export function categorizeOrderItem(itemClass?: string | null): OrderDisplayItemCategory {
  const normalized = (itemClass ?? "").toLowerCase();
  if (normalized.includes("service")) return "service";
  if (normalized.includes("installation")) return "installation";
  if (normalized.includes("activation")) return "activation";
  if (normalized.includes("add-on") || normalized.includes("addon")) return "addon";
  return "other";
}

/**
 * Build display items from order item summaries
 */
export function buildOrderDisplayItems(
  items: OrderItemSummary[] | null | undefined
): OrderDisplayItem[] {
  if (!Array.isArray(items) || items.length === 0) {
    return [];
  }

  return items.map((item, index) => {
    const category = categorizeOrderItem(item.itemClass);
    const charges = aggregateCharges([item]);

    return {
      id: item.productId || item.sku || `order-item-${index}`,
      name: item.productName || item.name || "Service item",
      quantity: item.quantity ?? undefined,
      status: item.status ?? undefined,
      primaryCategory: category,
      categories: [category],
      charges,
      included: charges.every((charge) => charge.amount <= 0),
      sourceItems: [item],
      isBundle: Boolean(item.isBundledAddon),
    };
  });
}

function aggregateCharges(items: OrderItemSummary[]): OrderDisplayItemCharge[] {
  const accumulator = new Map<
    string,
    OrderDisplayItemCharge & { kind: OrderDisplayItemChargeKind }
  >();
  const CHARGE_ORDER: Record<OrderDisplayItemChargeKind, number> = {
    monthly: 0,
    "one-time": 1,
    other: 2,
  };

  for (const item of items) {
    const amount = Number(item.totalPrice ?? item.unitPrice ?? 0);
    const normalizedCycle = normalizeBillingCycle(item.billingCycle ?? undefined);

    let kind: OrderDisplayItemChargeKind = "other";
    let key = "other";
    let label = item.billingCycle?.trim() || "Billing";
    let suffix: string | undefined;

    if (normalizedCycle === "monthly") {
      kind = "monthly";
      key = "monthly";
      label = "Monthly";
      suffix = "/ month";
    } else if (normalizedCycle === "onetime" || normalizedCycle === "free") {
      kind = "one-time";
      key = "one-time";
      label = "One-time";
    } else if (typeof item.billingCycle === "string" && item.billingCycle.length > 0) {
      key = `other:${item.billingCycle.toLowerCase()}`;
    }

    const existing = accumulator.get(key);
    if (existing) {
      existing.amount += amount;
    } else {
      accumulator.set(key, { kind, amount, label, suffix });
    }
  }

  return Array.from(accumulator.values())
    .map(({ kind, amount, label, suffix }) => ({ kind, amount, label, suffix }))
    .sort((a, b) => {
      const orderDiff = CHARGE_ORDER[a.kind] - CHARGE_ORDER[b.kind];
      if (orderDiff !== 0) return orderDiff;
      return a.label.localeCompare(b.label);
    });
}
