/**
 * CfgAccounts Context
 *
 * Manages user authentication and profile operations using generated SWR hooks.
 *
 * Features:
 * - OTP-based authentication
 * - User profile management
 * - Avatar upload
 * - Profile updates
 * - localStorage cache with TTL (1 hour)
 */

"use client";

import {
    createContext, memo, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState
} from 'react';

import { auth } from '../../_api/generated/helpers/auth';
import { CfgAccountsAuth, CfgAccountsProfile } from '../../_api/generated/_cfg_accounts/sdk.gen';
import {
    useCfgAccountsOtpRequestCreate,
    useCfgAccountsOtpVerifyCreate,
    useCfgAccountsProfileAvatarCreate,
    useCfgAccountsProfilePartialUpdate,
    useCfgAccountsProfileUpdateUpdate,
} from '../../_api/generated/_cfg_accounts/hooks';
import { PatchedCfgUserUpdateRequestSchema } from '../../_api/generated/_cfg_accounts/schemas';
import { clearProfileCache, getCachedProfile, setCachedProfile } from '../hooks/useProfileCache';
import { authLogger } from '../utils/logger';

import type {
    User,
    CfgUserUpdateRequest,
    PatchedCfgUserUpdateRequest,
    OtpRequestRequest,
    OtpVerifyRequest,
    OtpRequestResponse,
    OtpVerifyResponse,
} from '../../_api/generated/_cfg_accounts/types.gen';

export { PatchedCfgUserUpdateRequestSchema };
export type { PatchedCfgUserUpdateRequest, User, CfgUserUpdateRequest as UserProfileUpdateRequest };

// ─────────────────────────────────────────────────────────────────────────
// Context Type
// ─────────────────────────────────────────────────────────────────────────

export interface AccountsContextValue {
  profile?: User;
  isLoadingProfile: boolean;
  profileError: Error | null;

  updateProfile: (data: CfgUserUpdateRequest) => Promise<User>;
  partialUpdateProfile: (data: PatchedCfgUserUpdateRequest) => Promise<User>;
  uploadAvatar: (avatar: File | Blob) => Promise<User>;
  refreshProfile: (options?: { callerId?: string; force?: boolean }) => Promise<User | undefined>;

  requestOTP: (data: OtpRequestRequest) => Promise<OtpRequestResponse>;
  verifyOTP: (data: OtpVerifyRequest) => Promise<OtpVerifyResponse>;
  logout: () => void;
}

const AccountsContext = createContext<AccountsContextValue | undefined>(undefined);

interface AccountsProviderProps {
  children: ReactNode;
}

export function AccountsProvider({ children }: AccountsProviderProps) {
  const [profile, setProfile] = useState<User | undefined>(() => {
    const cached = getCachedProfile() as User | null;
    return cached || undefined;
  });
  const [isLoadingProfile, setIsLoadingProfile] = useState(false);
  const [profileError, setProfileError] = useState<Error | null>(null);

  const profileRef = useRef<User | undefined>(profile);
  const isLoadingRef = useRef(false);

  useEffect(() => {
    profileRef.current = profile;
  }, [profile]);

  useEffect(() => {
    isLoadingRef.current = isLoadingProfile;
  }, [isLoadingProfile]);

  const { trigger: triggerProfileUpdate } = useCfgAccountsProfileUpdateUpdate();
  const { trigger: triggerProfilePartialUpdate } = useCfgAccountsProfilePartialUpdate();
  const { trigger: triggerAvatar } = useCfgAccountsProfileAvatarCreate();
  const { trigger: triggerOtpRequest } = useCfgAccountsOtpRequestCreate();
  const { trigger: triggerOtpVerify } = useCfgAccountsOtpVerifyCreate();

  const refreshProfile = useCallback(async (options?: { callerId?: string; force?: boolean }): Promise<User | undefined> => {
    const { callerId, force } = options || {};

    if (isLoadingRef.current) {
      authLogger.debug(`Profile loading in progress, skipping (caller: ${callerId})`);
      return profileRef.current;
    }

    setIsLoadingProfile(true);
    isLoadingRef.current = true;
    setProfileError(null);
    try {
      authLogger.debug(`Fetching profile from API (caller: ${callerId}, force: ${force})`);
      const res = await CfgAccountsProfile.cfgAccountsProfileRetrieve({ throwOnError: true });
      const result = res.data;
      setProfile(result);
      profileRef.current = result;
      setCachedProfile(result);
      return result;
    } catch (error) {
      const err = error instanceof Error ? error : new Error('Failed to fetch profile');
      setProfileError(err);
      throw err;
    } finally {
      setIsLoadingProfile(false);
      isLoadingRef.current = false;
    }
  }, []);

  const updateProfile = async (data: CfgUserUpdateRequest): Promise<User> => {
    await triggerProfileUpdate({ body: data });
    const user = await refreshProfile({ callerId: 'updateProfile', force: true });
    return user as User;
  };

  const partialUpdateProfile = async (data: PatchedCfgUserUpdateRequest): Promise<User> => {
    await triggerProfilePartialUpdate({ body: data });
    const user = await refreshProfile({ callerId: 'partialUpdateProfile', force: true });
    return user as User;
  };

  const uploadAvatar = async (avatar: File | Blob): Promise<User> => {
    await triggerAvatar({ body: { avatar } });
    const user = await refreshProfile({ callerId: 'uploadAvatar', force: true });
    return user as User;
  };

  const requestOTP = async (data: OtpRequestRequest): Promise<OtpRequestResponse> => {
    const result = await triggerOtpRequest({ body: data });
    return result as OtpRequestResponse;
  };

  const verifyOTP = async (data: OtpVerifyRequest): Promise<OtpVerifyResponse> => {
    const result = (await triggerOtpVerify({ body: data })) as OtpVerifyResponse;

    if (result.requires_2fa && result.session_id) {
      authLogger.info('2FA required, session:', result.session_id);
      return result;
    }

    if (result.access && result.refresh) {
      // The ONE write path — atomic pair, notifies every session subscriber.
      auth.setSession({ access: result.access, refresh: result.refresh });
      try {
        await refreshProfile({ callerId: 'verifyOTP', force: true });
      } catch (profileError) {
        authLogger.warn('Profile refresh failed after OTP verify (tokens are saved):', profileError);
      }
    }

    return result;
  };

  const logout = useCallback(() => {
    // Best-effort server-side revocation: without blacklisting, a stolen
    // refresh token survives "logout" until it expires. Fire-and-forget —
    // local logout must never block on the network.
    const refresh = auth.getRefreshToken();
    if (refresh) {
      CfgAccountsAuth.cfgAccountsTokenBlacklistCreate({ body: { refresh } }).catch(() => {
        authLogger.warn('Refresh-token blacklist failed (logged out locally anyway)');
      });
    }
    auth.clearSession();
    setProfile(undefined);
    setProfileError(null);
    clearProfileCache();
  }, []);

  const value: AccountsContextValue = useMemo(() => ({
    profile,
    isLoadingProfile,
    profileError,
    updateProfile,
    partialUpdateProfile,
    uploadAvatar,
    refreshProfile,
    requestOTP,
    verifyOTP,
    logout,
  }), [
    profile,
    isLoadingProfile,
    profileError,
    updateProfile,
    partialUpdateProfile,
    uploadAvatar,
    refreshProfile,
    requestOTP,
    verifyOTP,
    logout,
  ]);

  return (
    <AccountsContext.Provider value={value}>
      {children}
    </AccountsContext.Provider>
  );
}

export function useAccountsContext(): AccountsContextValue {
  const context = useContext(AccountsContext);
  if (!context) {
    throw new Error('useAccountsContext must be used within AccountsProvider');
  }
  return context;
}
