import {
  createContext,
  useContext,
  useState,
  useEffect,
  useCallback,
  useMemo,
  useRef,
  type ReactNode,
} from "react";
import { setSessionExpired, setTokenGetter, setRefreshFn } from "../utils/api";

const API_BASE = "/api/admin";

interface User {
  id: number;
  username: string;
  email: string;
  fullName: string;
  roleDisplay: string;
  isAdmin: boolean;
  totpEnabled: boolean;
  require2FA: boolean;
  permissions: string[];
  [key: string]: unknown;
}

interface AuthState {
  user: User | null;
  loading: boolean;
  error: string | null;
  isAuthenticated: boolean;
  isAdmin: boolean;
  permissions: string[];
  hasPermission: (permission: string) => boolean;
}

interface AuthActions {
  login: (
    username: string,
    password: string,
    remember?: boolean,
  ) => Promise<{
    success: boolean;
    requires2FA?: boolean;
    loginToken?: string;
    error?: string;
    remember?: boolean;
  }>;
  verify2FA: (
    loginToken: string,
    code: string,
    remember?: boolean,
    isBackup?: boolean,
  ) => Promise<{ success: boolean; error?: string }>;
  logout: () => Promise<void>;
  checkSession: () => Promise<boolean>;
  getAccessToken: () => string | null;
  apiRequest: (endpoint: string, options?: RequestInit) => Promise<Response>;
  silentRefresh: () => Promise<boolean>;
  updateUser: (updates: Partial<User>) => void;
}

const AuthStateContext = createContext<AuthState | null>(null);
const AuthActionsContext = createContext<AuthActions | null>(null);

function mapUser(u: Record<string, unknown> | null): User | null {
  if (!u) return null;
  const id = (u.userId ?? u.id) as number;
  const firstName = (u.firstName ?? u.first_name ?? "") as string;
  const lastName = (u.lastName ?? u.last_name ?? "") as string;
  const roleName = (u.roleName ?? u.role_name ?? "") as string;
  return {
    ...u,
    id,
    fullName: (u.fullName ??
      u.full_name ??
      `${firstName} ${lastName}`.trim()) as string,
    roleDisplay: (u.roleDisplay ?? u.role_display ?? roleName) as string,
    isAdmin: (u.isAdmin ?? u.is_admin ?? roleName === "admin") as boolean,
    totpEnabled: (u.totpEnabled ?? u.totp_enabled ?? false) as boolean,
    require2FA: (u.require2FA ?? u.require_2fa ?? false) as boolean,
    permissions: (u.permissions ?? []) as string[],
  } as User;
}

let accessToken: string | null = null;
let tokenExpiresAt: number | null = null;
let cachedUser: User | null = null;
let sessionFetched = false;
let silentRefreshInFlight: Promise<boolean> | null = null;

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(cachedUser);
  const [loading, setLoading] = useState(!sessionFetched);
  const [error, setError] = useState<string | null>(null);
  const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    cachedUser = user;
  }, [user]);

  const getAccessTokenFn = useCallback((): string | null => {
    if (!tokenExpiresAt || Date.now() > tokenExpiresAt - 30000) return null;
    return accessToken;
  }, []);

  const setAccessTokenFn = useCallback(
    (token: string | null, expiresIn?: number) => {
      const ttl = expiresIn ?? 900; // default 15 min matching backend config
      accessToken = token;
      tokenExpiresAt = token ? Date.now() + ttl * 1000 : null;
      if (refreshTimeoutRef.current) {
        clearTimeout(refreshTimeoutRef.current);
        refreshTimeoutRef.current = null;
      }
      if (token && ttl > 60) {
        refreshTimeoutRef.current = setTimeout(
          () => silentRefresh(),
          (ttl - 60) * 1000,
        );
      }
    },
    [],
  ); // eslint-disable-line react-hooks/exhaustive-deps

  const silentRefresh = useCallback(async (): Promise<boolean> => {
    // Deduplicate concurrent refresh calls — token rotation means only one call can succeed
    if (silentRefreshInFlight) return silentRefreshInFlight;

    const promise = (async (): Promise<boolean> => {
      try {
        const response = await fetch(`${API_BASE}/refresh`, {
          method: "POST",
          credentials: "include",
        });
        const data = await response.json();
        if (data.success && data.data?.access_token) {
          setAccessTokenFn(data.data.access_token, data.data.expires_in);
          setUser(mapUser(data.data.user));
          return true;
        }
        accessToken = null;
        tokenExpiresAt = null;
        setUser(null);
        cachedUser = null;
        setSessionExpired();
        return false;
      } catch {
        // Network error — don't kick the user out, just return false
        return false;
      } finally {
        silentRefreshInFlight = null;
      }
    })();

    silentRefreshInFlight = promise;
    return promise;
  }, [setAccessTokenFn]);

  const checkSession = useCallback(async (): Promise<boolean> => {
    try {
      const token = getAccessTokenFn();
      if (token) {
        const headers: Record<string, string> = {
          "Content-Type": "application/json",
          Authorization: `Bearer ${token}`,
        };
        const response = await fetch(`${API_BASE}/session`, {
          method: "GET",
          credentials: "include",
          headers,
        });
        if (response.status === 429 || response.status >= 500)
          return !!cachedUser;
        const data = await response.json();
        if (data.success && data.data?.user) {
          if (data.data.access_token) setAccessTokenFn(data.data.access_token);
          setUser(mapUser(data.data.user));
          cachedUser = mapUser(data.data.user);
          return true;
        }
      }
      // No token or session invalid — try silent refresh via cookie
      const refreshed = await silentRefresh();
      if (refreshed) return true;
      setUser(null);
      cachedUser = null;
      accessToken = null;
      tokenExpiresAt = null;
      return false;
    } catch {
      return !!cachedUser;
    } finally {
      setLoading(false);
      sessionFetched = true;
    }
  }, [getAccessTokenFn, setAccessTokenFn, silentRefresh]);

  useEffect(() => {
    setTokenGetter(getAccessTokenFn);
    setRefreshFn(silentRefresh);
  }, [getAccessTokenFn, silentRefresh]);

  useEffect(() => {
    checkSession();
    return () => {
      if (refreshTimeoutRef.current) clearTimeout(refreshTimeoutRef.current);
    };
  }, [checkSession]);

  const login = useCallback(
    async (username: string, password: string, remember = false) => {
      setError(null);
      try {
        const response = await fetch(`${API_BASE}/login`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          credentials: "include",
          body: JSON.stringify({ username, password, remember_me: remember }),
        });
        const data = await response.json();
        if (data.success) {
          if (data.data?.totp_required) {
            return {
              success: false,
              requires2FA: true,
              loginToken: data.data.login_token,
              remember,
            };
          }
          setAccessTokenFn(data.data.access_token, data.data.expires_in);
          setUser(mapUser(data.data.user));
          cachedUser = mapUser(data.data.user);
          sessionFetched = true;
          return { success: true };
        }
        setError(data.error);
        return { success: false, error: data.error };
      } catch {
        const errorMsg =
          "Chyba pripojeni. Zkontrolujte prosim pripojeni k internetu a zkuste to znovu.";
        setError(errorMsg);
        return { success: false, error: errorMsg };
      }
    },
    [setAccessTokenFn],
  );

  const verify2FA = useCallback(
    async (
      loginToken: string,
      code: string,
      remember = false,
      isBackup = false,
    ) => {
      setError(null);
      try {
        const response = await fetch(`${API_BASE}/login/totp`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          credentials: "include",
          body: JSON.stringify({
            login_token: loginToken,
            totp_code: code,
            remember_me: remember,
          }),
        });
        const data = await response.json();
        if (data.success) {
          setAccessTokenFn(data.data.access_token, data.data.expires_in);
          setUser(mapUser(data.data.user));
          cachedUser = mapUser(data.data.user);
          sessionFetched = true;
          return { success: true };
        }
        setError(data.error);
        return { success: false, error: data.error };
      } catch {
        const errorMsg = "Chyba pripojeni.";
        setError(errorMsg);
        return { success: false, error: errorMsg };
      }
    },
    [setAccessTokenFn],
  );

  const logout = useCallback(async () => {
    try {
      const token = getAccessTokenFn();
      await fetch(`${API_BASE}/logout`, {
        method: "POST",
        headers: { ...(token && { Authorization: `Bearer ${token}` }) },
        credentials: "include",
      });
    } catch {
      /* ignore */
    } finally {
      accessToken = null;
      tokenExpiresAt = null;
      setUser(null);
      cachedUser = null;
      sessionFetched = false;
      if (refreshTimeoutRef.current) {
        clearTimeout(refreshTimeoutRef.current);
        refreshTimeoutRef.current = null;
      }
    }
  }, [getAccessTokenFn]);

  const apiRequest = useCallback(
    async (endpoint: string, options: RequestInit = {}) => {
      let token = getAccessTokenFn();
      if (!token && user) {
        const refreshed = await silentRefresh();
        if (refreshed) token = getAccessTokenFn();
      }
      const headers: Record<string, string> = {
        "Content-Type": "application/json",
        ...(options.headers as Record<string, string>),
      };
      if (token) headers["Authorization"] = `Bearer ${token}`;
      const response = await fetch(`${API_BASE}${endpoint}`, {
        ...options,
        headers,
        credentials: "include",
      });
      if (response.status === 401 && user) {
        const refreshed = await silentRefresh();
        if (refreshed) {
          token = getAccessTokenFn();
          if (token) headers["Authorization"] = `Bearer ${token}`;
          return fetch(`${API_BASE}${endpoint}`, {
            ...options,
            headers,
            credentials: "include",
          });
        }
      }
      return response;
    },
    [getAccessTokenFn, silentRefresh, user],
  );

  const updateUser = useCallback((updates: Partial<User>) => {
    setUser((prev) => (prev ? { ...prev, ...updates } : null));
  }, []);

  const hasPermission = useCallback(
    (permission: string): boolean => {
      if (!user) return false;
      if (user.isAdmin) return true;
      return (user.permissions || []).includes(permission);
    },
    [user],
  );

  const permissions = useMemo(() => user?.permissions || [], [user]);

  const stateValue = useMemo<AuthState>(
    () => ({
      user,
      loading,
      error,
      isAuthenticated: !!user,
      isAdmin: user?.isAdmin || false,
      permissions,
      hasPermission,
    }),
    [user, loading, error, permissions, hasPermission],
  );

  const actionsValue = useMemo<AuthActions>(
    () => ({
      login,
      verify2FA,
      logout,
      checkSession,
      getAccessToken: getAccessTokenFn,
      apiRequest,
      silentRefresh,
      updateUser,
    }),
    [
      login,
      verify2FA,
      logout,
      checkSession,
      getAccessTokenFn,
      apiRequest,
      silentRefresh,
      updateUser,
    ],
  );

  return (
    <AuthActionsContext.Provider value={actionsValue}>
      <AuthStateContext.Provider value={stateValue}>
        {children}
      </AuthStateContext.Provider>
    </AuthActionsContext.Provider>
  );
}

export function useAuth(): AuthState & AuthActions {
  const state = useContext(AuthStateContext);
  const actions = useContext(AuthActionsContext);
  if (!state || !actions)
    throw new Error("useAuth must be used within an AuthProvider");
  return { ...state, ...actions };
}

export function useAuthState(): AuthState {
  const context = useContext(AuthStateContext);
  if (!context)
    throw new Error("useAuthState must be used within an AuthProvider");
  return context;
}

export function useAuthActions(): AuthActions {
  const context = useContext(AuthActionsContext);
  if (!context)
    throw new Error("useAuthActions must be used within an AuthProvider");
  return context;
}

export default AuthStateContext;
