import type { BillingCheckoutStatus } from "../data/repos";

type Translate = (key: string, params?: Record<string, string | number>) => string;

type ApiErrorLike = {
  code?: unknown;
  httpStatus?: unknown;
};

export type CheckoutPollResult =
  | { outcome: "fulfilled"; checkout: BillingCheckoutStatus }
  | { outcome: "failed"; checkout: BillingCheckoutStatus }
  | { outcome: "timeout"; checkout: BillingCheckoutStatus };

export function isSafeBillingUrl(value: string, dev = import.meta.env.DEV): boolean {
  try {
    const url = new URL(value);
    if (url.protocol === "https:") return true;
    return (
      dev &&
      url.protocol === "http:" &&
      (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]")
    );
  } catch {
    return false;
  }
}

export function billingErrorMessage(
  error: unknown,
  t: Translate,
  fallbackKey: string,
): string {
  const apiError = error && typeof error === "object" ? (error as ApiErrorLike) : null;
  const isApiError = apiError && ("code" in apiError || "httpStatus" in apiError);
  if (!isApiError) return t(fallbackKey);
  const code = typeof apiError?.code === "number" ? apiError.code : 0;
  const status = typeof apiError?.httpStatus === "number" ? apiError.httpStatus : 0;
  if (status === 0 && code === 0) return t("checkout.networkError");
  if (status === 503 || status === 504 || code === 503010) return t("checkout.unavailable");
  if (status === 401) return t("checkout.sessionExpired");
  if (code === 409081) return t("checkout.portalUnavailable");
  return t(fallbackKey);
}

export function startBillingRedirect(
  assign: () => void,
  onFailure: () => void,
  timeoutMs = 3_000,
): () => void {
  let settled = false;
  let timer = 0;
  const cleanup = () => {
    settled = true;
    window.clearTimeout(timer);
    window.removeEventListener("pagehide", cleanup);
  };
  window.addEventListener("pagehide", cleanup, { once: true });
  timer = window.setTimeout(() => {
    if (settled) return;
    cleanup();
    onFailure();
  }, timeoutMs);
  try {
    assign();
  } catch {
    cleanup();
    onFailure();
  }
  return cleanup;
}

export function isTerminalCheckoutFailure(checkout: BillingCheckoutStatus): boolean {
  const checkoutStatus = checkout.checkout_status.toLowerCase();
  const paymentStatus = checkout.payment_status.toLowerCase();
  const fulfillmentStatus = checkout.fulfillment_status.toLowerCase();
  return (
    ["failed", "expired", "canceled", "cancelled"].includes(checkoutStatus) ||
    ["failed", "expired", "canceled", "cancelled"].includes(paymentStatus) ||
    ["failed", "expired", "canceled", "cancelled"].includes(fulfillmentStatus)
  );
}

export async function pollCheckout(
  getCheckout: () => Promise<BillingCheckoutStatus>,
  options: {
    intervalMs?: number;
    timeoutMs?: number;
    sleep?: (ms: number) => Promise<void>;
    stopped?: () => boolean;
  } = {},
): Promise<CheckoutPollResult | null> {
  const intervalMs = options.intervalMs ?? 1_500;
  const timeoutMs = options.timeoutMs ?? 30_000;
  const sleep = options.sleep ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
  let elapsed = 0;
  let checkout = await getCheckout();

  while (!options.stopped?.()) {
    if (checkout.fulfillment_status.toLowerCase() === "fulfilled") {
      return { outcome: "fulfilled", checkout };
    }
    if (isTerminalCheckoutFailure(checkout)) return { outcome: "failed", checkout };
    if (elapsed >= timeoutMs) return { outcome: "timeout", checkout };
    const wait = Math.min(intervalMs, timeoutMs - elapsed);
    await sleep(wait);
    elapsed += wait;
    if (options.stopped?.()) return null;
    checkout = await getCheckout();
  }
  return null;
}
