package incheon.com.cmm.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * IP 접근 제어 유틸리티
 * - CIDR 표기법 지원 (예: 10.0.0.0/24)
 * - IPv6 루프백 (0:0:0:0:0:0:0:1) ↔ IPv4 루프백 (127.0.0.1) 동일 처리
 * - 콤마로 구분된 IP 목록 지원
 */
@Slf4j
public class IpAccessUtil {

    // IPv6 루프백 주소
    private static final String IPV6_LOOPBACK = "0:0:0:0:0:0:0:1";
    private static final String IPV6_LOOPBACK_SHORT = "::1";
    // IPv4 루프백 주소
    private static final String IPV4_LOOPBACK = "127.0.0.1";

    /**
     * 현재 IP가 허용 목록에 포함되는지 확인
     *
     * @param currentIp   현재 접속 IP
     * @param allowedList 허용 IP 목록 (콤마 구분, CIDR 지원)
     *                    예: "192.168.0.1, 10.0.0.0/24, 0:0:0:0:0:0:0:1"
     * @return true: 접근 허용, false: 접근 거부
     */
    public static boolean isAllowed(String currentIp, String allowedList) {
        // 허용 목록이 비어있으면 모든 IP 허용 (기본값)
        if (!StringUtils.hasText(allowedList)) {
            return true;
        }

        if (!StringUtils.hasText(currentIp)) {
            log.warn("현재 IP가 비어있습니다.");
            return false;
        }

        // 현재 IP 정규화
        String normalizedCurrentIp = normalizeIp(currentIp);

        // 허용 목록 파싱 (콤마 구분)
        List<String> allowedIps = parseIpList(allowedList);

        for (String allowedIp : allowedIps) {
            String trimmed = allowedIp.trim();
            if (!StringUtils.hasText(trimmed)) {
                continue;
            }

            // CIDR 표기법 체크
            if (trimmed.contains("/")) {
                if (isInCidrRange(normalizedCurrentIp, trimmed)) {
                    log.debug("IP 허용 (CIDR 매칭): {} in {}", currentIp, trimmed);
                    return true;
                }
            } else {
                // 단일 IP 비교
                String normalizedAllowed = normalizeIp(trimmed);
                if (normalizedCurrentIp.equals(normalizedAllowed)) {
                    log.debug("IP 허용 (정확히 일치): {} = {}", currentIp, trimmed);
                    return true;
                }
            }
        }

        log.debug("IP 거부: {} not in [{}]", currentIp, allowedList);
        return false;
    }

    /**
     * IP 주소 정규화
     * - IPv6 루프백 → IPv4 루프백으로 변환
     * - 공백 제거
     */
    private static String normalizeIp(String ip) {
        if (ip == null) {
            return "";
        }

        String trimmed = ip.trim();

        // IPv6 루프백 → IPv4 루프백
        if (IPV6_LOOPBACK.equals(trimmed) || IPV6_LOOPBACK_SHORT.equals(trimmed)) {
            return IPV4_LOOPBACK;
        }

        return trimmed;
    }

    /**
     * 콤마로 구분된 IP 목록 파싱
     */
    private static List<String> parseIpList(String ipList) {
        return Arrays.stream(ipList.split(","))
                .map(String::trim)
                .filter(StringUtils::hasText)
                .collect(Collectors.toList());
    }

    /**
     * IP가 CIDR 범위에 포함되는지 확인
     *
     * @param ip   확인할 IP (정규화됨)
     * @param cidr CIDR 표기법 (예: 10.0.0.0/24)
     * @return true: 범위 내, false: 범위 외
     */
    private static boolean isInCidrRange(String ip, String cidr) {
        try {
            String[] parts = cidr.split("/");
            if (parts.length != 2) {
                log.warn("잘못된 CIDR 형식: {}", cidr);
                return false;
            }

            String networkAddr = parts[0].trim();
            int prefixLength = Integer.parseInt(parts[1].trim());

            // IPv6 루프백 정규화
            networkAddr = normalizeIp(networkAddr);

            InetAddress network = InetAddress.getByName(networkAddr);
            InetAddress target = InetAddress.getByName(ip);

            // IPv4 vs IPv6 체크
            if (network.getAddress().length != target.getAddress().length) {
                return false;
            }

            byte[] networkBytes = network.getAddress();
            byte[] targetBytes = target.getAddress();

            int fullBytes = prefixLength / 8;
            int remainingBits = prefixLength % 8;

            // 전체 바이트 비교
            for (int i = 0; i < fullBytes; i++) {
                if (networkBytes[i] != targetBytes[i]) {
                    return false;
                }
            }

            // 남은 비트 비교
            if (remainingBits > 0 && fullBytes < networkBytes.length) {
                int mask = 0xFF << (8 - remainingBits);
                if ((networkBytes[fullBytes] & mask) != (targetBytes[fullBytes] & mask)) {
                    return false;
                }
            }

            return true;

        } catch (UnknownHostException e) {
            log.warn("IP 파싱 오류: ip={}, cidr={}, error={}", ip, cidr, e.getMessage());
            return false;
        } catch (NumberFormatException e) {
            log.warn("CIDR prefix 파싱 오류: cidr={}, error={}", cidr, e.getMessage());
            return false;
        }
    }

    /**
     * IP 주소가 루프백인지 확인
     */
    public static boolean isLoopback(String ip) {
        if (!StringUtils.hasText(ip)) {
            return false;
        }
        String normalized = normalizeIp(ip);
        return IPV4_LOOPBACK.equals(normalized);
    }
}
