import { useGetTutorialsStatus, useUpdateTutorialsStatus } from "@/hooks/AccountHooks";
import { AccountPermission } from "@/interface/AccountPermission";
import { AccountTutorialStatus } from "@/interface/AccountTutorialStatus";
import { GamePermission } from "@/interface/GamePermission";
import { RaidGroupPermission } from "@/interface/RaidGroupPermission";
import { RaidGroupRequest } from "@/interface/RaidGroupRequest";
import { api } from "@/util/AxiosUtil";
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useState } from "react";
import { Navigate, Outlet } from "react-router";


type AuthProviderProps = {
	children: React.ReactNode;
	jwtStorageKey?: string;
	refreshTokenStorageKey?: string;
}

type AuthProviderState = {
	jwt: string | null;
	setJwt: (token: string | null) => void;
	expiration: Date | null;
	setExpiration: (expiration: Date | null) => void;
	accountId: string | null;
	accountPermissions: AccountPermission[];
	raidGroupPermissions: RaidGroupPermission[];
	gamePermissions: GamePermission[];
	raidGroupRequests: RaidGroupRequest[];
	tutorialsStatus: AccountTutorialStatus;
	setTutorialsStatus: (tutorialsStatus: AccountTutorialStatus) => void;
}

const initialState: AuthProviderState = {
	jwt: null,
	setJwt: () => null,
	expiration: null,
	setExpiration: () => null,
	accountId: null,
	accountPermissions: [],
	raidGroupPermissions: [],
	gamePermissions: [],
	raidGroupRequests: [],
	tutorialsStatus: {} as AccountTutorialStatus,
	setTutorialsStatus: () => null
}

const AuthContext = createContext<AuthProviderState>(initialState);


export function AuthProvider({
	children
}: AuthProviderProps){
	const [ jwt, setJwt ] = useState<string | null>(null);
	const [ expiration, setExpiration ] = useState<Date | null>(null);
	const [ firstFetch, setFirstFetch ] = useState(true);
	const [ accountId, setAccountId ] = useState<string | null>(null);
	const [ accountPermissions, setAccountPermissions ] = useState<AccountPermission[]>([]);
	const [ raidGroupPermissions, setRaidGroupPermissions ] = useState<RaidGroupPermission[]>([]);
	const [ gamePermissions, setGamePermissions ] = useState<GamePermission[]>([]);
	const [ raidGroupRequests, setRaidGroupRequests ] = useState<RaidGroupRequest[]>([]);
	const [ tutorialsStatus, setTutorialsStatus ] = useState<AccountTutorialStatus>({} as AccountTutorialStatus);


	const tutorialsStatusQuery = useGetTutorialsStatus(accountId);
	const { mutate: tutorialsStatusMutation } = useUpdateTutorialsStatus();


	const fetchToken = useCallback(async () => {
		//console.log("Fetching token");
		try{
			const response = await api.get("/auth/refresh");
			//If the token is retrieved
			if((response.status === 200) && (!response.data.errors)){
				setJwt(response.data.token);
				const decodedToken = JSON.parse(atob(response.data.token.split(".")[1]));
				//console.log("decodedToken = ");
				//console.log(decodedToken);
				setExpiration(new Date(decodedToken.exp * 1000));
				setFirstFetch(false);
				setAccountId(decodedToken.accountId);
				setAccountPermissions(JSON.parse(decodedToken.accountPermissions));
				setRaidGroupPermissions(JSON.parse(decodedToken.raidGroupPermissions));
				setGamePermissions(JSON.parse(decodedToken.gamePermissions));
				setRaidGroupRequests(JSON.parse(decodedToken.raidGroupRequests));
				return response.data.token;
			}
			//If the token cannot be retrieved
			else{
				setJwt(null);
				setExpiration(null);
				setFirstFetch(false);
			}
		}
		//If the token cannot be retrieved
		catch{
			setJwt(null);
			setExpiration(null);
			setFirstFetch(false);
		}
	}, [ setJwt, setExpiration, setFirstFetch ]);


	//Add the token to all queries
	useLayoutEffect(() => {
		const authInterceptor = api.interceptors.request.use(async (config) => {
			if(config.url?.endsWith("/auth/refresh")){
				return config;
			}
			let currentJwt = jwt;
			if((expiration) && (expiration < new Date()) && (!config.url?.endsWith("/auth/refresh"))){
				currentJwt = await fetchToken();
				config.headers.Authorization = jwt ? `Bearer ${currentJwt}` : config.headers.Authorization;
			}
			config.headers.Authorization = jwt ? `Bearer ${currentJwt}` : config.headers.Authorization;

			return config;
		});

		return () => { api.interceptors.request.eject(authInterceptor); };
	}, [ jwt, expiration, fetchToken ]);

	//Try to get the token on page load
	useEffect(() => {
		fetchToken();
	}, [ fetchToken ]);

	//Update the tutorial status when fetched
	useEffect(() => {
		if(tutorialsStatusQuery.status === "success"){
			setTutorialsStatus(tutorialsStatusQuery.data);
		}
	}, [ tutorialsStatusQuery.status, tutorialsStatusQuery.data ]);

	const updateTutorialsStatus = useCallback((newTutorialsStatus: AccountTutorialStatus) => {
		setTutorialsStatus(newTutorialsStatus);
		tutorialsStatusMutation(newTutorialsStatus);
	}, [ setTutorialsStatus, tutorialsStatusMutation ]);


	const currentTokens = useMemo(() => ({
		jwt, setJwt,
		expiration, setExpiration,
		accountId,
		accountPermissions,
		raidGroupPermissions,
		gamePermissions,
		raidGroupRequests,
		tutorialsStatus, setTutorialsStatus: updateTutorialsStatus
	}), [ jwt, expiration, accountId, accountPermissions, raidGroupPermissions, gamePermissions, raidGroupRequests, tutorialsStatus, updateTutorialsStatus ]);


	//TODO: Return a spinner while the first token is being fetched
	if(firstFetch){
		return (
			<main
				className="text-4xl"
			>
				Loading...
			</main>
		);
	}


	return (
		<AuthContext.Provider value={currentTokens}>
			{children}
		</AuthContext.Provider>
	);
}

export function ProtectedRoute(){
	const { jwt } = useAuth();

	if(!jwt){
		return <Navigate to="/login"/>;
	}

	return <Outlet/>;
}


export const useAuth = () => {
	const context = useContext(AuthContext);

	if(context === undefined){
		throw new Error("useAuth must be used within an AuthProvider");
	}

	return context;
}
