package kz.konturai.parser.service;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.crypto.SecretKey;

@Service
public class JwtService {

    private static final Logger logger = LoggerFactory.getLogger(JwtService.class);
    private final SecretKey signingKey;

    public JwtService(
            @Value("${security.jwt.secret-base64:}") String base64Secret) {
        if (StringUtils.hasText(base64Secret)) {
            this.signingKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Secret));
        } else {
            // Если секрет не настроен, создаем временный ключ (для разработки)
            // В продакшене это должно быть обязательно настроено
            logger.warn("JWT secret not configured, using default key. This should be configured in production!");
            String defaultSecret = "dGVzdC1zZWNyZXQta2V5LWZvci1kZXZlbG9wbWVudC1vbmx5LWRvLW5vdC11c2UtaW4tcHJvZHVjdGlvbg==";
            this.signingKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(defaultSecret));
        }
    }

    /**
     * Парсит и валидирует JWT токен
     * 
     * @param token JWT токен
     * @return Claims из токена
     * @throws JwtException если токен невалиден или истёк
     */
    public Claims parseAndValidate(String token) throws JwtException {
        return Jwts.parser()
                .verifyWith(signingKey)
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }

    /**
     * Извлекает JWT токен из заголовка Authorization
     * 
     * @param authHeader значение заголовка Authorization (например, "Bearer
     *                   <token>")
     * @return JWT токен или null если не найден
     */
    public String extractTokenFromHeader(String authHeader) {
        if (!StringUtils.hasText(authHeader)) {
            return null;
        }

        if (authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }

        return authHeader;
    }

    /**
     * Извлекает userId (uid) из JWT токена
     * Согласно документации, userId находится в поле "uid" как Long
     * 
     * Структура JWT токена:
     * - sub: Email пользователя
     * - uid: ID пользователя (Long)
     * - roles: Роли пользователя (String, разделённые запятыми)
     * 
     * @param token JWT токен
     * @return userId как String или null если не найден
     */
    public String extractUserIdFromToken(String token) {
        if (!StringUtils.hasText(token)) {
            return null;
        }

        try {
            Claims claims = parseAndValidate(token);

            // Основной способ: извлекаем uid (Long) из claims согласно документации
            Long uid = claims.get("uid", Long.class);
            if (uid != null) {
                return String.valueOf(uid);
            }

            // Fallback: пробуем другие поля, если uid не найден (для совместимости)
            if (claims.get("userId") != null) {
                logger.debug("Using 'userId' field as fallback");
                return String.valueOf(claims.get("userId"));
            }
            if (claims.get("id") != null) {
                logger.debug("Using 'id' field as fallback");
                return String.valueOf(claims.get("id"));
            }
            if (claims.get("user_id") != null) {
                logger.debug("Using 'user_id' field as fallback");
                return String.valueOf(claims.get("user_id"));
            }

            // Если ничего не найдено, пробуем subject (email) как последний вариант
            // (не рекомендуется, но может быть полезно для обратной совместимости)
            String subject = claims.getSubject();
            if (subject != null && !subject.isEmpty()) {
                logger.warn("Using subject (email) as userId fallback. Token should contain 'uid' field: {}", subject);
                return subject;
            }

            logger.warn("No userId field (uid) found in JWT token");
            return null;

        } catch (io.jsonwebtoken.ExpiredJwtException e) {
            logger.error("JWT token has expired: {}", e.getMessage());
            return null;
        } catch (io.jsonwebtoken.MalformedJwtException e) {
            logger.error("Malformed JWT token: {}", e.getMessage());
            return null;
        } catch (io.jsonwebtoken.security.SignatureException e) {
            logger.error("Invalid JWT signature: {}", e.getMessage());
            return null;
        } catch (JwtException e) {
            logger.error("Error parsing/validating JWT token: {}", e.getMessage());
            return null;
        } catch (Exception e) {
            logger.error("Unexpected error parsing JWT token: {}", e.getMessage(), e);
            return null;
        }
    }

    /**
     * Извлекает userId из заголовка Authorization
     * 
     * @param authHeader значение заголовка Authorization
     * @return userId или null если не найден
     */
    public String extractUserIdFromHeader(String authHeader) {
        String token = extractTokenFromHeader(authHeader);
        if (token == null) {
            return null;
        }
        return extractUserIdFromToken(token);
    }

    /**
     * Извлекает email (subject) из JWT токена
     * 
     * @param token JWT токен
     * @return email или null если не найден
     */
    public String extractEmailFromToken(String token) {
        if (!StringUtils.hasText(token)) {
            return null;
        }

        try {
            Claims claims = parseAndValidate(token);
            return claims.getSubject();
        } catch (JwtException e) {
            logger.error("Error parsing/validating JWT token: {}", e.getMessage());
            return null;
        }
    }

    /**
     * Извлекает роли из JWT токена
     * 
     * @param token JWT токен
     * @return список ролей или пустой список
     */
    public java.util.List<String> extractRolesFromToken(String token) {
        if (!StringUtils.hasText(token)) {
            return java.util.List.of();
        }

        try {
            Claims claims = parseAndValidate(token);
            String rolesString = claims.get("roles", String.class);

            if (rolesString == null || rolesString.isBlank()) {
                return java.util.List.of();
            }

            return java.util.Arrays.stream(rolesString.split(","))
                    .map(String::trim)
                    .filter(s -> !s.isEmpty())
                    .collect(java.util.stream.Collectors.toList());
        } catch (JwtException e) {
            logger.error("Error parsing/validating JWT token: {}", e.getMessage());
            return java.util.List.of();
        }
    }
}
