package com.bio.bio_backend.global.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.bio.bio_backend.domain.base.member.service.MemberService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.Objects;

@Component
@RequiredArgsConstructor
@Slf4j
public class JwtUtils {

    private final MemberService memberService;
    private final Environment env;

    @Value("${token.secret_key}")
    private String SECRET_KEY;

    private SecretKey getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    // Token 생성
    public String generateToken(String username, String role, long expirationTime) {

        return Jwts.builder()
                .subject(username)
                .claim("role", role)
                .issuedAt(new Date(System.currentTimeMillis()))
                .expiration(new Date(System.currentTimeMillis() + expirationTime))
                .signWith(getSigningKey())
                .compact();
    }

    // Token 검증
    public Boolean validateAccessToken(String token) {
        try {
            return isTokenExpired(token);
        } catch (io.jsonwebtoken.ExpiredJwtException e) {
            log.debug("Access Token 만료: {}", e.getMessage());
            return false;
        } catch (Exception e) {
            log.debug("Access Token 검증 실패: {}", e.getMessage());
            return false;
        }
    }

    // Refresh Token 생성 시 IP 정보 포함
    public String createRefreshToken(String username, String role, String clientIp) {
        return generateToken(username, role, clientIp,
                Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_refresh"))));
    }

    // IP 정보를 포함한 토큰 생성
    public String generateToken(String username, String role, String clientIp, long expirationTime) {
        return Jwts.builder()
                .subject(username)
                .claim("role", role)
                .claim("ip", clientIp)  // IP 정보 추가
                .issuedAt(new Date(System.currentTimeMillis()))
                .expiration(new Date(System.currentTimeMillis() + expirationTime))
                .signWith(getSigningKey())
                .compact();
    }

    // IP 정보 추출
    public String extractClientIp(String token) {
        Claims claims = extractAllClaims(token);
        return claims.get("ip", String.class);
    }

    // Refresh Token 검증 시 IP도 함께 검증
    public Boolean validateRefreshToken(String token, String clientIp) {
        // 1. 토큰 유효성 검증
        if (!isValidRefreshToken(token)) {
            return false;
        }
        
        // 2. IP 주소 검증
        if (!isValidClientIp(token, clientIp)) {
            return false;
        }
        
        return true;
    }
    
    // Refresh Token 유효성 검증 (토큰 일치, 만료 여부)
    public Boolean isValidRefreshToken(String token) {
        try {
            String savedToken = memberService.getRefreshToken(extractUsername(token));
            return savedToken.equals(token) && !isTokenExpired(token);
        } catch (Exception e) {
            log.debug("Refresh Token 검증 실패: {}", e.getMessage());
            return false;
        }
    }
    
    // 클라이언트 IP 주소 검증
    public Boolean isValidClientIp(String token, String clientIp) {
        try {
            String tokenIp = extractClientIp(token);
            boolean isValid = Objects.equals(tokenIp, clientIp);
            
            if (!isValid) {
                log.debug("IP 주소 불일치 - 토큰 IP: {}, 클라이언트 IP: {}", tokenIp, clientIp);
            }
            
            return isValid;
        } catch (Exception e) {
            log.debug("IP 주소 검증 실패: {}", e.getMessage());
            return false;
        }
    }

    private boolean isTokenExpired(String token) {
        return !extractAllClaims(token).getExpiration().before(new Date());
    }

    public String extractUsername(String token) {
        return extractAllClaims(token).getSubject();
    }
    
    // Role 정보 추출
    public String extractRole(String token) {
        Claims claims = extractAllClaims(token);
        return claims.get("role", String.class);
    }

    public Claims extractAllClaims(String token) {

        return Jwts.parser()
                .verifyWith(getSigningKey())
                .build()
                .parseSignedClaims(token).getPayload();
    }

    public String extractAccessJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);    // "Bearer " 제거
        }
        return bearerToken;
    }

    public String extractRefreshJwtFromCookie(HttpServletRequest request) {
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
                if ("RefreshToken".equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    // Access Token 생성
    public String createAccessToken(String username, String role) {
        return generateToken(username, role,
                Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
    }
    
    // Refresh Token 생성
    public String createRefreshToken(String username, String role) {
        return generateToken(username, role,
                Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_refresh"))));
    }
    
    // Refresh Token 갱신 (Access Token 갱신 시 함께)
    public String refreshTokens(String username, String role) {
        // 새로운 Refresh Token 생성 및 DB 저장
        String newRefreshToken = createRefreshToken(username, role);
        
        log.info("Refresh Token 갱신 완료: {}", username);
        return newRefreshToken;
    }
    
    // Refresh Token 쿠키 설정
    public void setRefreshTokenCookie(HttpServletResponse response, String refreshToken) {
        Cookie refreshTokenCookie = new Cookie("RefreshToken", refreshToken);
        refreshTokenCookie.setHttpOnly(true);
        refreshTokenCookie.setSecure(false);
        refreshTokenCookie.setPath("/");
        refreshTokenCookie.setMaxAge(Integer.parseInt(env.getProperty("token.expiration_time_refresh")));
        
        response.addCookie(refreshTokenCookie);
    }
}
