/**
 * useProfile - react-query hooks for loading and updating the user profile.
 *
 * All profile mutations use createOptimisticMutation for immediate UI feedback
 * with automatic rollback + error toast on failure.
 */

'use client';

import { useQuery, useMutation } from '@tanstack/react-query';
import { createOptimisticMutation } from '@/lib/query/optimistic';
import { qk } from '@/lib/query/keys';
import { fetchWithRefresh } from '@/lib/api/refresh-on-401';
import { getCsrfToken } from '@/lib/csrf';
import { encodeImage } from '@/lib/image-upload/encodeImage';
import { uploadEncoded } from '@/lib/image-upload/uploadImage';
import { PURPOSES } from '@/lib/imageVariants';
import type { AvatarResolution } from '@/lib/avatar/resolveAvatar';

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

export interface UserAddress {
  id: string;
  label: string;
  address: string;
  isDefault?: boolean;
}

export interface UserPaymentMethod {
  id: string;
  brandName: string;
  last4: string;
  isDefault: boolean;
  brandLogoUrl?: string;
}

export interface UserPrefs {
  /** Receive push notifications for new deals near the user. */
  dealAlerts?: boolean;
  /** Receive push notifications for club rewards. */
  clubAlerts?: boolean;
  /** Receive push notifications for order status updates. */
  orderAlerts?: boolean;
  /** Two-factor authentication enabled. */
  twoFa?: boolean;
}

export interface UserProfile {
  id: string;
  displayName: string;
  phone: string;
  email?: string;
  avatar?: AvatarResolution;
  avatarStatus?: 'PENDING' | 'APPROVED' | 'REJECTED' | null;
  avatarRejectReasonCode?: string | null;
  purchaseCount: number;
  twoFaEnabled: boolean;
  addresses: UserAddress[];
  paymentMethods: UserPaymentMethod[];
  prefs?: UserPrefs;
}

// ─── Fetch helpers ────────────────────────────────────────────────────────────

async function fetchProfile(): Promise<UserProfile> {
  const res = await fetchWithRefresh('/api/auth/session');
  const data = (await res.json()) as {
    ok: boolean;
    user?: UserProfile;
    error?: string;
  };
  if (!data.ok || !data.user) throw new Error(data.error ?? 'Failed to load profile');
  return data.user;
}

async function updateDisplayName(displayName: string): Promise<void> {
  const res = await fetchWithRefresh('/api/user/profile', {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
      'x-csrf-token': getCsrfToken(),
    },
    body: JSON.stringify({ displayName }),
  });
  if (!res.ok) throw new Error('Failed to update display name');
}

async function updateAvatar(file: File): Promise<void> {
  const ac = new AbortController();
  const bundle = await encodeImage(file, () => {}, ac.signal, PURPOSES['avatar'].variants);
  const { r2Key } = await uploadEncoded(bundle, 'avatar');
  const res = await fetchWithRefresh('/api/user/profile', {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json', 'x-csrf-token': getCsrfToken() },
    body: JSON.stringify({ avatar: { type: 'UPLOADED', r2Key } }),
  });
  if (!res.ok) throw new Error('Failed to submit avatar for review');
}

async function selectGravatar(): Promise<void> {
  const res = await fetchWithRefresh('/api/user/profile', {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json', 'x-csrf-token': getCsrfToken() },
    body: JSON.stringify({ avatar: { type: 'GRAVATAR' } }),
  });
  if (!res.ok) throw new Error('Failed to set gravatar');
}

async function removeAvatar(): Promise<void> {
  const res = await fetchWithRefresh('/api/user/profile', {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json', 'x-csrf-token': getCsrfToken() },
    body: JSON.stringify({ avatar: { type: 'ICON' } }),
  });
  if (!res.ok) throw new Error('Failed to reset avatar');
}

async function addPaymentMethod(vars: {
  singleUseToken: string;
  brand?: string;
}): Promise<{ id: string; last4: string; brand: string; isDefault: boolean }> {
  const res = await fetchWithRefresh('/api/payment-methods', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-csrf-token': getCsrfToken(),
    },
    body: JSON.stringify(vars),
  });
  const data = (await res.json()) as {
    ok: boolean;
    method?: { id: string; last4: string; brand: string; isDefault: boolean };
    error?: string;
  };
  if (!data.ok || !data.method) throw new Error(data.error ?? 'Failed to add payment method');
  return data.method;
}

async function removePaymentMethod(methodId: string): Promise<void> {
  const res = await fetchWithRefresh(`/api/payment-methods/${methodId}`, {
    method: 'DELETE',
    headers: { 'x-csrf-token': getCsrfToken() },
  });
  if (!res.ok) throw new Error('Failed to remove payment method');
}

async function addAddress(vars: {
  fullAddress: string;
  label?: string;
  isDefault?: boolean;
}): Promise<{ id: string; label: string; isDefault: boolean }> {
  const res = await fetchWithRefresh('/api/addresses', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-csrf-token': getCsrfToken(),
    },
    body: JSON.stringify(vars),
  });
  if (!res.ok) throw new Error('Failed to add address');
  const data = (await res.json()) as {
    ok: boolean;
    address?: { id: string; label: string; isDefault: boolean };
  };
  if (!data.ok || !data.address) throw new Error('Failed to add address');
  return data.address;
}

async function updateEmail(email: string): Promise<void> {
  const res = await fetchWithRefresh('/api/user/profile', {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
      'x-csrf-token': getCsrfToken(),
    },
    body: JSON.stringify({ email }),
  });
  if (!res.ok) throw new Error('Failed to update email');
}

async function updateAddress(vars: {
  id: string;
  fullAddress?: string;
  label?: string;
  isDefault?: boolean;
}): Promise<{ id: string; label: string; isDefault: boolean }> {
  const { id, ...body } = vars;
  const res = await fetchWithRefresh(`/api/addresses/${id}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
      'x-csrf-token': getCsrfToken(),
    },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error('Failed to update address');
  const data = (await res.json()) as {
    ok: boolean;
    address?: { id: string; label: string; isDefault: boolean };
  };
  if (!data.ok || !data.address) throw new Error('Failed to update address');
  return data.address;
}

async function deleteAddress(addressId: string): Promise<void> {
  const res = await fetchWithRefresh(`/api/addresses/${addressId}`, {
    method: 'DELETE',
    headers: { 'x-csrf-token': getCsrfToken() },
  });
  if (!res.ok) throw new Error('Failed to delete address');
}

async function updatePrefs(prefs: Partial<UserPrefs>): Promise<void> {
  const res = await fetchWithRefresh('/api/user/profile', {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
      'x-csrf-token': getCsrfToken(),
    },
    body: JSON.stringify({ prefs }),
  });
  if (!res.ok) throw new Error('Failed to update preferences');
}

async function freezeAccount(): Promise<void> {
  const res = await fetchWithRefresh('/api/user/profile/freeze', {
    method: 'POST',
    headers: { 'x-csrf-token': getCsrfToken() },
  });
  if (!res.ok) throw new Error('Failed to freeze account');
}

async function requestAccountDeletion(): Promise<void> {
  const res = await fetchWithRefresh('/api/user/profile/delete-request', {
    method: 'POST',
    headers: { 'x-csrf-token': getCsrfToken() },
  });
  if (!res.ok) throw new Error('Failed to request deletion');
}

// ─── Hooks ────────────────────────────────────────────────────────────────────

/**
 * Load the current user's profile.
 */
export function useProfile() {
  return useQuery({
    queryKey: qk.profile(),
    queryFn: fetchProfile,
    staleTime: 5 * 60_000,
  });
}

/**
 * Update the display name — optimistic: immediately reflects the new name.
 */
export const useUpdateDisplayName = createOptimisticMutation<void, string, UserProfile>({
  mutationFn: updateDisplayName,
  queryKey: qk.profile(),
  optimisticUpdate: (prev, displayName) =>
    prev ? { ...prev, displayName } : ({ displayName } as UserProfile),
  errorToast: () => 'לא ניתן לעדכן את השם — השינוי בוטל',
});

/**
 * Upload a new avatar — submits for moderation.
 * Optimistic: shows a local object URL with PENDING status immediately.
 */
export const useUpdateAvatar = createOptimisticMutation<void, File, UserProfile>({
  mutationFn: updateAvatar,
  queryKey: qk.profile(),
  optimisticUpdate: (prev, file) => {
    const url = URL.createObjectURL(file);
    const avatar: AvatarResolution = { kind: 'image', url, pipeline: 'external' };
    return prev
      ? { ...prev, avatar, avatarStatus: 'PENDING' }
      : ({ avatar, avatarStatus: 'PENDING' } as UserProfile);
  },
  errorToast: () => 'העלאת התמונה נכשלה — השינוי בוטל',
  successToast: () => 'התמונה נשלחה לבדיקה',
  invalidateOn: 'success',
});

/** Switch the avatar to the user's gravatar.com photo. */
export const useSelectGravatar = createOptimisticMutation<void, void, UserProfile>({
  mutationFn: selectGravatar,
  queryKey: qk.profile(),
  optimisticUpdate: (prev) => prev as UserProfile,
  errorToast: () => 'לא ניתן להגדיר תמונת Gravatar',
  successToast: () => 'התמונה עודכנה בהצלחה',
  invalidateOn: 'success',
});

/** Remove the current avatar — revert to the default icon. */
export const useRemoveAvatar = createOptimisticMutation<void, void, UserProfile>({
  mutationFn: removeAvatar,
  queryKey: qk.profile(),
  optimisticUpdate: (prev) =>
    prev
      ? { ...prev, avatar: { kind: 'icon', name: 'user' }, avatarStatus: 'APPROVED' }
      : (prev as unknown as UserProfile),
  errorToast: () => 'לא ניתן להסיר את התמונה',
  successToast: () => 'התמונה הוסרה',
  invalidateOn: 'success',
});

/** On-demand gravatar existence probe — enabled when the avatar section opens. */
export function useGravatarCheck(enabled: boolean) {
  return useQuery({
    queryKey: ['gravatar-check'],
    enabled,
    staleTime: 5 * 60_000,
    queryFn: async (): Promise<{ available: boolean; hash: string | null }> => {
      const res = await fetchWithRefresh('/api/user/avatar/gravatar-check');
      const data = (await res.json()) as {
        ok: boolean;
        data?: { available: boolean; hash: string | null };
      };
      return data.data ?? { available: false, hash: null };
    },
  });
}

/**
 * Remove a payment method — optimistic: immediately removes the chip.
 */
export const useRemovePaymentMethod = createOptimisticMutation<void, string, UserProfile>({
  mutationFn: removePaymentMethod,
  queryKey: qk.profile(),
  optimisticUpdate: (prev, methodId) =>
    prev
      ? { ...prev, paymentMethods: prev.paymentMethods.filter((m) => m.id !== methodId) }
      : (prev as unknown as UserProfile),
  errorToast: () => '',
});

/**
 * Add a payment method — invalidates profile on success so the new card appears.
 */
export function useAddPaymentMethod() {
  return useMutation({
    mutationFn: addPaymentMethod,
  });
}

/**
 * Add a new address — optimistic: immediately appends a placeholder entry.
 * Invalidates on settled so the real server ID replaces the placeholder.
 */
export const useAddAddress = createOptimisticMutation<
  { id: string; label: string; isDefault: boolean },
  { fullAddress: string; label?: string; isDefault?: boolean },
  UserProfile
>({
  mutationFn: addAddress,
  queryKey: qk.profile(),
  optimisticUpdate: (prev, vars) => {
    const optimisticAddr: UserAddress = {
      id: `optimistic-${Date.now()}`,
      label: vars.label ?? vars.fullAddress,
      address: vars.fullAddress,
    };
    return prev
      ? { ...prev, addresses: [...prev.addresses, optimisticAddr] }
      : ({ addresses: [optimisticAddr] } as UserProfile);
  },
  errorToast: () => 'לא ניתן להוסיף את הכתובת — השינוי בוטל',
  successToast: () => 'הכתובת נוספה בהצלחה',
});

/**
 * Update email — optimistic: immediately reflects the new email.
 */
export const useUpdateEmail = createOptimisticMutation<void, string, UserProfile>({
  mutationFn: updateEmail,
  queryKey: qk.profile(),
  optimisticUpdate: (prev, email) => (prev ? { ...prev, email } : ({ email } as UserProfile)),
  errorToast: () => 'לא ניתן לעדכן את האימייל — השינוי בוטל',
});

/**
 * Update an existing address — optimistic: replaces the address row in place.
 */
export const useUpdateAddress = createOptimisticMutation<
  { id: string; label: string; isDefault: boolean },
  { id: string; fullAddress?: string; label?: string; isDefault?: boolean },
  UserProfile
>({
  mutationFn: updateAddress,
  queryKey: qk.profile(),
  optimisticUpdate: (prev, vars) =>
    prev
      ? {
          ...prev,
          addresses: prev.addresses.map((a) =>
            a.id === vars.id
              ? { ...a, label: vars.label ?? a.label, address: vars.fullAddress ?? a.address }
              : a,
          ),
        }
      : (prev as unknown as UserProfile),
  errorToast: () => 'לא ניתן לעדכן את הכתובת — השינוי בוטל',
});

/**
 * Delete an address — optimistic: immediately removes the row.
 */
export const useDeleteAddress = createOptimisticMutation<void, string, UserProfile>({
  mutationFn: deleteAddress,
  queryKey: qk.profile(),
  optimisticUpdate: (prev, addressId) =>
    prev
      ? { ...prev, addresses: prev.addresses.filter((a) => a.id !== addressId) }
      : (prev as unknown as UserProfile),
  errorToast: () => 'לא ניתן למחוק את הכתובת — השינוי בוטל',
});

/**
 * Update notification preferences — optimistic: immediately reflects the toggle.
 */
export const useUpdatePrefs = createOptimisticMutation<void, Partial<UserPrefs>, UserProfile>({
  mutationFn: updatePrefs,
  queryKey: qk.profile(),
  optimisticUpdate: (prev, prefs) =>
    prev
      ? { ...prev, prefs: { ...(prev.prefs ?? {}), ...prefs } as UserPrefs }
      : ({ prefs } as UserProfile),
  errorToast: () => 'לא ניתן לעדכן את ההעדפות — השינוי בוטל',
});

/**
 * Freeze the account — no optimistic UI (destructive, irreversible).
 */
export function useFreezeAccount() {
  return useMutation({ mutationFn: freezeAccount });
}

/**
 * Request account deletion — no optimistic UI (destructive, irreversible).
 */
export function useRequestAccountDeletion() {
  return useMutation({ mutationFn: requestAccountDeletion });
}
