// createContext jest potrzbny do tworzenia kontekstu w React - przechowywanie stanu zalogowanego użytkownika
// useContext jest potrzebny do pobierania stanu zalogowanego użytkownika z kontekstu
// useState jest potrzebny do przechowywania stanu zalogowanego użytkownika w komponencie
// useEffect jest potrzebny do synchronizacji stanu zalogowanego użytkownika z localStorage
// useCallback jest potrzebny do tworzenia funkcji, które nie zmieniają się przy każdym renderze komponentu
// useRef jest potrzebny do przechowywania wartości, która nie powoduje renderowania komponentu przy zmianie  
// [REQ F1]  System uwierzytelniania – login, 2FA, role, sesja
// [REQ T1]  Podstawowe hooki – useState, useEffect, useCallback, useRef
// [REQ T4]  Context API – globalny stan użytkownika dostępny przez useAuth()
// [REQ T6]  Optymalizacja – useCallback zapobiega tworzeniu nowych referencji funkcji
import { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react';
// axios jest potrzebny do wykonywania zapytań HTTP do API - odpowiednik fetch, ale z większą funkcjonalnością i prostszym API
import axios from 'axios';
import { API_URL } from '../config';

const AuthContext = createContext(null);
// klucz do localStorage do przechowywania stanu zalogowanego użytkownika
const STORAGE_KEY = 'rms_user';

export const AuthProvider = ({ children }) => {
  // inicjalizacja stanu zalogowanego użytkownika z localStorage, jeśli istnieje
  const [user, setUser] = useState(() => {
    try {
      // pobieranie stanu zalogowanego użytkownika z localStorage, jeśli istnieje
      const stored = localStorage.getItem(STORAGE_KEY);
      // jeśli uda się sparsować JSONa z localStorage, zwracamy obiekt użytkownika
      return stored ? JSON.parse(stored) : null;
    } 
    // jeśli nie uda się sparsować JSONa z localStorage, zwracamy null
      catch {
      return null;
    }
  });
  // state o stanie ładowania, domyślnie false, ustawiany na true podczas logowania i wylogowywania
  // bo po wejściu na stronę nic się jescze nie ładuje 
  const [loading, setLoading] = useState(false);
  // state o błędzie logowania, domyślnie null, ustawiany na string z komunikatem o błędzie podczas logowania
  const [error, setError] = useState(null);
  // state o stanie oczekiwania na weryfikację 2FA, domyślnie null, ustawiany na obiekt użytkownika po poprawnym logowaniu
  const [pendingUser, setPendingUser] = useState(null); // set after password OK, before 2FA
  // state o kodzie 2FA, domyślnie null, ustawiany na string z kodem 2FA po poprawnym logowaniu
  const [twoFACode, setTwoFACode] = useState(null);    // the "sent" code
  // useRef do przechowywania obiektu użytkownika podczas oczekiwania na weryfikację 2FA, nie powoduje renderowania komponentu przy zmianie
  const twoFARef = useRef(null);
  // useEffect do synchronizacji stanu zalogowanego użytkownika z localStorage, wywoływany przy zmianie stanu user
  // useEffect reguje na efekty uboczne poza reactem, w tym przypadku gdy zmienni się stan user
  // wtedy zapisujemy stan user do localStorage, jeśli user jest null to usuwamy z localStorage
  // localStorage jest to funkcja przegladarki do przechowywania danych w przeglądarce
  useEffect(() => {
    if (user) {
      // ustawiamy item po kluczu STORAGE_KEY w localStorage na string JSONa z obiektem user
      // STORAGE_KEY jest to klucz do localStorage, w tym przypadku 'rms_user' ustawiony wyżej
      localStorage.setItem(STORAGE_KEY, JSON.stringify(user));
    } else {
      // jeśli user jest null to usuwamy item po kluczu STORAGE_KEY z localStorage
      localStorage.removeItem(STORAGE_KEY);
    }
  }, [user]);
  // useCallback mówi reactowi żeby zapamiętał te funkcję w pamięci i nie tworzył jej przy każdym renderze komponentu
  // async mówi reactowi że funkcja jest asynchroniczna i zwraca promise, dzięki temu możemy używać await w tej funkcji
  // funkcja przyjmuje dwa parametry email i password, które są stringami, które użytkownik wpisuje w formularzu logowania
  const login = useCallback(async (email, password) => {
    setLoading(true);
    setError(null);
    try {
      // pytam backend o listę użytkowników, filtruję po emailu ,a następnie po password
      // jeśli znajdę użytkownika to zwracam obiekt użytkownika bez hasła, jeśli nie to rzucam błąd
      // await - czekamy na odpowiedź
      const { data } = await axios.get(`${API_URL}/users`, {
        params: { email },
      });
      const match = data.find(
        // (u) to funkcja strzałkowa, która przyjmuje obiekt użytkownika i zwraca true jeśli email i password się zgadzają
        (u) => u.email === email && u.password === password
      );

      if (!match) {
        throw new Error('Invalid email or password.');
      }

      if (match.verified === false) {
        throw new Error('Please verify your email address before signing in.');
      }

      // Never store the password in state or localStorage
      // destrukturzacjia z porzuceniem pola password z obiektu match, reszta pól idzie do safeUser
      const { password: _omit, ...safeUser } = match;

      // Generate fake 2FA code and wait for verification
      const code = String(Math.floor(100000 + Math.random() * 900000));
      twoFARef.current = safeUser;
      setTwoFACode(code);
      setPendingUser(safeUser);
      return { twoFAPending: true, code }; // caller shows the 2FA step
    } catch (err) {
      const knownMessages = [
        'Invalid email or password.',
        'Please verify your email address before signing in.',
      ];
      const message = knownMessages.includes(err.message)
        ? err.message
        : 'Could not reach the server. Make sure the API is running.';
      setError(message);
      throw new Error(message);
    } finally {
      setLoading(false); // wyłącza kręciołek ładowania
    }
  }, []); // [] funkcja nie zależy od żadnych zmiennych z zewnątrz komponentu

  const verify2FA = useCallback((inputCode) => {
    if (inputCode === twoFACode) {
      const safeUser = twoFARef.current;
      setPendingUser(null);
      setTwoFACode(null);
      twoFARef.current = null;
      setUser(safeUser);
      return safeUser;
    }
    throw new Error('Incorrect code. Please try again.');
  }, [twoFACode]);

  const cancel2FA = useCallback(() => {
    setPendingUser(null);
    setTwoFACode(null);
    twoFARef.current = null;
  }, []);

  const logout = useCallback(() => {
    setUser(null);
    setError(null);
  }, []);

  const clearError = useCallback(() => setError(null), []);
// zwrócenie komponentu AuthContext.Provider, który dostarcza wartości 
// user, loading, error, login, logout, clearError, pendingUser, twoFACode, 
// verify2FA i cancel2FA do całej aplikacji React, która jest opakowana w ten provider. 
// Dzięki temu możemy korzystać z tych wartości w dowolnym miejscu w aplikacji React za pomocą hooka useAuth.
  return (
    <AuthContext.Provider value={{
      user, loading, error, login, logout, clearError,
      pendingUser, twoFACode, verify2FA, cancel2FA,
    }}>
      {children}
    </AuthContext.Provider>
  );
};
// export const useAuth to custom hook, który pozwala na pobieranie wartości z AuthContext w dowolnym miejscu w aplikacji React
export const useAuth = () => {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used inside <AuthProvider>');
  return ctx;
};
