import { AuthContext, type UserInfo } from '@/contexts/AuthContext';
import type { GenerateTokenResponse } from '@/features/authentication/api/identityAPI';
import axios from 'axios';
import {
  useCallback,
  useEffect,
  useRef,
  useState,
  type ReactNode,
} from 'react';
import { jwtDecode } from 'jwt-decode';
import type { GUID } from '@/types/commonTypes';

export interface AccessTokenJwtPayload {
  iss: string;
  nbf: number;
  iat: number;
  exp: number;
  scope: string[];
  amr: string[];
  client_id: string;
  sub: GUID;
  auth_time: number;
  idp: string;
  picture: string;
  given_name: string;
  family_name: string;
  email: string;
  name: string;
  phone_number: string;
  session: GUID;
  jti: string;
}

export const ACCESS_TOKEN_KEY = 'access_token';
export const REFRESH_TOKEN_KEY = 'refresh_token';
export const EXPIRES_IN_KEY = 'expires_in';

export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
  const [authFinished, setAuthFinished] = useState<boolean>(false);
  const [accessToken, setAccessToken] = useState<string | null>(null);

  const refreshPromiseRef = useRef<Promise<string | null> | null>(null);
  const expiresAtRef = useRef<number>(0);

  const extractUserFromToken = useCallback((token: string) => {
    try {
      const decoded = jwtDecode<AccessTokenJwtPayload>(token);
      setUserInfo({
        picture: decoded.picture,
        firstName: decoded.given_name,
        lastName: decoded.family_name,
        fullName: decoded.name,
        email: decoded.email,
        phoneNumber: decoded.phone_number,
        userID: decoded.sub,
      });
    } catch (e) {
      console.error('Failed to decode token', e);
      setUserInfo(null);
    }
  }, []);

  const logout = useCallback(() => {
    setAccessToken(null);
    setUserInfo(null);
    expiresAtRef.current = 0;
    sessionStorage.removeItem(ACCESS_TOKEN_KEY);
    localStorage.removeItem(REFRESH_TOKEN_KEY);
  }, []);

  const performRefresh = async (): Promise<string | null> => {
    const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY);

    if (!refreshToken) {
      logout();
      return null;
    }

    try {
      const result = await axios.post<GenerateTokenResponse>(
        import.meta.env.VITE_IDENTITY_URL,
        new URLSearchParams({
          grant_type: 'refresh_token',
          refresh_token: refreshToken,
          client_id: import.meta.env.VITE_IDENTITY_CLIENT_ID,
        }),
        { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
      );

      const newAccessToken = result.data.access_token;
      const newRefreshToken = result.data.refresh_token;

      localStorage.setItem(REFRESH_TOKEN_KEY, newRefreshToken);
      sessionStorage.setItem(ACCESS_TOKEN_KEY, newAccessToken);

      expiresAtRef.current = Date.now() + result.data.expires_in * 1000;
      setAccessToken(newAccessToken);
      extractUserFromToken(newAccessToken);

      return newAccessToken;
    } catch (error) {
      console.error('Refresh failed', error);
      logout();
      return null;
    }
  };

  const getOrRefreshAccessToken = async () => {
    // If we have a valid token in memory, return it
    if (accessToken && Date.now() < expiresAtRef.current - 60000) {
      return accessToken;
    }

    // If a refresh is already happening, return that existing promise
    if (refreshPromiseRef.current) {
      return refreshPromiseRef.current;
    }

    // Otherwise, start a new refresh
    refreshPromiseRef.current = performRefresh().finally(() => {
      refreshPromiseRef.current = null;
    });

    return refreshPromiseRef.current;
  };

  const login = (tokens: {
    access_token: string;
    refresh_token: string;
    expires_in: number;
  }) => {
    setAccessToken(tokens.access_token);
    extractUserFromToken(tokens.access_token);

    expiresAtRef.current = Date.now() + tokens.expires_in * 1000;

    localStorage.setItem(REFRESH_TOKEN_KEY, tokens.refresh_token);
    sessionStorage.setItem(ACCESS_TOKEN_KEY, tokens.access_token);
  };

  // INITIALIZATION
  useEffect(() => {
    const initAuth = async () => {
      const persistedRefreshToken = localStorage.getItem(REFRESH_TOKEN_KEY);
      const persistedAccessToken = sessionStorage.getItem(ACCESS_TOKEN_KEY);

      if (persistedRefreshToken) {
        if (persistedAccessToken) {
          setAccessToken(persistedAccessToken);
          extractUserFromToken(persistedAccessToken);
        }
        await getOrRefreshAccessToken();
      }
      setAuthFinished(true);
    };

    initAuth();
  }, [extractUserFromToken]);

  // INTERVAL CHECK
  useEffect(() => {
    if (!accessToken) return;

    const intervalId = setInterval(() => {
      getOrRefreshAccessToken();
    }, 30_000);

    return () => clearInterval(intervalId);
  }, [accessToken]);

  return (
    <AuthContext.Provider
      value={{
        accessToken,
        getToken: () => accessToken,
        login,
        logout,
        authFinished,
        userInfo: userInfo as UserInfo,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};
