import AsyncStorage from '@react-native-async-storage/async-storage';
import { NavigationContainerRef, ParamListBase } from '@react-navigation/native';
import * as LocalAuthentication from 'expo-local-authentication';
import { router, useGlobalSearchParams, usePathname } from 'expo-router';
import React, { createContext, useContext, useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { AppState, Platform } from 'react-native';

import EncryptionUtility from '@/utils/EncryptionUtility';

import { useDb } from '@/context/DbContext';
import NativeVaultManager from '@/specs/NativeVaultManager';

// Create a navigation reference
export const navigationRef = React.createRef<NavigationContainerRef<ParamListBase>>();

export type AuthMethod = 'faceid' | 'password';

type AuthContextType = {
  isLoggedIn: boolean;
  isInitialized: boolean;
  username: string | null;
  isOffline: boolean;
  getEnabledAuthMethods: () => Promise<AuthMethod[]>;
  isBiometricsEnabled: () => Promise<boolean>;
  setAuthTokens: (username: string, accessToken: string, refreshToken: string) => Promise<void>;
  initializeAuth: () => Promise<{ isLoggedIn: boolean; enabledAuthMethods: AuthMethod[] }>;
  login: () => Promise<void>;
  logout: (errorMessage?: string) => Promise<void>;
  globalMessage: string | null;
  clearGlobalMessage: () => void;
  setAuthMethods: (methods: AuthMethod[]) => Promise<void>;
  getAuthMethodDisplay: () => Promise<string>;
  getAutoLockTimeout: () => Promise<number>;
  setAutoLockTimeout: (timeout: number) => Promise<void>;
  getBiometricDisplayName: () => Promise<string>;
  isBiometricsEnabledOnDevice: () => Promise<boolean>;
  setOfflineMode: (isOffline: boolean) => void;
  verifyPassword: (password: string) => Promise<string | null>;
  // Autofill methods
  shouldShowAutofillReminder: boolean;
  markAutofillConfigured: () => Promise<void>;
  // Return URL methods
  returnUrl: { path: string; params?: object } | null;
  setReturnUrl: (url: { path: string; params?: object } | null) => void;
}

const AUTOFILL_CONFIGURED_KEY = 'autofill_configured';

/**
 * Auth context.
 */
const AuthContext = createContext<AuthContextType | undefined>(undefined);

/**
 * AuthProvider to provide the authentication state to the app that components can use.
 */
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [isInitialized, setIsInitialized] = useState(false);
  const [username, setUsername] = useState<string | null>(null);
  const [globalMessage, setGlobalMessage] = useState<string | null>(null);
  const [shouldShowAutofillReminder, setShouldShowAutofillReminder] = useState(false);
  const [returnUrl, setReturnUrl] = useState<{ path: string; params?: object } | null>(null);
  const [isOffline, setIsOffline] = useState(false);
  const appState = useRef(AppState.currentState);
  const dbContext = useDb();
  const pathname = usePathname();
  const params = useGlobalSearchParams();
  const lastRouteRef = useRef<{ path: string, params?: object }>({ path: pathname, params });

  useEffect(() => {
    lastRouteRef.current = { path: pathname, params };
  }, [pathname, params]);

  /**
   * Get enabled auth methods from the native module
   */
  const getEnabledAuthMethods = useCallback(async (): Promise<AuthMethod[]> => {
    try {
      let methods = await NativeVaultManager.getAuthMethods() as AuthMethod[];
      // Check if Face ID is actually available despite being enabled
      if (methods.includes('faceid')) {
        const isEnrolled = await LocalAuthentication.isEnrolledAsync();
        if (!isEnrolled) {
          // Remove Face ID from the list of enabled auth methods
          methods = methods.filter(method => method !== 'faceid');
        }
      }
      return methods;
    } catch (error) {
      console.error('Failed to get enabled auth methods:', error);
      return ['password'];
    }
  }, []);

  /**
   * Check if biometrics is enabled on the device (regardless of whether it's enabled in the AliasVault app).
   */
  const isBiometricsEnabledOnDevice = useCallback(async (): Promise<boolean> => {
    const hasBiometrics = await LocalAuthentication.hasHardwareAsync();
    if (!hasBiometrics) {
      return false;
    }

    return await LocalAuthentication.isEnrolledAsync();
  }, []);

  /**
   * Check if biometrics is enabled based on enabled auth methods
   */
  const isBiometricsEnabled = useCallback(async (): Promise<boolean> => {
    const deviceHasBiometrics = await isBiometricsEnabledOnDevice();
    if (!deviceHasBiometrics) {
      return false;
    }

    const methods = await getEnabledAuthMethods();
    return methods.includes('faceid');
  }, [getEnabledAuthMethods, isBiometricsEnabledOnDevice]);

  /**
   * Set auth tokens in storage as part of the login process. After db is initialized, the login method should be called as well.
   */
  const setAuthTokens = useCallback(async (username: string, accessToken: string, refreshToken: string): Promise<void> => {
    await AsyncStorage.setItem('username', username);
    await AsyncStorage.setItem('accessToken', accessToken);
    await AsyncStorage.setItem('refreshToken', refreshToken);

    setUsername(username);
  }, []);

  /**
   * Initialize the authentication state, called on initial load by _layout.tsx.
   * @returns object containing whether the user is logged in and enabled auth methods
   */
  const initializeAuth = useCallback(async (): Promise<{ isLoggedIn: boolean; enabledAuthMethods: AuthMethod[] }> => {
    const accessToken = await AsyncStorage.getItem('accessToken') as string;
    const refreshToken = await AsyncStorage.getItem('refreshToken') as string;
    const username = await AsyncStorage.getItem('username') as string;
    let isAuthenticated = false;
    let methods: AuthMethod[] = ['password'];

    if (accessToken && refreshToken && username) {
      setUsername(username);
      setIsLoggedIn(true);
      isAuthenticated = true;
      methods = await getEnabledAuthMethods();
    }
    setIsInitialized(true);
    return { isLoggedIn: isAuthenticated, enabledAuthMethods: methods };
  }, [getEnabledAuthMethods]);

  /**
   * Set logged in status to true which refreshes the app.
   */
  const login = useCallback(async (): Promise<void> => {
    setIsLoggedIn(true);
  }, []);

  /**
   * Logout the user and clear the auth tokens from chrome storage.
   */
  const logout = useCallback(async (errorMessage?: string): Promise<void> => {
    await AsyncStorage.removeItem('username');
    await AsyncStorage.removeItem('accessToken');
    await AsyncStorage.removeItem('refreshToken');
    await AsyncStorage.removeItem('authMethods');
    dbContext?.clearDatabase();

    // Set local storage global message that will be shown on the login page.
    if (errorMessage) {
      setGlobalMessage(errorMessage);
    }

    setUsername(null);
    setIsLoggedIn(false);
  }, [dbContext]);

  /**
   * Clear global message (called after displaying the message).
   */
  const clearGlobalMessage = useCallback((): void => {
    setGlobalMessage(null);
  }, []);

  /**
   * Set the authentication methods and save them to storage
   */
  const setAuthMethods = useCallback(async (methods: AuthMethod[]): Promise<void> => {
    // Ensure password is always included
    const methodsToSave = methods.includes('password') ? methods : [...methods, 'password'];

    // Update native credentials manager
    try {
      await NativeVaultManager.setAuthMethods(methodsToSave);
    } catch (error) {
      console.error('Failed to update native auth methods:', error);
    }
  }, []);

  /**
   * Get the appropriate biometric display name based on device capabilities
   */
  const getBiometricDisplayName = useCallback(async (): Promise<string> => {
    try {
      const hasBiometrics = await LocalAuthentication.hasHardwareAsync();
      const enrolled = await LocalAuthentication.isEnrolledAsync();

      // For Android, we use the term "Biometrics" for facial recognition and fingerprint.
      if (Platform.OS === 'android') {
        return 'Biometrics';
      }

      // For iOS, we check if the device has explicit Face ID or Touch ID support.
      if (!hasBiometrics || !enrolled) {
        return 'Face ID / Touch ID';
      }

      const types = await LocalAuthentication.supportedAuthenticationTypesAsync();
      const hasFaceIDSupport = types.includes(LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION);
      const hasTouchIDSupport = types.includes(LocalAuthentication.AuthenticationType.FINGERPRINT);

      if (hasFaceIDSupport) {
        return 'Face ID';
      } else if (hasTouchIDSupport) {
        return 'Touch ID';
      }

      return 'Face ID / Touch ID';
    } catch (error) {
      console.error('Failed to get biometric display name:', error);
      return 'Face ID / Touch ID';
    }
  }, []);

  /**
   * Get the display label for the current auth method
   * Prefers Face ID if enabled, otherwise falls back to Password
   */
  const getAuthMethodDisplay = useCallback(async (): Promise<string> => {
    const methods = await getEnabledAuthMethods();
    if (methods.includes('faceid')) {
      try {
        if (await isBiometricsEnabledOnDevice()) {
          return await getBiometricDisplayName();
        }
      } catch (error) {
        console.error('Failed to check Face ID enrollment:', error);
      }
    }
    return 'Password';
  }, [getEnabledAuthMethods, getBiometricDisplayName, isBiometricsEnabledOnDevice]);

  /**
   * Get the auto-lock timeout from the iOS credentials manager
   */
  const getAutoLockTimeout = useCallback(async (): Promise<number> => {
    try {
      return await NativeVaultManager.getAutoLockTimeout();
    } catch (error) {
      console.error('Failed to get auto-lock timeout:', error);
      return 0;
    }
  }, []);

  /**
   * Set the auto-lock timeout in the iOS credentials manager
   */
  const setAutoLockTimeout = useCallback(async (timeout: number): Promise<void> => {
    try {
      await NativeVaultManager.setAutoLockTimeout(timeout);
    } catch (error) {
      console.error('Failed to update iOS auto-lock timeout:', error);
    }
  }, []);

  /**
   * Check if the vault is unlocked.
   */
  const isVaultUnlocked = useCallback(async (): Promise<boolean> => {
    try {
      return await NativeVaultManager.isVaultUnlocked();
    } catch (error) {
      console.error('Failed to check vault status:', error);
      return false;
    }
  }, []);

  /**
   * Verify the password. Returns the current password hash if the password is correct, otherwise returns null.
   */
  const verifyPassword = useCallback(async (password: string): Promise<string | null> => {
    // Check locally if the current password is correct by attempting to decrypt the vault
    const encryptionKeyDerivationParams = await NativeVaultManager.getEncryptionKeyDerivationParams();
    if (!encryptionKeyDerivationParams) {
      throw new Error('Failed to verify current password. Please try again.');
    }

    // Parse the key derivation parameters
    const params = JSON.parse(encryptionKeyDerivationParams);

    // Derive the encryption key from the password using the stored parameters
    const passwordHash = await EncryptionUtility.deriveKeyFromPassword(
      password,
      params.salt,
      params.encryptionType,
      params.encryptionSettings
    );

    const currentPasswordHashBase64 = Buffer.from(passwordHash).toString('base64');

    // Check if the current password is correct by attempting to decrypt the vault
    const dbAvailable = await dbContext.testDatabaseConnection(currentPasswordHashBase64);
    if (!dbAvailable) {
      return null;
    }

    return currentPasswordHashBase64;
  }, [dbContext]);

  // Handle app state changes
  useEffect(() => {
    const subscription = AppState.addEventListener('change', async (nextAppState) => {
      if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
        // App coming to foreground
        if (!pathname?.includes('unlock') && !pathname?.includes('login')) {
          try {
            // Check if vault is unlocked.
            const isUnlocked = await isVaultUnlocked();
            if (!isUnlocked) {
              // Get current full URL including query params
              const currentRoute = lastRouteRef.current;
              if (currentRoute?.path) {
                setReturnUrl({
                  path: currentRoute.path,
                  params: currentRoute.params
                });
              }

              // Database connection failed, navigate to reinitialize flow
              router.replace('/reinitialize');
            }
          } catch {
            // Database query failed, navigate to reinitialize flow
            router.replace('/reinitialize');
          }
        }
      }
      appState.current = nextAppState;
    });

    return (): void => {
      subscription.remove();
    };
  }, [isVaultUnlocked, pathname]);

  /**
   * Load autofill state from storage
   */
  const loadAutofillState = useCallback(async () => {
    const configured = await AsyncStorage.getItem(AUTOFILL_CONFIGURED_KEY);
    setShouldShowAutofillReminder(configured !== 'true');
  }, []);

  /**
   * Mark autofill as configured for the current platform
   */
  const markAutofillConfigured = useCallback(async () => {
    await AsyncStorage.setItem(AUTOFILL_CONFIGURED_KEY, 'true');
    setShouldShowAutofillReminder(false);
  }, []);

  // Load autofill state on mount
  useEffect(() => {
    loadAutofillState();
  }, [loadAutofillState]);

  const contextValue = useMemo(() => ({
    isLoggedIn,
    isInitialized,
    username,
    globalMessage,
    shouldShowAutofillReminder,
    returnUrl,
    isOffline,
    getEnabledAuthMethods,
    isBiometricsEnabled,
    setAuthTokens,
    initializeAuth,
    login,
    logout,
    clearGlobalMessage,
    setAuthMethods,
    getAuthMethodDisplay,
    isBiometricsEnabledOnDevice,
    getAutoLockTimeout,
    setAutoLockTimeout,
    getBiometricDisplayName,
    markAutofillConfigured,
    setReturnUrl,
    verifyPassword,
    setOfflineMode: setIsOffline,
  }), [
    isLoggedIn,
    isInitialized,
    username,
    globalMessage,
    shouldShowAutofillReminder,
    returnUrl,
    isOffline,
    getEnabledAuthMethods,
    isBiometricsEnabled,
    setAuthTokens,
    initializeAuth,
    login,
    logout,
    clearGlobalMessage,
    setAuthMethods,
    getAuthMethodDisplay,
    isBiometricsEnabledOnDevice,
    getAutoLockTimeout,
    setAutoLockTimeout,
    getBiometricDisplayName,
    markAutofillConfigured,
    setReturnUrl,
    verifyPassword,
  ]);

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

/**
 * Hook to use the AuthContext
 */
export const useAuth = (): AuthContextType => {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};