import * as Haptics from 'expo-haptics';
import { LinearGradient } from 'expo-linear-gradient';
import { router } from 'expo-router';
import { useState, useEffect, useCallback } from 'react';
import { StyleSheet, View, KeyboardAvoidingView, Platform, ScrollView, Dimensions, Text } from 'react-native';

import { AppUnlockUtility } from '@/utils/AppUnlockUtility';
import { HapticsUtility } from '@/utils/HapticsUtility';
import { AppErrorCode, getAppErrorCode, getErrorTranslationKey, formatErrorWithCode } from '@/utils/types/errors/AppErrorCodes';
import { VaultVersionIncompatibleError } from '@/utils/types/errors/VaultVersionIncompatibleError';

import { useColors } from '@/hooks/useColorScheme';
import { useLogout } from '@/hooks/useLogout';
import { useTranslation } from '@/hooks/useTranslation';

import Logo from '@/assets/images/logo.svg';
import { ConfirmDialog } from '@/components/common/ConfirmDialog';
import LoadingIndicator from '@/components/LoadingIndicator';
import { ThemedText } from '@/components/themed/ThemedText';
import { Avatar } from '@/components/ui/Avatar';
import { RobustPressable } from '@/components/ui/RobustPressable';
import { useApp } from '@/context/AppContext';
import { useDb } from '@/context/DbContext';
import NativeVaultManager from '@/specs/NativeVaultManager';

/**
 * Unlock screen.
 */
export default function UnlockScreen() : React.ReactNode {
  const { isLoggedIn, username, getEncryptionKeyDerivationParams } = useApp();
  const { logoutUserInitiated, logoutForced } = useLogout();
  const dbContext = useDb();
  const [isLoading, setIsLoading] = useState(true);
  const [isBiometricsAvailable, setIsBiometricsAvailable] = useState(false);
  const colors = useColors();
  const { t } = useTranslation();
  const [biometricDisplayName, setBiometricDisplayName] = useState('');

  // PIN unlock state
  const [pinAvailable, setPinAvailable] = useState(false);

  // Error state for password unlock
  const [error, setError] = useState<string | null>(null);

  // Alert dialog state
  const [alertConfig, setAlertConfig] = useState<{ title: string; message: string } | null>(null);

  /**
   * Check if the key derivation parameters are stored in native storage.
   * If not, we can't unlock the vault so logout instead to redirect user to login screen.
   */
  const getKeyDerivationParams = useCallback(async () : Promise<{ salt: string; encryptionType: string; encryptionSettings: string } | null> => {
    const params = await getEncryptionKeyDerivationParams();
    if (!params) {
      // No params means corrupted state - force logout without confirmation
      await logoutForced();
      return null;
    }
    return params;
  }, [logoutForced, getEncryptionKeyDerivationParams]);

  useEffect(() => {
    let isMounted = true;

    /**
     * Fetch config and display unlock screen.
     * Note: The user has been redirected here because automatic unlock (biometric/PIN)
     * failed or was cancelled in initialize/reinitialize. We should NOT attempt
     * automatic unlock again - just show the manual unlock options.
     */
    const fetchConfig = async (): Promise<void> => {
      await getKeyDerivationParams();

      if (!isMounted) {
        return;
      }

      // Check if biometrics is available
      const enabled = await AppUnlockUtility.isBiometricUnlockAvailable();
      if (!isMounted) {
        return;
      }
      setIsBiometricsAvailable(enabled);

      const displayName = await AppUnlockUtility.getBiometricDisplayName();
      if (!isMounted) {
        return;
      }
      setBiometricDisplayName(displayName);

      // Check if PIN is enabled
      const pinEnabled = await NativeVaultManager.isPinEnabled();
      if (!isMounted) {
        return;
      }
      setPinAvailable(pinEnabled);

      /*
       * If ONLY password unlock is available (no biometric, no PIN),
       * show password unlock immediately to avoid showing empty unlock screen.
       * Otherwise, show the unlock screen with manual retry options.
       */
      if (!enabled && !pinEnabled) {
        // Only password available - show it immediately
        try {
          if (!isLoggedIn || !username) {
            router.replace('/login');
            return;
          }

          const unlocked = await NativeVaultManager.showPasswordUnlock(
            t('auth.unlockVault'),
            t('auth.enterPassword'),
            null
          );

          if (!unlocked) {
            // User cancelled - show the unlock screen
            setIsLoading(false);
            return;
          }

          if (await dbContext.hasPendingMigrations()) {
            router.replace('/upgrade');
            return;
          }

          // Haptic feedback for successful unlock
          HapticsUtility.notification(Haptics.NotificationFeedbackType.Success);

          router.replace('/reinitialize');
        } catch (err) {
          if (err instanceof VaultVersionIncompatibleError) {
            await logoutForced();
            return;
          }

          // Check if max attempts reached
          if (err && typeof err === 'object' && 'code' in err && err.code === 'MAX_ATTEMPTS_REACHED') {
            // Max attempts reached - vault has been cleared, force logout
            await logoutForced();
            return;
          }

          console.error('Unlock error:', err);
          const errorCode = getAppErrorCode(err);

          // Haptic feedback for authentication error
          HapticsUtility.notification(Haptics.NotificationFeedbackType.Error);

          if (!errorCode || errorCode === AppErrorCode.VAULT_DECRYPT_FAILED) {
            setError(t('auth.errors.incorrectPassword'));
          } else {
            const translationKey = getErrorTranslationKey(errorCode);
            setError(formatErrorWithCode(t(translationKey), errorCode));
          }
          setIsLoading(false);
        }
      } else {
        // Biometric or PIN available - show unlock screen with manual retry options
        setIsLoading(false);
      }
    };

    fetchConfig();

    return (): void => {
      isMounted = false;
    };
  }, [getKeyDerivationParams, dbContext, isLoggedIn, username, t, logoutForced]);

  /**
   * Hide the alert dialog.
   */
  const hideAlert = useCallback((): void => {
    setAlertConfig(null);
  }, []);

  /**
   * Handle password unlock using native password screen.
   */
  const handlePasswordUnlock = useCallback(async () : Promise<void> => {
    setError(null);
    setIsLoading(true);
    try {
      if (!isLoggedIn || !username) {
        // No username means we're not logged in, redirect to login
        router.replace('/login');
        return;
      }

      // Show native password unlock screen
      const unlocked = await NativeVaultManager.showPasswordUnlock(
        t('auth.unlockVault'),
        t('auth.enterPassword'),
        null
      );

      // User cancelled
      if (!unlocked) {
        setIsLoading(false);
        return;
      }

      // Check if the vault is up to date, if not, redirect to the upgrade page.
      if (await dbContext.hasPendingMigrations()) {
        router.replace('/upgrade');
        return;
      }

      // Haptic feedback for successful unlock
      HapticsUtility.notification(Haptics.NotificationFeedbackType.Success);

      /*
       * Navigate to reinitialize which will sync vault with server
       * and then navigate to the appropriate destination.
       */
      router.replace('/reinitialize');
    } catch (err) {
      if (err instanceof VaultVersionIncompatibleError) {
        // Vault version incompatible - force logout without confirmation
        await logoutForced();
        return;
      }

      // Check if max attempts reached
      if (err && typeof err === 'object' && 'code' in err && err.code === 'MAX_ATTEMPTS_REACHED') {
        // Max attempts reached - vault has been cleared, force logout
        await logoutForced();
        return;
      }

      // Try to extract error code from the error
      const errorCode = getAppErrorCode(err);

      // Haptic feedback for authentication error
      HapticsUtility.notification(Haptics.NotificationFeedbackType.Error);

      /*
       * During unlock, VAULT_DECRYPT_FAILED indicates wrong password.
       * This is thrown when decryption fails due to incorrect encryption key.
       */
      if (!errorCode || errorCode === AppErrorCode.VAULT_DECRYPT_FAILED) {
        // Treat as incorrect password - show error and allow retry
        setError(t('auth.errors.incorrectPassword'));
      } else {
        // Other error codes: show the formatted message with raw error code
        const translationKey = getErrorTranslationKey(errorCode);
        setError(formatErrorWithCode(t(translationKey), errorCode));
      }
    } finally {
      setIsLoading(false);
    }
  }, [isLoggedIn, username, dbContext, t, logoutForced]);

  /**
   * Internal PIN unlock handler - performs PIN unlock and navigates on success.
   * Returns true if unlock succeeded, false otherwise.
   */
  const performPinUnlock = useCallback(async () : Promise<boolean> => {
    try {
      await NativeVaultManager.showPinUnlock();

      // Check if vault is now unlocked
      const isNowUnlocked = await NativeVaultManager.isVaultUnlocked();
      if (isNowUnlocked) {
        // Check if the vault is up to date
        if (await dbContext.hasPendingMigrations()) {
          router.replace('/upgrade');
          return true;
        }

        // Haptic feedback for successful unlock
        HapticsUtility.notification(Haptics.NotificationFeedbackType.Success);

        router.replace('/reinitialize');
        return true;
      }
      // Not unlocked means user cancelled - return false but don't show error
      return false;
    } catch {
      return false;
    }
  }, [dbContext]);

  /**
   * Handle the biometrics retry - directly triggers biometric unlock.
   * If biometric fails and PIN is available, automatically falls back to PIN.
   */
  const handleBiometricRetry = useCallback(async () : Promise<void> => {
    setIsLoading(true);
    setError(null);
    try {
      /*
       * Clear any wrong key from memory first (e.g., from failed password attempt).
       * This forces getEncryptionKey() to fetch from keychain via biometrics.
       */
      await NativeVaultManager.clearEncryptionKeyFromMemory();

      // unlockVault now throws errors instead of returning false
      await dbContext.unlockVault();

      // Check if the vault is up to date
      if (await dbContext.hasPendingMigrations()) {
        router.replace('/upgrade');
        return;
      }

      // Haptic feedback for successful unlock
      HapticsUtility.notification(Haptics.NotificationFeedbackType.Success);

      router.replace('/reinitialize');
    } catch (err) {
      console.error('Biometric retry error:', err);

      // Check if this is a cancellation - don't show error, just allow retry
      const errorCode = getAppErrorCode(err);
      if (errorCode === 'E-509') { // BIOMETRIC_CANCELLED
        // User cancelled - don't show error, just stay on screen
        return;
      }

      // For other errors, try PIN fallback if available
      if (pinAvailable) {
        await performPinUnlock();
      } else if (errorCode) {
        // Haptic feedback for authentication error
        HapticsUtility.notification(Haptics.NotificationFeedbackType.Error);

        // Show the error with code if no PIN fallback
        const translationKey = getErrorTranslationKey(errorCode);
        setError(formatErrorWithCode(t(translationKey), errorCode));
      }
    } finally {
      setIsLoading(false);
    }
  }, [dbContext, pinAvailable, performPinUnlock, t]);

  /**
   * Handle the PIN retry button - directly triggers PIN unlock flow.
   */
  const handlePinRetry = useCallback(async () : Promise<void> => {
    setIsLoading(true);
    setError(null);
    await performPinUnlock();
    setIsLoading(false);
  }, [performPinUnlock]);

  /**
   * Get ordered unlock methods based on availability and preference.
   * Returns array of unlock methods in priority order: biometrics > PIN > password
   */
  const unlockMethods = ((): Array<{
    type: 'biometrics' | 'pin' | 'password';
    label: string;
    handler: () => Promise<void>;
  }> => {
    const methods: Array<{
      type: 'biometrics' | 'pin' | 'password';
      label: string;
      handler: () => Promise<void>;
    }> = [];

    // Add methods in priority order
    if (isBiometricsAvailable) {
      methods.push({
        type: 'biometrics',
        label: t('auth.unlockWithBiometric', { biometric: biometricDisplayName }),
        handler: handleBiometricRetry,
      });
    }

    if (pinAvailable) {
      methods.push({
        type: 'pin',
        label: t('auth.unlockWithPin'),
        handler: handlePinRetry,
      });
    }

    // Password is always available
    methods.push({
      type: 'password',
      label: t('auth.unlockWithPassword'),
      handler: handlePasswordUnlock,
    });

    return methods;
  })();

  const styles = StyleSheet.create({
    appName: {
      color: colors.text,
      fontSize: 32,
      fontWeight: 'bold',
      textAlign: 'center',
    },
    avatarContainer: {
      alignItems: 'center',
      flexDirection: 'row',
      justifyContent: 'center',
      marginBottom: 16,
    },
    button: {
      alignItems: 'center',
      backgroundColor: colors.primary,
      borderRadius: 8,
      justifyContent: 'center',
      marginBottom: 16,
      minHeight: 50,
      paddingVertical: 8,
      width: '100%',
    },
    buttonText: {
      color: colors.primarySurfaceText,
      fontSize: 16,
      fontWeight: '600',
      paddingHorizontal: 16,
      paddingVertical: 4,
      textAlign: 'center',
    },
    container: {
      flex: 1,
    },
    content: {
      backgroundColor: colors.accentBackground,
      borderRadius: 10,
      padding: 20,
      width: '100%',
    },
    errorText: {
      color: colors.errorText,
      fontSize: 14,
      marginBottom: 12,
      textAlign: 'center',
    },
    faceIdButton: {
      alignItems: 'center',
      justifyContent: 'center',
      minHeight: 50,
      paddingVertical: 8,
      width: '100%',
    },
    faceIdButtonText: {
      color: colors.primary,
      fontSize: 16,
      fontWeight: '600',
      paddingHorizontal: 16,
      paddingVertical: 4,
      textAlign: 'center',
    },
    gradientContainer: {
      height: Dimensions.get('window').height * 0.4,
      left: 0,
      position: 'absolute',
      right: 0,
      top: 0,
    },
    headerSection: {
      paddingBottom: 24,
      paddingHorizontal: 16,
      paddingTop: 24,
    },
    input: {
      backgroundColor: colors.background,
      borderRadius: 8,
      color: colors.text,
      flex: 1,
      fontSize: 16,
      height: 50,
      paddingHorizontal: 16,
    },
    inputContainer: {
      alignItems: 'center',
      backgroundColor: colors.background,
      borderColor: colors.accentBorder,
      borderRadius: 8,
      borderWidth: 1,
      flexDirection: 'row',
      marginBottom: 16,
    },
    inputIcon: {
      padding: 12,
    },
    linkButton: {
      marginTop: 16,
    },
    linkButtonText: {
      color: colors.primary,
      fontSize: 16,
      textAlign: 'center',
    },
    loadingContainer: {
      alignItems: 'center',
      flex: 1,
      justifyContent: 'flex-start',
      paddingHorizontal: 20,
      paddingTop: '40%',
    },
    logoContainer: {
      alignItems: 'center',
      marginBottom: 8,
    },
    logoutButton: {
      alignSelf: 'center',
      justifyContent: 'center',
      marginTop: 16,
    },
    logoutButtonText: {
      color: colors.red,
      fontSize: 16,
    },
    mainContent: {
      flex: 1,
      justifyContent: 'center',
      paddingBottom: 40,
      paddingHorizontal: 20,
    },
    scrollContent: {
      flexGrow: 1,
    },
    subtitle: {
      color: colors.text,
      fontSize: 16,
      marginBottom: 24,
      opacity: 0.7,
      textAlign: 'center',
    },
    username: {
      color: colors.text,
      fontSize: 18,
      opacity: 0.8,
      textAlign: 'center',
    },
  });

  // Render password mode or loading
  return (
    <KeyboardAvoidingView
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      style={styles.container}
      testID="unlock-screen"
    >
      {isLoading ? (
        <View style={styles.loadingContainer}>
          <LoadingIndicator status={t('app.status.unlockingVault')} />
        </View>
      ) : (
        <ScrollView
          style={styles.container}
          contentContainerStyle={styles.scrollContent}
          keyboardShouldPersistTaps="handled"
        >
          <LinearGradient
            colors={[colors.loginHeader, colors.background]}
            style={styles.gradientContainer}
          />
          <View style={styles.mainContent}>
            <View style={styles.headerSection}>
              <View style={styles.logoContainer}>
                <Logo width={80} height={80} />
                <Text style={styles.appName}>{t('auth.unlockVault')}</Text>
              </View>
            </View>
            <View style={styles.content}>
              <View style={styles.avatarContainer}>
                <Avatar />
                <ThemedText style={styles.username}>{username}</ThemedText>
              </View>

              {/* Error Message */}
              {error && <ThemedText style={styles.errorText}>{error}</ThemedText>}

              {/* Unlock buttons in priority order */}
              {unlockMethods.map((method, index) => (
                <RobustPressable
                  key={method.type}
                  style={index === 0 ? styles.button : styles.faceIdButton}
                  onPress={method.handler}
                  disabled={isLoading && index === 0}
                  testID={index === 0 ? 'unlock-button' : undefined}
                >
                  <ThemedText style={index === 0 ? styles.buttonText : styles.faceIdButtonText}>
                    {index === 0 && isLoading ? t('auth.unlocking') : method.label}
                  </ThemedText>
                </RobustPressable>
              ))}
            </View>

            <RobustPressable
              style={styles.logoutButton}
              onPress={logoutUserInitiated}
              testID="logout-button"
            >
              <ThemedText style={styles.logoutButtonText}>{t('auth.logout')}</ThemedText>
            </RobustPressable>
          </View>
        </ScrollView>
      )}

      <ConfirmDialog
        isVisible={alertConfig !== null}
        title={alertConfig?.title ?? ''}
        message={alertConfig?.message ?? ''}
        buttons={[{ text: t('common.ok'), style: 'default', onPress: hideAlert }]}
        onClose={hideAlert}
      />
    </KeyboardAvoidingView>
  );
}