package com.wishurgift.utils;

import com.wishurgift.service.dto.UserDTO;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;

import java.security.Principal;
import java.util.Calendar;
import java.util.Date;

@Slf4j
public class JwtUtils {

    /**
     * Genere un token pour un utilisateur
     *
     * @param user L'utilisateur pour generer le token
     * @return Le token
     */
    public static String generate(UserDTO user) {
        // Creation de la date d'expiration
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.DAY_OF_MONTH, 1);

        JwtBuilder builder = Jwts.builder();
        // Parametrage du payload
        Date date = new Date();
        String tag = user.getTag().split("#")[0];
        builder.setId(tag + date.getTime())
                .claim("id", user.getId())
                .claim("name", user.getName())
                .claim("email", user.getEmail())
                .claim("tag", user.getTag())
                .claim("public", user.isPublic())
                .setExpiration(calendar.getTime());
        // Ajout clef pour signature
        builder.signWith(SignatureAlgorithm.HS256, Constant.JWT_KEY);
        // creation token
        return builder.compact();
    }

    /**
     * Verifie qu'un token est valide
     *
     * @param token Le token a valider
     * @return le token est valide ou non
     */
    public static boolean verify(String token) {
        try {
            Jwts.parser().setSigningKey(Constant.JWT_KEY).parse(token);
            return true;
        } catch (JwtException e) {
            log.info("Invalid token: " + e.getMessage());
            return false;
        }
    }

    /**
     * Création d'un nouveau token pour un utilisateur déja connecté
     *
     * @param principal Le principal du security context
     * @return Le nouveau token
     */
    public static String refresh(Principal principal) {
        if (principal == null) {
            return null;
        }
        return refresh(principal.getName());
    }

    /**
     * Création d'un nouveau token pour un utilisateur déja connecté
     *
     * @param token Le token à mettre à jour
     * @return Le nouveau token
     */
    public static String refresh(String token) {
        UserDTO user;
        if ((user = getUser(token)) == null) {
            return null;
        }
        return generate(user);
    }

    /**
     * Recupère les informations de l'utilisateur connecté
     *
     * @param principal Le principal du security context
     * @return L'utilisateur connecté
     */
    public static UserDTO getUser(Principal principal) {
        if (principal == null) {
            log.info("No user");
            return null;
        }
        return getUser(principal.getName(), false);
    }

    public static UserDTO getUser(String token) {
        return getUser(token, true);
    }

    /**
     * Récupère l'utilisateur dans le payload du JWT
     *
     * @param token Le token JWT
     * @param valid Si le token doit être valide (non expiré) pour récupèrer l'utilisateur
     * @return L'utilisateur ou null si le token doit être valide alors qu'il ne l'est pas
     */
    public static UserDTO getUser(String token, boolean valid) {
        // Récupération payload
        Claims payload = getPayload(token, valid);
        if (payload == null) {
            log.info("No user");
            return null;
        }
        // Mapping
        UserDTO userDTO = new UserDTO();
        userDTO.setId(payload.get("id", Integer.class));
        userDTO.setName(payload.get("name", String.class));
        userDTO.setEmail(payload.get("email", String.class));
        userDTO.setTag(payload.get("tag", String.class));
        userDTO.setPublic(payload.get("public", Boolean.class));
        log.info("User {}", userDTO.getTag());
        return userDTO;
    }

    /**
     * Récupère l'utilisateur dans le payload du JWT
     *
     * @param token Le token JWT
     * @param valid Si le token doit être valide (non expiré) pour récupèrer l'utilisateur
     * @return L'utilisateur ou null si le token doit être valide alors qu'il ne l'est pas
     */
    public static Claims getPayload(String token, boolean valid) {
        Claims result = null;
        if (StringUtils.isNotBlank(token)) {
            try {
                result = Jwts.parser().setSigningKey(Constant.JWT_KEY).parseClaimsJws(token).getBody();
            } catch (ExpiredJwtException e) {
                if (!valid) {
                    result = e.getClaims();
                }
            } catch (JwtException e) {
                log.info("Invalid token: " + e.getMessage());
            }
        }
        return result;
    }

    /**
     * Verifie que la chaine est bien une authentification par token
     *
     * @param authorizationHeader La chaine testée, elle doit être non null et préfixée oar "Bearer "
     * @return
     */
    public static boolean isTokenAuthentication(String authorizationHeader) {
        return authorizationHeader != null &&
                authorizationHeader.toLowerCase().startsWith(Constant.AUTHENTICATION_SCHEME.toLowerCase() + " ");
    }

}
