/**
 * CartCheckout — multi-line checkout component for /cart page (W2-C).
 *
 * Registered users:   payment method picker → POST /api/cart/checkout.
 * Anonymous users:    OTP dialog → merge → POST /api/cart/checkout.
 * 409 stale:          shows CartStaleToast + InlineNotice for updated qty,
 *                     disables Pay until user re-confirms.
 * Success:            invalidates ['cart'] query, redirects to first purchase confirmation.
 */

'use client';
import { ErrorState } from '@/components/ui/feedback/ErrorState';

import { useState, useRef, useCallback } from 'react';
import { useQueryClient, useQuery } from '@tanstack/react-query';
import { useT } from '@/lib/i18n/react';
import { getCsrfToken } from '@/lib/csrf';
import { useCart } from './useCart';
import { useCartMerge } from './useCartMerge';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/overlays/Dialog';
import { Button } from '@/components/ui/primitives/Button';
import { RadioGroup, RadioItem } from '@/components/ui/primitives/RadioGroup';
import { PaymentMethodChip } from '@/components/ui/domain/PaymentMethodChip';
import { CartStaleToast } from '@/components/ui/domain/cart/CartStaleToast';
import type { RemovedItem } from '@/components/ui/domain/cart/CartStaleToast';
import { InlineNotice } from '@/components/ui/feedback/InlineNotice';
import { Switch } from '@/components/ui/primitives/Switch';
import { OtpLoginShell } from '@/features/auth-flow/OtpLoginShell';
import { PromoCodeInput, type PromoApplied } from '@/features/checkout/PromoCodeInput';
import { captureCaught } from '@/lib/observability';
import { agorotToShekelsFloor } from '@/lib/money';

// ─── Referral credit types ────────────────────────────────────────────────────

interface ReferralMeData {
  code: string | null;
  url: string | null;
  pending: number;
  qualified: number;
  balanceAgorot: number;
  lifetimeEarnedAgorot: number;
}

async function fetchReferralCredit(): Promise<ReferralMeData> {
  const res = await fetch('/api/referrals/me');
  if (!res.ok) throw new Error('Failed to fetch referral balance');
  // GET /api/referrals/me uses defineApi with respondOk which spreads result.data
  // into the top-level body: { ok: true, balanceAgorot, ... } — no 'data' wrapper.
  const { ok: _ok, ...rest } = (await res.json()) as { ok: true } & ReferralMeData;
  return rest as ReferralMeData;
}

// ─── Types ────────────────────────────────────────────────────────────────────

export interface SavedPaymentMethod {
  id: string;
  brandName: string;
  last4: string;
  brandLogoUrl?: string;
}

export interface CartCheckoutProps {
  /** Whether the current visitor is a registered (logged-in) user. */
  isRegistered: boolean;
  /** Saved payment methods (registered users only). */
  paymentMethods?: SavedPaymentMethod[];
}

// ─── API response shape ───────────────────────────────────────────────────────

interface StaleItem {
  dealId: string;
  reason: 'expired' | 'inactive' | 'sold_out' | 'qty_clamped';
  availableQty?: number;
}

interface CheckoutOkResponse {
  ok: true;
  purchaseIds: string[];
}

interface CheckoutStaleResponse {
  ok: false;
  code: 'CART_STALE';
  removed: StaleItem[];
  updated: StaleItem[];
}

interface CheckoutErrorResponse {
  ok: false;
  code: string;
  error?: string;
}

type CheckoutResponse = CheckoutOkResponse | CheckoutStaleResponse | CheckoutErrorResponse;

// ─── CartCheckout ─────────────────────────────────────────────────────────────

/**
 * CartCheckout — renders "Pay" CTA with payment method picker for registered
 * users or OTP dialog gate for anonymous users.
 *
 * Mount inside CartPage, after the line items section:
 * ```tsx
 * // Inside CartPage.tsx:
 * <CartCheckout isRegistered={isRegistered} paymentMethods={paymentMethods} />
 * ```
 */
export function CartCheckout({ isRegistered, paymentMethods = [] }: CartCheckoutProps) {
  const t = useT('cart');
  const tCommon = useT('common');
  const tRef = useT('referrals');
  const queryClient = useQueryClient();
  const { items, flushPendingUndo } = useCart();
  const { mergeFromLocalStorage } = useCartMerge();

  // Payment method selection
  const [selectedMethodId, setSelectedMethodId] = useState<string>(paymentMethods[0]?.id ?? '');

  // 409 stale state
  const [removedItems, setRemovedItems] = useState<RemovedItem[]>([]);
  const [updatedItems, setUpdatedItems] = useState<StaleItem[]>([]);
  const [needsReconfirm, setNeedsReconfirm] = useState(false);

  // Checkout in-flight state
  const [isPaying, setIsPaying] = useState(false);
  const [checkoutError, setCheckoutError] = useState<string | null>(null);

  // OTP dialog for anon users
  const [otpDialogOpen, setOtpDialogOpen] = useState(false);

  // Promo code applied state
  const [appliedPromo, setAppliedPromo] = useState<PromoApplied | null>(null);

  // Referral credit hold toggle — false = auto-apply credit (default)
  const [holdCredit, setHoldCredit] = useState(false);

  // Referral credit balance (registered users only; errors silently ignored)
  const {
    data: creditData,
    isError,
    refetch,
  } = useQuery({
    queryKey: ['referrals', 'me'],
    queryFn: fetchReferralCredit,
    enabled: isRegistered,
    staleTime: 60_000,
    retry: false,
  });
  const creditAgorot = creditData?.balanceAgorot ?? 0;

  // Per-cart Idempotency-Key (Path A) — generated once on mount, REUSED across
  // retries within the session so the server can dedupe a partially-applied
  // charge. Regenerated only after a successful checkout (just before
  // navigating away — defensive; the page navigates so the next mount gets
  // a fresh key naturally).
  const idempotencyKeyRef = useRef<string>(crypto.randomUUID());

  // ── Resolve stale item titles from current cart ───────────────────────────

  const resolveRemovedItems = useCallback(
    (raw: StaleItem[]): RemovedItem[] => {
      return raw
        .filter((s) => s.reason !== 'qty_clamped')
        .map((s) => {
          const cartItem = items.find((i) => i.dealId === s.dealId);
          return {
            dealId: s.dealId,
            title: cartItem?.title ?? s.dealId,
            reason:
              s.reason === 'expired'
                ? ('expired' as const)
                : s.reason === 'inactive'
                  ? ('expired' as const) // map inactive → expired for display
                  : ('sold_out' as const),
          };
        });
    },
    [items],
  );

  // ── Core checkout POST ────────────────────────────────────────────────────

  const postCheckout = useCallback(
    async (paymentMethodId?: string): Promise<void> => {
      // NOTE: Idempotency-Key is REUSED across retries within this mount
      // (Path A). Do NOT regenerate on each attempt — the server uses the
      // key to dedupe a partially-applied charge. Key is regenerated only
      // after a successful checkout (success branch below).

      setIsPaying(true);
      setCheckoutError(null);
      setRemovedItems([]);
      setUpdatedItems([]);
      setNeedsReconfirm(false);

      try {
        await flushPendingUndo();
        const res = await fetch('/api/cart/checkout', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'x-csrf-token': getCsrfToken(),
            'idempotency-key': idempotencyKeyRef.current,
          },
          body: JSON.stringify({
            paymentMethodId: paymentMethodId ?? undefined,
            holdCredit: holdCredit || undefined,
            ...(appliedPromo ? { promoCode: appliedPromo.code } : {}),
          }),
        });

        const data = (await res.json()) as CheckoutResponse;

        if (data.ok) {
          // Server cleared the cart; invalidate client cache
          void queryClient.invalidateQueries({ queryKey: ['cart'] });
          // Redirect to first purchase confirmation
          if (data.purchaseIds.length > 0) {
            window.location.href = `/purchases/${data.purchaseIds[0]}/confirmation`;
          }
          return;
        }

        if (data.code === 'CART_STALE') {
          const staleData = data as CheckoutStaleResponse;
          // Invalidate so CartPage re-renders without stale lines
          void queryClient.invalidateQueries({ queryKey: ['cart'] });
          setRemovedItems(resolveRemovedItems(staleData.removed));
          setUpdatedItems(staleData.updated);
          setNeedsReconfirm(true);
          return;
        }

        // Other errors
        setCheckoutError((data as CheckoutErrorResponse).error ?? t('checkout_error'));
      } catch (err) {
        captureCaught(err, { scope: 'features.cart.CartCheckout', severity: 'warning' });
        setCheckoutError(t('checkout_error'));
      } finally {
        setIsPaying(false);
      }
    },

    [queryClient, t, holdCredit, appliedPromo, flushPendingUndo, resolveRemovedItems],
  );

  // ── OTP success handler (anon flow) ───────────────────────────────────────

  const handleOtpSuccess = useCallback(async () => {
    setOtpDialogOpen(false);
    // Merge localStorage cart into server cart
    await mergeFromLocalStorage();
    // Then proceed to checkout
    await postCheckout(selectedMethodId || undefined);
  }, [mergeFromLocalStorage, postCheckout, selectedMethodId]);

  // ── Pay button click ──────────────────────────────────────────────────────

  function handlePayClick() {
    if (!isRegistered) {
      setOtpDialogOpen(true);
      return;
    }
    void postCheckout(selectedMethodId || undefined);
  }

  // ── Render ────────────────────────────────────────────────────────────────

  const hasStale = removedItems.length > 0 || updatedItems.length > 0;
  const payButtonDisabled = isPaying || needsReconfirm || (isRegistered && !selectedMethodId);

  if (isError) {
    return (
      <ErrorState
        title={tCommon('error_loading')}
        action={
          <Button variant="secondary" size="sm" onClick={() => refetch()}>
            {tCommon('retry')}
          </Button>
        }
      />
    );
  }
  return (
    <div className="flex flex-col gap-4">
      {/* Payment method section — heading always shown for registered users */}
      {isRegistered && (
        <div>
          <p className="text-text-primary mb-2 text-sm font-semibold">
            {t('checkout_payment_method')}
          </p>
          {paymentMethods.length > 0 ? (
            <RadioGroup value={selectedMethodId} onValueChange={setSelectedMethodId}>
              {paymentMethods.map((pm) => (
                <RadioItem
                  key={pm.id}
                  value={pm.id}
                  id={`checkout-pm-${pm.id}`}
                  label={
                    <span className="flex items-center gap-2">
                      <PaymentMethodChip
                        brandName={pm.brandName}
                        last4={pm.last4}
                        brandLogoUrl={pm.brandLogoUrl}
                      />
                    </span>
                  }
                />
              ))}
            </RadioGroup>
          ) : (
            <div className="flex flex-col gap-3">
              <InlineNotice tone="info" title={t('no_saved_methods')} />
              <Button variant="secondary" size="sm" asChild>
                <a href="/profile">{t('add_payment_method')}</a>
              </Button>
            </div>
          )}
        </div>
      )}

      {/* 409 stale: removed items */}
      {removedItems.length > 0 && (
        <div>
          <p className="text-text-primary mb-2 text-sm font-semibold">
            {t('checkout_stale_title')}
          </p>
          <CartStaleToast removedItems={removedItems} />
        </div>
      )}

      {/* 409 stale: qty-updated items */}
      {updatedItems.length > 0 && (
        <div className="flex flex-col gap-2">
          {updatedItems.map((item) => {
            const cartItem = items.find((i) => i.dealId === item.dealId);
            const label = t('checkout_updated_qty').replace(
              '{qty}',
              String(item.availableQty ?? 1),
            );
            return (
              <InlineNotice
                key={item.dealId}
                tone="warning"
                title={cartItem?.title ?? item.dealId}
                description={label}
              />
            );
          })}
        </div>
      )}

      {/* Re-confirm notice */}
      {needsReconfirm && hasStale && (
        <InlineNotice tone="warning" description={t('checkout_stale_desc')} />
      )}

      {/* Generic checkout error */}
      {checkoutError && <InlineNotice tone="danger" description={checkoutError} />}

      {/* Referral credit line — only shown when user has a non-zero balance */}
      {isRegistered && creditAgorot > 0 && (
        <div
          className={`flex items-center justify-between gap-3 rounded-lg px-3 py-2 ${holdCredit ? 'bg-surface-muted' : 'bg-success-50'}`}
        >
          <span
            className={`text-sm font-medium ${holdCredit ? 'text-text-secondary' : 'text-success-700'}`}
          >
            {(holdCredit ? tRef('credit_held') : tRef('credit_applied')).replace(
              '{{amount}}',
              String(agorotToShekelsFloor(creditAgorot)),
            )}
          </span>
          <label className="flex cursor-pointer items-center gap-2" htmlFor="hold-credit-toggle">
            <span className="text-text-secondary text-xs">{tRef('hold_credit_label')}</span>
            <Switch
              id="hold-credit-toggle"
              checked={holdCredit}
              onCheckedChange={setHoldCredit}
              aria-label={tRef('hold_credit_label')}
            />
          </label>
        </div>
      )}

      {/* Promo code input — registered users only */}
      {isRegistered && <PromoCodeInput applied={appliedPromo} onApplied={setAppliedPromo} />}

      {/* Pay / Re-confirm button */}
      <Button
        variant="primary"
        size="lg"
        className="w-full"
        onClick={handlePayClick}
        loading={isPaying}
        disabled={payButtonDisabled}
      >
        {isPaying
          ? t('checkout_processing')
          : needsReconfirm
            ? t('checkout_reconfirm')
            : t('checkout_pay')}
      </Button>

      {/* OTP dialog for anonymous users */}
      <Dialog open={otpDialogOpen} onOpenChange={setOtpDialogOpen}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>{t('checkout_login_title')}</DialogTitle>
          </DialogHeader>
          <p className="text-text-secondary mb-4 text-sm">{t('checkout_login_desc')}</p>
          <OtpLoginShell embedded onSuccess={handleOtpSuccess} />
        </DialogContent>
      </Dialog>
    </div>
  );
}
