"use client";

import { useEffect, useMemo, useState, type FormEvent } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import type { AuthUser } from "@/types/user";
import type { KolaboraEvent, TicketType } from "@/types/event";
import type { Order, PricingBreakdown } from "@/types/order";
import { formatCurrency } from "@/lib/format";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/spinner";
import { FOCUS_RING_CLASSES } from "@/components/ui/styles";
import { cn } from "@/lib/utils";
import { PricingSummary } from "@/components/checkout/pricing-summary";

// UI-only ceiling for the quantity stepper when a phase has no explicit
// purchase_limit (null = no per-phase cap) — not a business rule, just a
// sane bound on the input; quota (if set) still governs server-side.
const NO_PER_TYPE_LIMIT_FALLBACK = 99;

interface SelectablePhase {
  id: number;
  categoryId: number;
  categoryName: string;
  name: string;
  price: string;
  purchase_limit: number | null;
}

export function CheckoutForm({
  event,
  ticketTypes,
  user,
}: {
  event: KolaboraEvent;
  ticketTypes: TicketType[];
  user: AuthUser;
}) {
  const router = useRouter();

  // Flatten Category > Phase into one selectable list — only phases
  // currently on sale are offered here (page.tsx already filters
  // categories down to ones with at least one available phase).
  const phases: SelectablePhase[] = useMemo(
    () =>
      ticketTypes.flatMap((tt) =>
        (tt.phases ?? [])
          .filter((phase) => phase.status === "available")
          .map((phase) => ({
            id: phase.id,
            categoryId: tt.id,
            categoryName: tt.name,
            name: phase.name,
            price: phase.price,
            purchase_limit: phase.purchase_limit,
          })),
      ),
    [ticketTypes],
  );

  const [quantities, setQuantities] = useState<Record<number, number>>(
    Object.fromEntries(phases.map((phase) => [phase.id, 0])),
  );
  const [holderName, setHolderName] = useState(user.name);
  const [holderEmail, setHolderEmail] = useState(user.email);
  const [holderPhone, setHolderPhone] = useState(user.phone ?? "");
  const [holderKtp, setHolderKtp] = useState("");
  const [holderAge, setHolderAge] = useState("");
  const [acceptTerms, setAcceptTerms] = useState(false);

  const [formError, setFormError] = useState<string | null>(null);
  const [fieldErrors, setFieldErrors] = useState<Record<string, string[]>>({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  // One shared input for both code types, mirroring the Signature wizard's
  // StepTotalOrder — only one of promo/voucher may ever be applied to an
  // order (2026-08-04), so a single slot removes the need to pick a box.
  type AppliedCode =
    | { type: "promo"; code: string; ticket_type_id: number | null; ticket_phase_id: number | null; discount_percentage: number }
    | { type: "voucher"; code: string; ticket_type_id: number | null; ticket_phase_id: number | null; discount_amount: string };

  const [codeInput, setCodeInput] = useState("");
  const [appliedCode, setAppliedCode] = useState<AppliedCode | null>(null);
  const [codeError, setCodeError] = useState<string | null>(null);
  const [isValidatingCode, setIsValidatingCode] = useState(false);

  const totalQuantity = Object.values(quantities).reduce((a, b) => a + b, 0);

  function scopeLabel(ticketTypeId: number | null, ticketPhaseId: number | null) {
    if (ticketPhaseId !== null) {
      const phase = phases.find((p) => p.id === ticketPhaseId);
      return phase ? `${phase.categoryName} - ${phase.name}` : "this phase";
    }
    if (ticketTypeId !== null) {
      return ticketTypes.find((tt) => tt.id === ticketTypeId)?.name ?? "this ticket type";
    }
    return "all tickets";
  }

  // The actual subtotal/discount/fee/tax/total — always fetched from the
  // backend (POST /checkout/quote), never recomputed in TS, so this can
  // never disagree with what checkout submission itself charges.
  const [quote, setQuote] = useState<PricingBreakdown | null>(null);
  const [quoteError, setQuoteError] = useState<string | null>(null);
  const [isQuoting, setIsQuoting] = useState(false);
  // `quote`/`quoteError` may still hold a stale response from before the
  // last ticket was removed — this is the value actually rendered/submitted.
  const visibleQuote = totalQuantity > 0 ? quote : null;
  const visibleQuoteError = totalQuantity > 0 ? quoteError : null;

  useEffect(() => {
    const items = Object.entries(quantities)
      .filter(([, quantity]) => quantity > 0)
      .map(([ticketPhaseId, quantity]) => ({ ticket_phase_id: Number(ticketPhaseId), quantity }));

    if (items.length === 0) {
      // No setState here — `visibleQuote`/`visibleQuoteError`/the quoting
      // indicator below all derive the empty state from `totalQuantity` at
      // render time instead, so nothing stale ever has to be cleared
      // out-of-band (and a leftover `isQuoting=true` is harmless: every
      // place it's read is already gated on `totalQuantity > 0`).
      return;
    }

    let cancelled = false;
    // Deferred to a callback (not a direct statement) to satisfy
    // react-hooks/set-state-in-effect — see filter-select.tsx for the same
    // pattern with requestAnimationFrame.
    queueMicrotask(() => {
      if (!cancelled) setIsQuoting(true);
    });

    fetch("/api/checkout/quote", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        event_id: event.id,
        items,
        promo_code: appliedCode?.type === "promo" ? appliedCode.code : null,
        voucher_code: appliedCode?.type === "voucher" ? appliedCode.code : null,
      }),
    })
      .then((res) => res.json().then((body) => ({ ok: res.ok, body })))
      .then(({ ok, body }) => {
        if (cancelled) return;
        if (!ok) {
          setQuoteError(body?.message ?? "Failed to calculate total.");
          setQuote(null);
          return;
        }
        setQuoteError(null);
        setQuote(body.data);
      })
      .catch(() => {
        if (!cancelled) setQuoteError("Unable to reach the server.");
      })
      .finally(() => {
        if (!cancelled) setIsQuoting(false);
      });

    return () => {
      cancelled = true;
    };
  }, [quantities, appliedCode, event.id]);

  async function handleApplyCode() {
    setCodeError(null);
    setIsValidatingCode(true);

    try {
      const promoRes = await fetch("/api/checkout/validate-promo", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ event_id: event.id, code: codeInput }),
      });
      const promoBody = await promoRes.json().catch(() => null);

      if (promoRes.ok) {
        setAppliedCode({
          type: "promo",
          code: promoBody.data.code,
          ticket_type_id: promoBody.data.ticket_type_id,
          ticket_phase_id: promoBody.data.ticket_phase_id,
          discount_percentage: promoBody.data.discount_percentage,
        });
        setCodeInput("");
        return;
      }

      const voucherRes = await fetch("/api/checkout/validate-voucher", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ event_id: event.id, code: codeInput }),
      });
      const voucherBody = await voucherRes.json().catch(() => null);

      if (!voucherRes.ok) {
        setCodeError(
          voucherBody?.errors?.voucher_code?.[0] ?? promoBody?.message ?? "Promo/voucher code not found.",
        );
        return;
      }

      setAppliedCode({
        type: "voucher",
        code: voucherBody.data.code,
        ticket_type_id: voucherBody.data.ticket_type_id,
        ticket_phase_id: voucherBody.data.ticket_phase_id,
        discount_amount: voucherBody.data.discount_amount,
      });
      setCodeInput("");
    } catch {
      setCodeError("Unable to reach the server. Please try again.");
    } finally {
      setIsValidatingCode(false);
    }
  }

  function setQuantity(ticketPhaseId: number, value: number, limit: number) {
    const clamped = Math.max(0, Math.min(limit, value));
    setQuantities((prev) => ({ ...prev, [ticketPhaseId]: clamped }));
  }

  async function handleSubmit(formEvent: FormEvent<HTMLFormElement>) {
    formEvent.preventDefault();
    setFormError(null);
    setFieldErrors({});

    const items = Object.entries(quantities)
      .filter(([, quantity]) => quantity > 0)
      .map(([ticketPhaseId, quantity]) => ({
        ticket_phase_id: Number(ticketPhaseId),
        quantity,
      }));

    if (items.length === 0) {
      setFormError("Select at least 1 ticket.");
      return;
    }

    if (!acceptTerms) {
      setFieldErrors({ accept_terms: ["You must agree to the Terms & Conditions and Privacy Policy."] });
      return;
    }

    setIsSubmitting(true);

    try {
      const res = await fetch("/api/checkout", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          event_id: event.id,
          items,
          holder_name: holderName,
          holder_email: holderEmail,
          holder_phone: holderPhone,
          holder_ktp: holderKtp,
          holder_age: Number(holderAge),
          accept_terms: acceptTerms,
          promo_code: appliedCode?.type === "promo" ? appliedCode.code : null,
          voucher_code: appliedCode?.type === "voucher" ? appliedCode.code : null,
        }),
      });

      const body = await res.json().catch(() => null);

      if (!res.ok) {
        setFormError(body?.message ?? "Checkout failed.");
        setFieldErrors(body?.errors ?? {});
        return;
      }

      const order: Order = body.data;
      // Payment method (QRIS / bank VA) is picked on the status page, not
      // here — the order exists as soon as checkout succeeds.
      router.push(`/checkout/status?order=${order.id}`);
      return;
    } catch {
      setFormError("Unable to reach the server. Please check your connection and try again.");
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    <form onSubmit={handleSubmit} className="mt-8 flex flex-col gap-8">
      <section className="flex flex-col gap-3">
        <h2 className="text-lg font-semibold">Select Tickets</h2>
        {phases.map((phase) => (
          <div
            key={phase.id}
            className="flex items-center justify-between gap-4 rounded-xl border border-kolabora-neutral-dark/10 px-4 py-3"
          >
            <div className="min-w-0">
              <p className="font-medium">
                {phase.categoryName} &mdash; {phase.name}
              </p>
              <p className="text-sm text-kolabora-neutral-dark/70">
                {formatCurrency(phase.price)}
                {phase.purchase_limit !== null && ` · max. ${phase.purchase_limit}/account`}
              </p>
            </div>
            <div className="flex shrink-0 items-center gap-2">
              <button
                type="button"
                onClick={() =>
                  setQuantity(phase.id, (quantities[phase.id] ?? 0) - 1, phase.purchase_limit ?? NO_PER_TYPE_LIMIT_FALLBACK)
                }
                aria-label={`Decrease ${phase.name} quantity`}
                className={cn(
                  "flex h-11 w-11 items-center justify-center rounded-full border border-kolabora-neutral-dark/20 text-lg transition-colors hover:bg-kolabora-neutral-dark/5",
                  FOCUS_RING_CLASSES,
                )}
              >
                −
              </button>
              <span className="w-6 text-center font-medium">{quantities[phase.id] ?? 0}</span>
              <button
                type="button"
                onClick={() =>
                  setQuantity(phase.id, (quantities[phase.id] ?? 0) + 1, phase.purchase_limit ?? NO_PER_TYPE_LIMIT_FALLBACK)
                }
                aria-label={`Increase ${phase.name} quantity`}
                className={cn(
                  "flex h-11 w-11 items-center justify-center rounded-full border border-kolabora-neutral-dark/20 text-lg transition-colors hover:bg-kolabora-neutral-dark/5",
                  FOCUS_RING_CLASSES,
                )}
              >
                +
              </button>
            </div>
          </div>
        ))}
        {fieldErrors.items && (
          <p className="text-sm text-red-600">{fieldErrors.items[0]}</p>
        )}
      </section>

      <section className="flex flex-col gap-4">
        <h2 className="text-lg font-semibold">Participant Details</h2>
        <label className="flex flex-col gap-1 text-sm font-medium">
          Full Name
          <Input required value={holderName} onChange={(e) => setHolderName(e.target.value)} />
        </label>
        <label className="flex flex-col gap-1 text-sm font-medium">
          Email
          <Input
            type="email"
            required
            value={holderEmail}
            onChange={(e) => setHolderEmail(e.target.value)}
          />
        </label>
        <label className="flex flex-col gap-1 text-sm font-medium">
          Phone Number
          <Input required value={holderPhone} onChange={(e) => setHolderPhone(e.target.value)} />
        </label>
        <label className="flex flex-col gap-1 text-sm font-medium">
          National ID Number
          <Input required value={holderKtp} onChange={(e) => setHolderKtp(e.target.value)} />
        </label>
        <label className="flex flex-col gap-1 text-sm font-medium">
          Age
          <Input
            type="number"
            min={1}
            max={120}
            required
            value={holderAge}
            onChange={(e) => setHolderAge(e.target.value)}
          />
        </label>
      </section>

      <section className="flex flex-col gap-4">
        <h2 className="text-lg font-semibold">Promo &amp; Voucher</h2>

        {appliedCode ? (
          <div className="flex items-center justify-between rounded-lg border border-kolabora-primary/30 bg-kolabora-primary/5 px-3 py-2 text-sm">
            <span>
              {appliedCode.type === "promo" ? "Promo Code" : "Voucher Code"}{" "}
              <span className="font-mono font-medium">{appliedCode.code}</span> &middot;{" "}
              {appliedCode.type === "promo"
                ? `${appliedCode.discount_percentage}% discount`
                : `${formatCurrency(appliedCode.discount_amount)} off`}{" "}
              for {scopeLabel(appliedCode.ticket_type_id, appliedCode.ticket_phase_id)}
            </span>
            <button
              type="button"
              onClick={() => {
                setAppliedCode(null);
                setCodeInput("");
              }}
              className={cn("rounded text-sm font-medium text-red-600 hover:underline", FOCUS_RING_CLASSES)}
            >
              Remove
            </button>
          </div>
        ) : (
          <div className="flex items-end gap-2">
            <label className="flex flex-1 flex-col gap-1 text-sm font-medium">
              Promo/Voucher Code
              <Input value={codeInput} onChange={(e) => setCodeInput(e.target.value.toUpperCase())} />
            </label>
            <Button
              type="button"
              variant="secondary"
              onClick={handleApplyCode}
              loading={isValidatingCode}
              disabled={!codeInput}
            >
              Apply
            </Button>
          </div>
        )}
        {codeError && <p className="text-sm text-red-600">{codeError}</p>}
      </section>

      <label className="flex items-start gap-2 text-sm">
        <input
          type="checkbox"
          checked={acceptTerms}
          onChange={(e) => setAcceptTerms(e.target.checked)}
          className="mt-0.5"
        />
        I agree to Kolaborativ&apos;s{" "}
        <Link
          href="/terms"
          target="_blank"
          className={cn("rounded underline hover:text-kolabora-primary", FOCUS_RING_CLASSES)}
        >
          Terms &amp; Conditions
        </Link>{" "}
        and{" "}
        <Link
          href="/privacy"
          target="_blank"
          className={cn("rounded underline hover:text-kolabora-primary", FOCUS_RING_CLASSES)}
        >
          Privacy Policy
        </Link>
        .
      </label>
      {fieldErrors.accept_terms && (
        <p className="-mt-6 text-sm text-red-600">{fieldErrors.accept_terms[0]}</p>
      )}

      {visibleQuote && (
        <section className="flex flex-col gap-3 rounded-xl border border-kolabora-neutral-dark/10 px-4 py-3">
          <div className="flex items-center gap-2">
            <h2 className="text-lg font-semibold">Payment Summary</h2>
            {isQuoting && <Spinner className="h-3.5 w-3.5 text-kolabora-neutral-dark/40" aria-label="Updating total" />}
          </div>
          <PricingSummary
            breakdown={visibleQuote}
            discountLabel={
              appliedCode?.type === "promo" ? `Promo ${appliedCode.code}` : `Voucher ${appliedCode?.code}`
            }
          />
        </section>
      )}
      {!visibleQuote && isQuoting && totalQuantity > 0 && (
        <p className="flex items-center gap-2 text-sm text-kolabora-neutral-dark/60">
          <Spinner className="h-3.5 w-3.5" />
          Calculating total...
        </p>
      )}
      {visibleQuoteError && <p className="text-sm text-red-600">{visibleQuoteError}</p>}

      <div className="sticky bottom-0 z-10 -mx-6 flex items-center justify-between gap-4 border-t border-kolabora-neutral-dark/10 bg-kolabora-neutral-white/95 px-6 py-4 pb-[calc(1rem+env(safe-area-inset-bottom))] backdrop-blur-sm">
        <div className="min-w-0">
          <p className="text-sm text-kolabora-neutral-dark/70">
            {totalQuantity} tickets selected
          </p>
          <p className="text-lg font-semibold">{formatCurrency(visibleQuote?.total ?? "0")}</p>
        </div>
        <Button type="submit" size="lg" loading={isSubmitting} disabled={totalQuantity === 0 || !visibleQuote}>
          Pay Now
        </Button>
      </div>

      {formError && <p className="text-sm text-red-600">{formError}</p>}
    </form>
  );
}
