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 com.bio.bio_backend.domain.base.member.dto.MemberDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

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(Long oid, String username, String name, long expirationTime) {
        return Jwts.builder()
                .subject(username)
                .claim("oid", oid)
                .claim("name", name)
                .issuedAt(new Date(System.currentTimeMillis()))
                .expiration(new Date(System.currentTimeMillis() + expirationTime))
                .signWith(getSigningKey())
                .compact();
    }

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

    // Access Token 생성
    public String createAccessToken(Long oid, String username, String name) {
        long expirationTime = Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access")));
        return generateToken(oid, username, name, expirationTime);
    }


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

    // Token 검증
    public Boolean validateAccessToken(String token) {
        try {
            return isValidTokenExpired(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;
        }
    }

    // IP 정보 추출
    public String extractClientIp(String token) {
        Claims claims = extractAllClaims(token);
        return claims.get("ip", String.class);
    }
    
    // Refresh Token 유효성 검증 (토큰 일치, 만료 여부)
    public Boolean isValidRefreshToken(String token) {
        try {
            String savedToken = memberService.getRefreshToken(extractUsername(token));
            return savedToken.equals(token) && isValidTokenExpired(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 isValidTokenExpired(String token) {
        Date expiration = extractAllClaims(token).getExpiration();
        return !expiration.before(new Date());
    }

    public String extractUsername(String token) {
        return extractAllClaims(token).getSubject();
    }

    public Claims extractAllClaims(String token) {
        return Jwts.parser()
                .verifyWith(getSigningKey())
                .build()
                .parseSignedClaims(token).getPayload();
    }

    // Access Token을 쿠키에서 추출
    public String extractAccessJwtFromCookie(HttpServletRequest request) {
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
                if ("AccessToken".equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    // Refresh Token을 쿠키에서 추출
    public String extractRefreshJwtFromCookie(HttpServletRequest request) {
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
                if ("RefreshToken".equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }
    
    // Refresh Token 쿠키 설정
    public void setRefreshTokenCookie(HttpServletResponse response, String refreshToken) {
        setCookie(response, "RefreshToken", refreshToken, 
                Integer.parseInt(env.getProperty("token.expiration_time_refresh")) / 1000);
    }

    // Access Token 쿠키 설정
    public void setAccessTokenCookie(HttpServletResponse response, String accessToken) {
        setCookie(response, "AccessToken", accessToken, 
                Integer.parseInt(env.getProperty("token.expiration_time_access")) / 1000);
    }

    // Access Token 쿠키 삭제
    public void deleteAccessTokenCookie(HttpServletResponse response) {
        setCookie(response, "AccessToken", "", 0);
    }

    // Refresh Token 쿠키 삭제
    public void deleteRefreshTokenCookie(HttpServletResponse response) {
        setCookie(response, "RefreshToken", "", 0);
    }

    // 모든 토큰 쿠키 삭제
    public void deleteAllTokenCookies(HttpServletResponse response) {
        deleteAccessTokenCookie(response);
        deleteRefreshTokenCookie(response);
    }

    // 쿠키 설정 헬퍼 메서드
    private void setCookie(HttpServletResponse response, String name, String value, int maxAge) {
        Cookie cookie = new Cookie(name, value);
        cookie.setHttpOnly(true);
        cookie.setSecure(false);
        cookie.setPath("/");
        cookie.setMaxAge(maxAge);
        response.addCookie(cookie);
    }

    public UserDetails createUserDetailsFromToken(String accessToken) {
        try {
            Claims claims = extractAllClaims(accessToken);
            String username = claims.getSubject();
            Long oid = claims.get("oid", Long.class);
            String name = claims.get("name", String.class);
            
            // 토큰에서 직접 UserDetails 생성 (DB 조회 없음)
            return MemberDto.builder()
                    .oid(oid)
                    .userId(username)
                    .name(name)
                    .build();
        } catch (Exception e) {
            log.debug("토큰에서 UserDetails 생성 실패: {}", e.getMessage());
            return null;
        }
    }
}
