'use client';

import * as React from 'react';
import { usePathname, useRouter } from 'next/navigation';
import {
  apiFetch,
  ApiError,
  AUTH_REFRESH_KEY,
  AUTH_SESSION_KEY,
  AUTH_TOKEN_KEY,
  AUTH_USER_CACHE_KEY,
  AuthSessionResponse,
  AuthTokens,
  fetchAuthSession,
  getDeviceFingerprint,
  IdentifyResponse,
  getApiErrorMessage,
  isPinRequiredError,
  OtpSendResponse,
  PasswordlessAuthResponse,
  PinVerificationResponse,
  PublicUser,
  refreshAuthSession,
  resetPinRequiredNotification,
  setPinRequiredHandler
} from '@/lib/api';
import { useToast } from './toast-provider';
import { PinLockModal } from './pin-lock-modal';
import { EMBEDDED_AUTH_MESSAGE, type EmbeddedAuthPayload } from '@/lib/embedded-auth-bridge';

interface AuthContextValue {
  user: PublicUser | null;
  token: string | null;
  isLoading: boolean;
  isPinLocked: boolean;
  hasStoredSession: boolean;
  login: (login: string, password: string) => Promise<AuthTokens>;
  identifyLogin: (login: string) => Promise<IdentifyResponse>;
  sendLoginOtp: (recipient: string, channel?: string) => Promise<OtpSendResponse>;
  verifyLoginOtp: (recipient: string, code: string) => Promise<PasswordlessAuthResponse>;
  loginWithPassword: (login: string, password: string) => Promise<AuthTokens>;
  loginWithLdap: (username: string, password: string) => Promise<AuthTokens>;
  beginTotpLogin: (recipient: string) => Promise<string>;
  verifyTotpLogin: (totpChallengeToken: string, code: string) => Promise<AuthTokens>;
  completePin: (sessionId: string, pin: string) => Promise<void>;
  applyLoginAuth: (auth: AuthTokens) => AuthTokens;
  register: (data: { displayName: string; login: string; password: string }) => Promise<void>;
  refreshProfile: () => Promise<void>;
  logout: () => void;
}

const AuthContext = React.createContext<AuthContextValue | null>(null);

function cacheUserProfile(user: PublicUser) {
  window.localStorage.setItem(AUTH_USER_CACHE_KEY, JSON.stringify(user));
}

function readCachedUserProfile(): PublicUser | null {
  const raw = window.localStorage.getItem(AUTH_USER_CACHE_KEY);
  if (!raw) return null;
  try {
    return JSON.parse(raw) as PublicUser;
  } catch {
    return null;
  }
}

function persistPartialAuth(auth: AuthTokens) {
  window.localStorage.setItem(AUTH_REFRESH_KEY, auth.refreshToken);
  window.localStorage.setItem(AUTH_SESSION_KEY, auth.sessionId);
  cacheUserProfile(auth.user);
}

function applySessionState(
  session: AuthSessionResponse,
  setters: {
    setUser: React.Dispatch<React.SetStateAction<PublicUser | null>>;
    setToken: React.Dispatch<React.SetStateAction<string | null>>;
    activatePinLock: (sessionId: string) => void;
    clearPinLock: () => void;
  }
) {
  setters.setUser(session.user);
  cacheUserProfile(session.user);
  if (session.sessionId) {
    window.localStorage.setItem(AUTH_SESSION_KEY, session.sessionId);
  }
  if (session.requiresPin) {
    setters.activatePinLock(session.sessionId ?? '');
  } else {
    setters.clearPinLock();
  }
}

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const router = useRouter();
  const pathname = usePathname();
  const { showToast } = useToast();
  const [token, setToken] = React.useState<string | null>(null);
  const [user, setUser] = React.useState<PublicUser | null>(null);
  const [isLoading, setIsLoading] = React.useState(true);
  const [isPinLocked, setIsPinLocked] = React.useState(false);
  const [hasStoredSession, setHasStoredSession] = React.useState(false);
  const [lockedSessionId, setLockedSessionId] = React.useState<string | null>(null);
  const [pinSubmitting, setPinSubmitting] = React.useState(false);
  const [pinError, setPinError] = React.useState<string | null>(null);
  const isPinLockedRef = React.useRef(false);
  const refreshInFlightRef = React.useRef<Promise<void> | null>(null);

  React.useEffect(() => {
    isPinLockedRef.current = isPinLocked;
  }, [isPinLocked]);

  const clearPinLock = React.useCallback(() => {
    setIsPinLocked(false);
    setLockedSessionId(null);
    setPinError(null);
    resetPinRequiredNotification();
  }, []);

  React.useEffect(() => {
    function handleEmbeddedAuth(event: Event) {
      const detail = (event as CustomEvent<EmbeddedAuthPayload>).detail;
      if (!detail?.token?.trim()) return;
      setToken(detail.token.trim());
      setHasStoredSession(true);
      clearPinLock();
    }

    window.addEventListener(EMBEDDED_AUTH_MESSAGE, handleEmbeddedAuth as EventListener);
    return () => window.removeEventListener(EMBEDDED_AUTH_MESSAGE, handleEmbeddedAuth as EventListener);
  }, [clearPinLock]);

  const activatePinLock = React.useCallback((sessionId: string) => {
    const resolvedSessionId = sessionId || window.localStorage.getItem(AUTH_SESSION_KEY);
    if (!resolvedSessionId) return;
    setLockedSessionId(resolvedSessionId);
    setIsPinLocked(true);
    setPinError(null);
    setHasStoredSession(true);
    const cachedUser = readCachedUserProfile();
    if (cachedUser) {
      setUser(cachedUser);
    }
  }, []);

  const saveSession = React.useCallback(
    (auth: AuthTokens) => {
      window.localStorage.setItem(AUTH_TOKEN_KEY, auth.accessToken);
      window.localStorage.setItem(AUTH_REFRESH_KEY, auth.refreshToken);
      window.localStorage.setItem(AUTH_SESSION_KEY, auth.sessionId);
      setToken(auth.accessToken);
      setUser(auth.user);
      cacheUserProfile(auth.user);
      setHasStoredSession(true);
      clearPinLock();
    },
    [clearPinLock]
  );

  const logout = React.useCallback(() => {
    window.localStorage.removeItem(AUTH_TOKEN_KEY);
    window.localStorage.removeItem(AUTH_REFRESH_KEY);
    window.localStorage.removeItem(AUTH_SESSION_KEY);
    window.localStorage.removeItem(AUTH_USER_CACHE_KEY);
    setToken(null);
    setUser(null);
    setHasStoredSession(false);
    clearPinLock();
    router.push('/auth/login');
  }, [clearPinLock, router]);

  const refreshProfile = React.useCallback(async () => {
    if (refreshInFlightRef.current) {
      return refreshInFlightRef.current;
    }

    const task = (async () => {
      const currentToken = window.localStorage.getItem(AUTH_TOKEN_KEY);
      const refreshToken = window.localStorage.getItem(AUTH_REFRESH_KEY);
      setHasStoredSession(Boolean(refreshToken));

      if (!currentToken && !refreshToken) {
        setIsLoading(false);
        return;
      }

      try {
        if (currentToken) {
          setToken(currentToken);
          try {
            const session = await fetchAuthSession(currentToken);
            applySessionState(session, { setUser, setToken, activatePinLock, clearPinLock });
            return;
          } catch (error) {
            if (isPinRequiredError(error)) {
              activatePinLock(error.sessionId ?? window.localStorage.getItem(AUTH_SESSION_KEY) ?? '');
              return;
            }
            if (!(error instanceof ApiError) || error.code !== 'TOKEN_EXPIRED') {
              throw error;
            }
          }
        }

        if (refreshToken) {
          const refreshed = await refreshAuthSession();
          if (refreshed.accessToken) {
            window.localStorage.setItem(AUTH_TOKEN_KEY, refreshed.accessToken);
            setToken(refreshed.accessToken);
          }
          if (refreshed.sessionId) {
            window.localStorage.setItem(AUTH_SESSION_KEY, refreshed.sessionId);
          }
          if (refreshed.requiresPin) {
            if (refreshed.user) {
              setUser(refreshed.user);
              cacheUserProfile(refreshed.user);
            }
            activatePinLock(refreshed.sessionId ?? '');
            return;
          }
          if (refreshed.accessToken) {
            const session = await fetchAuthSession(refreshed.accessToken);
            applySessionState(session, { setUser, setToken, activatePinLock, clearPinLock });
            return;
          }
        }

        throw new ApiError('Сессия недействительна', 401);
      } catch (error) {
        if (isPinRequiredError(error)) {
          activatePinLock(error.sessionId ?? window.localStorage.getItem(AUTH_SESSION_KEY) ?? '');
          return;
        }
        if (refreshToken && error instanceof ApiError && error.status === 403) {
          activatePinLock(window.localStorage.getItem(AUTH_SESSION_KEY) ?? '');
          return;
        }
        if (error instanceof ApiError && (error.code === 'NO_REFRESH' || error.status === 401)) {
          logout();
          return;
        }
        const message = getApiErrorMessage(error, 'Не удалось загрузить профиль');
        if (message) showToast(message);
        logout();
      } finally {
        setIsLoading(false);
        refreshInFlightRef.current = null;
      }
    })();

    refreshInFlightRef.current = task;
    return task;
  }, [activatePinLock, clearPinLock, logout, showToast]);

  React.useEffect(() => {
    setPinRequiredHandler(activatePinLock);
    return () => setPinRequiredHandler(null);
  }, [activatePinLock]);

  React.useEffect(() => {
    void refreshProfile();
  }, [refreshProfile]);

  React.useEffect(() => {
    function handleVisibilityChange() {
      if (document.visibilityState !== 'visible') return;
      if (isPinLockedRef.current) return;
      if (!window.localStorage.getItem(AUTH_REFRESH_KEY)) return;
      void refreshProfile();
    }

    function handleAuthRefreshed(event: Event) {
      const detail = (event as CustomEvent<{ accessToken?: string; user?: PublicUser }>).detail;
      if (detail.accessToken) {
        setToken(detail.accessToken);
      }
      if (detail.user) {
        setUser(detail.user);
        cacheUserProfile(detail.user);
      }
    }

    document.addEventListener('visibilitychange', handleVisibilityChange);
    window.addEventListener('lendry:auth-refreshed', handleAuthRefreshed);

    return () => {
      document.removeEventListener('visibilitychange', handleVisibilityChange);
      window.removeEventListener('lendry:auth-refreshed', handleAuthRefreshed);
    };
  }, [refreshProfile]);

  const login = React.useCallback(
    async (loginValue: string, password: string) => {
      const auth = await apiFetch<AuthTokens>('/auth/login', {
        method: 'POST',
        body: JSON.stringify({
          login: loginValue,
          password,
          fingerprint: getDeviceFingerprint(),
          deviceName: navigator.userAgent.includes('Windows') ? 'Windows Browser' : 'Browser',
          deviceType: 'WEB'
        })
      });
      if (auth.pinVerified) {
        saveSession(auth);
      } else {
        persistPartialAuth(auth);
        setHasStoredSession(true);
      }
      return auth;
    },
    [saveSession]
  );

  const identifyLogin = React.useCallback(async (loginValue: string) => {
    return apiFetch<IdentifyResponse>('/auth/identify', {
      method: 'POST',
      body: JSON.stringify({ login: loginValue })
    });
  }, []);

  const sendLoginOtp = React.useCallback(async (recipient: string, channel?: string) => {
    return apiFetch<OtpSendResponse>('/auth/otp/send', {
      method: 'POST',
      body: JSON.stringify({ recipient, channel })
    });
  }, []);

  const verifyLoginOtp = React.useCallback(
    async (recipient: string, code: string) => {
      const response = await apiFetch<PasswordlessAuthResponse>('/auth/otp/verify', {
        method: 'POST',
        body: JSON.stringify({
          recipient,
          code,
          fingerprint: getDeviceFingerprint(),
          deviceName: navigator.userAgent.includes('Windows') ? 'Windows Browser' : 'Browser',
          deviceType: 'WEB'
        })
      });
      if (response.auth?.pinVerified) {
        saveSession(response.auth);
      } else if (response.auth) {
        persistPartialAuth(response.auth);
        setHasStoredSession(true);
      }
      return response;
    },
    [saveSession]
  );

  const loginWithPassword = React.useCallback(
    async (loginValue: string, passwordValue: string) => {
      const auth = await apiFetch<AuthTokens>('/auth/login/password', {
        method: 'POST',
        body: JSON.stringify({
          login: loginValue,
          password: passwordValue,
          fingerprint: getDeviceFingerprint(),
          deviceName: navigator.userAgent.includes('Windows') ? 'Windows Browser' : 'Browser',
          deviceType: 'WEB'
        })
      });
      if (auth.pinVerified) {
        saveSession(auth);
      } else {
        persistPartialAuth(auth);
        setHasStoredSession(true);
      }
      return auth;
    },
    [saveSession]
  );

  const loginWithLdap = React.useCallback(
    async (username: string, passwordValue: string) => {
      const auth = await apiFetch<AuthTokens>('/auth/ldap/login', {
        method: 'POST',
        body: JSON.stringify({
          username,
          password: passwordValue,
          fingerprint: getDeviceFingerprint(),
          deviceName: navigator.userAgent.includes('Windows') ? 'Windows Browser' : 'Browser',
          deviceType: 'WEB'
        })
      });
      if (auth.pinVerified) {
        saveSession(auth);
      } else {
        persistPartialAuth(auth);
        setHasStoredSession(true);
      }
      return auth;
    },
    [saveSession]
  );

  const beginTotpLogin = React.useCallback(async (recipient: string) => {
    const response = await apiFetch<{ totpChallengeToken: string }>('/auth/totp/begin', {
      method: 'POST',
      body: JSON.stringify({
        recipient,
        fingerprint: getDeviceFingerprint(),
        deviceName: navigator.userAgent.includes('Windows') ? 'Windows Browser' : 'Browser',
        deviceType: 'WEB'
      })
    });
    return response.totpChallengeToken;
  }, []);

  const verifyTotpLogin = React.useCallback(
    async (totpChallengeToken: string, code: string) => {
      const auth = await apiFetch<AuthTokens>('/auth/totp/verify', {
        method: 'POST',
        body: JSON.stringify({ totpChallengeToken, code })
      });
      if (auth.pinVerified) {
        saveSession(auth);
      } else {
        persistPartialAuth(auth);
        setHasStoredSession(true);
      }
      return auth;
    },
    [saveSession]
  );

  const applyLoginAuth = React.useCallback(
    (auth: AuthTokens) => {
      if (auth.pinVerified) {
        saveSession(auth);
      } else {
        persistPartialAuth(auth);
        setHasStoredSession(true);
        activatePinLock(auth.sessionId);
      }
      return auth;
    },
    [activatePinLock, saveSession]
  );

  const completePin = React.useCallback(
    async (sessionId: string, pin: string) => {
      const response = await apiFetch<PinVerificationResponse>('/auth/pin/verify', {
        method: 'POST',
        body: JSON.stringify({ sessionId, pin })
      });
      window.localStorage.setItem(AUTH_TOKEN_KEY, response.accessToken);
      window.localStorage.setItem(AUTH_SESSION_KEY, sessionId);
      setToken(response.accessToken);

      const session = await fetchAuthSession(response.accessToken);
      applySessionState(session, { setUser, setToken, activatePinLock, clearPinLock });

      if (pathname.startsWith('/auth/')) {
        router.replace('/');
      }
    },
    [activatePinLock, clearPinLock, pathname, router]
  );

  const handlePinUnlock = React.useCallback(
    async (pin: string) => {
      const sessionId = lockedSessionId ?? window.localStorage.getItem(AUTH_SESSION_KEY);
      if (!sessionId) {
        setPinError('Сессия не найдена. Войдите снова.');
        return;
      }

      setPinSubmitting(true);
      setPinError(null);
      try {
        await completePin(sessionId, pin);
      } catch (error) {
        setPinError(error instanceof Error ? error.message : 'Не удалось подтвердить PIN-код');
      } finally {
        setPinSubmitting(false);
      }
    },
    [completePin, lockedSessionId]
  );

  const register = React.useCallback(
    async (data: { displayName: string; login: string; password: string }) => {
      const isEmail = data.login.includes('@');
      await apiFetch<PublicUser>('/auth/register', {
        method: 'POST',
        body: JSON.stringify({
          displayName: data.displayName,
          password: data.password,
          ...(isEmail ? { email: data.login } : { phone: data.login })
        })
      });
      await login(data.login, data.password);
    },
    [login]
  );

  return (
    <AuthContext.Provider
      value={{
        user,
        token,
        isLoading,
        isPinLocked,
        hasStoredSession,
        login,
        identifyLogin,
        sendLoginOtp,
        verifyLoginOtp,
        loginWithPassword,
        loginWithLdap,
        beginTotpLogin,
        verifyTotpLogin,
        completePin,
        applyLoginAuth,
        register,
        refreshProfile,
        logout
      }}
    >
      {children}
      <PinLockModal open={isPinLocked} isSubmitting={pinSubmitting} error={pinError} onSubmit={handlePinUnlock} />
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const context = React.useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth должен использоваться внутри AuthProvider');
  }
  return context;
}
