'use client';

import { useQuery, useQueryClient } from '@tanstack/react-query';
import { getCsrfToken } from '@/lib/csrf';
import { createOptimisticMutation } from '@/lib/query/optimistic';
import { qk } from '@/lib/query/keys';
import type { CartErrorCode, CartItem } from './useCart';

export interface SessionData {
  ok: boolean;
  user?: { id: string };
}
export type CartSnapshot = { items: CartItem[]; subtotal: number };
interface ServerCartResponse {
  ok: boolean;
  items?: ServerCartItem[];
  subtotal?: string | number;
  error?: string;
  code?: string;
}
interface ServerCartItem {
  dealSkuId?: string | null;
  dealId?: string;
  qty: number;
  addedAt: string | Date;
  dealTitle?: string;
  vendorName?: string;
  discountedPrice?: string | number;
  maxPerUser?: number | null;
  remainingStock?: number;
  imageUrl?: string | null;
  skuLabel?: string | null;
  qtyTiers?: { minQty: number; discountPercent: number }[];
}

export async function fetchSession(): Promise<SessionData> {
  const res = await fetch('/api/auth/session');
  return res.ok ? (res.json() as Promise<SessionData>) : { ok: false };
}
function agorot(value: string | number | undefined): number | undefined {
  if (value === undefined) return undefined;
  return typeof value === 'number'
    ? Math.round(value)
    : Number.isFinite(parseFloat(value))
      ? Math.round(parseFloat(value) * 100)
      : undefined;
}
function items(raw: ServerCartItem[]): CartItem[] {
  return raw.map((item) => ({
    dealSkuId: item.dealSkuId ?? item.dealId ?? '',
    dealId: item.dealId,
    qty: item.qty,
    addedAt: item.addedAt instanceof Date ? item.addedAt.toISOString() : item.addedAt,
    title: item.dealTitle,
    vendorName: item.vendorName,
    unitPrice: agorot(item.discountedPrice),
    maxQty:
      item.maxPerUser !== undefined && item.remainingStock !== undefined
        ? item.maxPerUser === null
          ? item.remainingStock
          : Math.min(item.maxPerUser, item.remainingStock)
        : undefined,
    imageId: item.imageUrl ?? undefined,
    skuLabel: item.skuLabel ?? null,
    qtyTiers: item.qtyTiers ?? [],
  }));
}
function snapshot(data: ServerCartResponse): CartSnapshot {
  return { items: items(data.items ?? []), subtotal: agorot(data.subtotal) ?? 0 };
}
async function request(url: string, init?: RequestInit): Promise<CartSnapshot> {
  const data = (await (await fetch(url, init)).json()) as ServerCartResponse;
  if (!data.ok)
    throw Object.assign(new Error(data.error ?? 'Cart request failed'), { code: data.code });
  return snapshot(data);
}
const headers = () => ({ 'Content-Type': 'application/json', 'x-csrf-token': getCsrfToken() });
const errorCode = (error: unknown): CartErrorCode => {
  const code = (error as { code?: string })?.code;
  return code === 'OUT_OF_STOCK' || code === 'MAX_REACHED' || code === 'DEAL_NOT_FOUND'
    ? code
    : 'NETWORK';
};
const mutation = <T>(
  onErrorRef: { current: ((code: CartErrorCode) => void) | undefined },
  mutationFn: (vars: T) => Promise<CartSnapshot>,
  optimisticUpdate: (previous: CartSnapshot | undefined, vars: T) => CartSnapshot,
) =>
  createOptimisticMutation<CartSnapshot, T, CartSnapshot>({
    mutationFn: async (vars) => {
      try {
        return await mutationFn(vars);
      } catch (error) {
        onErrorRef.current?.(errorCode(error));
        throw error;
      }
    },
    queryKey: qk.cart(),
    optimisticUpdate,
  })();

export function useServerCart(
  enabled: boolean,
  onErrorRef: { current: ((code: CartErrorCode) => void) | undefined },
) {
  const queryClient = useQueryClient();
  const query = useQuery({
    queryKey: ['cart'],
    queryFn: () => request('/api/cart'),
    enabled,
    staleTime: 30_000,
  });
  const addMutation = mutation<{ dealSkuId: string; qty: number }>(
    onErrorRef,
    (vars) =>
      request('/api/cart/items', {
        method: 'POST',
        headers: headers(),
        body: JSON.stringify(vars),
      }),
    (previous, vars) => {
      if (!previous) return { items: [], subtotal: 0 };
      const existing = previous.items.find((item) => item.dealSkuId === vars.dealSkuId);
      const next = existing
        ? { ...existing, qty: existing.qty + vars.qty }
        : { dealSkuId: vars.dealSkuId, qty: vars.qty, addedAt: new Date().toISOString() };
      return {
        items: existing
          ? previous.items.map((item) => (item.dealSkuId === vars.dealSkuId ? next : item))
          : [...previous.items, next],
        subtotal: previous.subtotal,
      };
    },
  );
  const updateMutation = mutation<{ dealSkuId: string; qty: number }>(
    onErrorRef,
    (vars) =>
      request(`/api/cart/items/${vars.dealSkuId}`, {
        method: 'PATCH',
        headers: headers(),
        body: JSON.stringify({ qty: vars.qty }),
      }),
    (previous, vars) =>
      !previous
        ? { items: [], subtotal: 0 }
        : {
            items: previous.items.map((item) =>
              item.dealSkuId === vars.dealSkuId ? { ...item, qty: vars.qty } : item,
            ),
            subtotal: previous.subtotal,
          },
  );
  const removeMutation = mutation<{ dealSkuId: string }>(
    onErrorRef,
    (vars) =>
      request(`/api/cart/items/${vars.dealSkuId}`, {
        method: 'DELETE',
        headers: { 'x-csrf-token': getCsrfToken() },
      }),
    (previous, vars) =>
      !previous
        ? { items: [], subtotal: 0 }
        : {
            items: previous.items.filter((item) => item.dealSkuId !== vars.dealSkuId),
            subtotal: previous.subtotal,
          },
  );
  return {
    query,
    add: addMutation.mutate,
    updateQty: (dealSkuId: string, qty: number) =>
      qty <= 0 ? removeMutation.mutate({ dealSkuId }) : updateMutation.mutate({ dealSkuId, qty }),
    remove: (dealSkuId: string) => removeMutation.mutate({ dealSkuId }),
    clear: () => {
      queryClient.setQueryData(qk.cart(), { items: [], subtotal: 0 });
      void queryClient.invalidateQueries({ queryKey: qk.cart() });
    },
    isPending: addMutation.isPending || updateMutation.isPending || removeMutation.isPending,
  };
}
