package org.example.Utils;

import java.net.InetAddress;
import java.net.URI;
import java.util.Locale;

public final class RemoteDownloadGuard {
    public static final int MAX_REDIRECTS = 3;
    public static final long DEFAULT_MAX_BYTES = 100L * 1024L * 1024L;

    private RemoteDownloadGuard() {
    }

    public static boolean isAllowedHttpUrl(String rawUrl) {
        try {
            requireAllowedHttpUri(rawUrl);
            return true;
        } catch (RuntimeException ex) {
            return false;
        }
    }

    public static URI requireAllowedHttpUri(String rawUrl) {
        if (rawUrl == null || rawUrl.trim().isEmpty()) {
            throw new IllegalArgumentException("Remote download URL is empty");
        }

        URI uri = URI.create(rawUrl.trim());
        validateUri(uri);
        return uri;
    }

    public static URI requireAllowedRedirect(URI currentUri, String location) {
        if (location == null || location.trim().isEmpty()) {
            throw new IllegalArgumentException("Redirect location is empty");
        }

        URI redirected = currentUri.resolve(location.trim());
        validateUri(redirected);
        return redirected;
    }

    public static long maxDownloadBytes() {
        String configured = WebConfigUtil.get("app.remote-download.max-bytes", String.valueOf(DEFAULT_MAX_BYTES));
        try {
            return Math.max(1L, Long.parseLong(configured));
        } catch (NumberFormatException ex) {
            return DEFAULT_MAX_BYTES;
        }
    }

    private static void validateUri(URI uri) {
        String scheme = uri.getScheme();
        if (scheme == null || (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme))) {
            throw new IllegalArgumentException("Only http/https remote downloads are allowed");
        }
        if (uri.getUserInfo() != null) {
            throw new IllegalArgumentException("Remote download URL must not contain user info");
        }

        String host = uri.getHost();
        if (host == null || host.isBlank()) {
            throw new IllegalArgumentException("Remote download URL host is empty");
        }

        String normalizedHost = host.toLowerCase(Locale.ROOT);
        if ("localhost".equals(normalizedHost)
                || normalizedHost.endsWith(".localhost")
                || "metadata.google.internal".equals(normalizedHost)) {
            throw new IllegalArgumentException("Remote download host is not allowed");
        }

        if (allowPrivateAddresses()) {
            return;
        }

        try {
            InetAddress[] addresses = InetAddress.getAllByName(host);
            for (InetAddress address : addresses) {
                if (isBlockedAddress(address)) {
                    throw new IllegalArgumentException("Remote download host resolves to a private address");
                }
            }
        } catch (IllegalArgumentException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new IllegalArgumentException("Remote download host cannot be resolved", ex);
        }
    }

    private static boolean allowPrivateAddresses() {
        return Boolean.parseBoolean(WebConfigUtil.get("app.remote-download.allow-private-address", "false"));
    }

    private static boolean isBlockedAddress(InetAddress address) {
        if (address.isAnyLocalAddress()
                || address.isLoopbackAddress()
                || address.isLinkLocalAddress()
                || address.isSiteLocalAddress()
                || address.isMulticastAddress()) {
            return true;
        }

        byte[] raw = address.getAddress();
        if (raw.length == 4) {
            int first = raw[0] & 0xff;
            int second = raw[1] & 0xff;
            int third = raw[2] & 0xff;
            int fourth = raw[3] & 0xff;
            return first == 0
                    || first == 10
                    || (first == 172 && second >= 16 && second <= 31)
                    || (first == 192 && second == 168)
                    || (first == 169 && second == 254)
                    || (first == 100 && second >= 64 && second <= 127)
                    || (first == 198 && (second == 18 || second == 19))
                    || (first == 169 && second == 254 && third == 169 && fourth == 254);
        }

        return raw.length == 16 && ((raw[0] & 0xfe) == 0xfc);
    }
}
