import {useEffect, useState} from 'react';
import {useRecoilValue, useSetRecoilState} from 'recoil';
import {jwtToken, userData} from '../recoilState/usersState';
import {refreshToken} from '../api/authenticationAPI';

const parseJWTPayload = (jwt) => {
    // A JWT consists of 3 parts, the header, payload, and signature.
    // We don't need the header or signature, they can be used to verify
    // the JWT when the server receives a JWT.
    const jwtPayload = jwt.split('.')[1];

    // The JWT payload is a base64 encoded string.
    // The atob method can be used to decode a base64 string.
    // The decoded JWT contains 3 attributes, the only important one for our application, is the exp attribute,
    // this attribute contains the time after which the JWT is no longer valid.
    // A list of all possible public attributes (called claims) is available at
    // https://www.iana.org/assignments/jwt/jwt.xhtml.
    // Anyone can add specific claims for their application.
    return JSON.parse(window.atob(jwtPayload));
}

const useJWT = () => {
    const [triedRefresh, setTriedRefresh] = useState(false);
    const jwt = useRecoilValue(jwtToken);
    const setUser = useSetRecoilState(userData);

    useEffect(() => {
        if (!jwt) {
            return;
        }

        // Get the expiration date and set a timeout to refresh it.
        // The exp attribute is given in seconds since epoch, JavaScript timeouts
        // are defined in milliseconds since epoch. So the exp attribute must be multiplied by
        // 1000 to go from seconds to milliseconds.
        const expirationDate = new Date(parseJWTPayload(jwt).exp * 1000);

        // Calculate the number of milliseconds before the JWT expires.
        const timeDelta = expirationDate - Date.now();

        // Set the timeout for 1 minute before the token expires.
        // It can then be refreshed so that the user can keep using
        // the application without interruptions.
        const timeout = timeDelta - (60 * 1000);
        const timeoutId = window.setTimeout(async () => {
            const user = await refreshToken();
            setUser(user);
        }, timeout);

        return () => {
            // Clear any refresh timeouts for the current JWT.
            if (timeoutId) {
                window.clearTimeout(timeoutId);
            }
        }
    }, [jwt, setUser])

    useEffect(() => {
        if (triedRefresh) return;
        if (jwt) setTriedRefresh(true);

        const refresh = async () => {
            const user = await refreshToken();
            setUser(user);
            setTriedRefresh(true);
        }
        refresh();
    }, [triedRefresh, jwt, setUser])
}

export default useJWT;
