import * as LocalAuthentication from "expo-local-authentication";
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from "react";
import { AppState, type AppStateStatus } from "react-native";

import { useAccounts } from "@/contexts/AccountsContext";
import {
  clearStoredPin,
  getAppLockEnabled,
  getBiometricEnabled,
  getStoredPin,
  isValidPin,
  setAppLockEnabled,
  setBiometricEnabled,
  setStoredPin,
} from "@/lib/app-lock";

type AppLockContextValue = {
  enabled: boolean;
  biometricEnabled: boolean;
  hasPin: boolean;
  isLocked: boolean;
  biometricAvailable: boolean;
  biometricLabel: string;
  unlockWithPin: (pin: string) => Promise<boolean>;
  unlockWithBiometric: () => Promise<boolean>;
  enableLock: (pin: string) => Promise<void>;
  disableLock: (pin: string) => Promise<boolean>;
  changePin: (currentPin: string, nextPin: string) => Promise<boolean>;
  setUseBiometric: (enabled: boolean) => Promise<void>;
  lock: () => void;
};

const AppLockContext = createContext<AppLockContextValue | null>(null);

export function AppLockProvider({ children }: { children: ReactNode }) {
  const { activeAccountId } = useAccounts();
  const [enabled, setEnabled] = useState(false);
  const [biometricEnabled, setBiometricEnabledState] = useState(false);
  const [hasPin, setHasPin] = useState(false);
  const [isLocked, setIsLocked] = useState(false);
  const [biometricAvailable, setBiometricAvailable] = useState(false);
  const [biometricLabel, setBiometricLabel] = useState("Biometrics");
  const wasBackgrounded = useRef(false);
  const biometricUnlockInProgress = useRef(false);
  const hydrated = useRef(false);

  useEffect(() => {
    if (!activeAccountId) {
      setEnabled(false);
      setHasPin(false);
      setBiometricEnabledState(false);
      setIsLocked(false);
      hydrated.current = false;
      return;
    }

    let cancelled = false;
    hydrated.current = false;
    const accountId = activeAccountId;

    async function hydrate() {
      const [lockEnabled, pin, bioEnabled, hasHardware, isEnrolled, authTypes] =
        await Promise.all([
          getAppLockEnabled(accountId),
          getStoredPin(accountId),
          getBiometricEnabled(accountId),
          LocalAuthentication.hasHardwareAsync(),
          LocalAuthentication.isEnrolledAsync(),
          LocalAuthentication.supportedAuthenticationTypesAsync(),
        ]);

      if (cancelled) return;

      const bioAvailable = hasHardware && isEnrolled;
      setEnabled(lockEnabled);
      setHasPin(Boolean(pin));
      setBiometricEnabledState(bioEnabled && bioAvailable);
      setBiometricAvailable(bioAvailable);
      setBiometricLabel(
        authTypes.includes(LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION)
          ? "Face ID"
          : authTypes.includes(LocalAuthentication.AuthenticationType.FINGERPRINT)
            ? "Touch ID"
            : "Biometrics",
      );
      setIsLocked(lockEnabled);
      hydrated.current = true;
    }

    void hydrate();
    return () => {
      cancelled = true;
    };
  }, [activeAccountId]);

  useEffect(() => {
    const subscription = AppState.addEventListener("change", (nextState: AppStateStatus) => {
      if (!hydrated.current || !enabled || !activeAccountId) return;

      // Only true backgrounding should re-lock — `inactive` fires during Face ID,
      // Control Center, and other system sheets and must not trigger another lock.
      if (nextState === "background") {
        wasBackgrounded.current = true;
      }

      if (
        nextState === "active" &&
        wasBackgrounded.current &&
        !biometricUnlockInProgress.current
      ) {
        wasBackgrounded.current = false;
        setIsLocked(true);
      }
    });

    return () => subscription.remove();
  }, [enabled, activeAccountId]);

  const unlockWithPin = useCallback(
    async (pin: string) => {
      if (!activeAccountId) return false;
      const stored = await getStoredPin(activeAccountId);
      if (!stored || stored !== pin) {
        return false;
      }
      wasBackgrounded.current = false;
      setIsLocked(false);
      return true;
    },
    [activeAccountId],
  );

  const unlockWithBiometric = useCallback(async () => {
    if (!biometricAvailable || !activeAccountId) {
      return false;
    }

    biometricUnlockInProgress.current = true;
    try {
      const result = await LocalAuthentication.authenticateAsync({
        promptMessage: "Unlock beenvoice",
        cancelLabel: "Use PIN",
        disableDeviceFallback: false,
        biometricsSecurityLevel: "weak",
      });

      if (!result.success) {
        return false;
      }

      if (!biometricEnabled) {
        await setBiometricEnabled(activeAccountId, true);
        setBiometricEnabledState(true);
      }

      wasBackgrounded.current = false;
      setIsLocked(false);
      return true;
    } finally {
      biometricUnlockInProgress.current = false;
    }
  }, [biometricAvailable, biometricEnabled, activeAccountId]);

  const enableLock = useCallback(
    async (pin: string) => {
      if (!activeAccountId) {
        throw new Error("No active account");
      }
      if (!isValidPin(pin)) {
        throw new Error("PIN must be 4–6 digits");
      }

      const [hasHardware, isEnrolled] = await Promise.all([
        LocalAuthentication.hasHardwareAsync(),
        LocalAuthentication.isEnrolledAsync(),
      ]);
      const bioAvailable = hasHardware && isEnrolled;

      await setStoredPin(activeAccountId, pin);
      await setAppLockEnabled(activeAccountId, true);
      setHasPin(true);
      setEnabled(true);
      setIsLocked(false);

      if (bioAvailable) {
        await setBiometricEnabled(activeAccountId, true);
        setBiometricEnabledState(true);
      }
    },
    [activeAccountId],
  );

  const disableLock = useCallback(
    async (pin: string) => {
      if (!activeAccountId) return false;
      const stored = await getStoredPin(activeAccountId);
      if (!stored || stored !== pin) {
        return false;
      }
      await setAppLockEnabled(activeAccountId, false);
      await clearStoredPin(activeAccountId);
      await setBiometricEnabled(activeAccountId, false);
      setEnabled(false);
      setHasPin(false);
      setBiometricEnabledState(false);
      setIsLocked(false);
      return true;
    },
    [activeAccountId],
  );

  const changePin = useCallback(
    async (currentPin: string, nextPin: string) => {
      if (!activeAccountId) return false;
      const stored = await getStoredPin(activeAccountId);
      if (!stored || stored !== currentPin || !isValidPin(nextPin)) {
        return false;
      }
      await setStoredPin(activeAccountId, nextPin);
      return true;
    },
    [activeAccountId],
  );

  const setUseBiometric = useCallback(
    async (next: boolean) => {
      if (!activeAccountId) return;
      if (next) {
        const result = await LocalAuthentication.authenticateAsync({
          promptMessage: `Enable ${biometricLabel}`,
          cancelLabel: "Cancel",
          disableDeviceFallback: true,
        });
        if (!result.success) return;
      }
      await setBiometricEnabled(activeAccountId, next);
      setBiometricEnabledState(next);
    },
    [biometricLabel, activeAccountId],
  );

  const lock = useCallback(() => {
    if (enabled) {
      setIsLocked(true);
    }
  }, [enabled]);

  const value = useMemo(
    () => ({
      enabled,
      biometricEnabled,
      hasPin,
      isLocked,
      biometricAvailable,
      biometricLabel,
      unlockWithPin,
      unlockWithBiometric,
      enableLock,
      disableLock,
      changePin,
      setUseBiometric,
      lock,
    }),
    [
      enabled,
      biometricEnabled,
      hasPin,
      isLocked,
      biometricAvailable,
      biometricLabel,
      unlockWithPin,
      unlockWithBiometric,
      enableLock,
      disableLock,
      changePin,
      setUseBiometric,
      lock,
    ],
  );

  return <AppLockContext.Provider value={value}>{children}</AppLockContext.Provider>;
}

export function useAppLock() {
  const context = useContext(AppLockContext);
  if (!context) {
    throw new Error("useAppLock must be used within AppLockProvider");
  }
  return context;
}
