package ru.kirillius.pf.sdn.External.API;

import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;
import org.json.JSONTokener;
import ru.kirillius.pf.sdn.core.Networking.ASInfoProvider;
import ru.kirillius.pf.sdn.core.Networking.IPv4Subnet;
import ru.kirillius.utils.logging.SystemLogger;

import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.regex.Pattern;

/**
 * Retrieves ASN prefix information from Hurricane Electric's public API.
 */
public class HEInfoProvider implements ASInfoProvider {



    /**
     * Fetches IPv4 prefixes announced by the specified autonomous system.
     */
    @Override
    @SneakyThrows
    public List<IPv4Subnet> getPrefixes(int as) {
        try (var client = HttpClient.newHttpClient()) {
            var request = HttpRequest.newBuilder()
                    .uri(URI.create("https://bgp.he.net/super-lg/report/api/v1/prefixes/originated/" + as))
                    .header("Accept", "application/json").timeout(Duration.ofMinutes(2)).GET().build();
            var response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
            if (response.statusCode() == 200) {
                try (var inputStream = response.body()) {
                    return getIPv4Subnets(inputStream);
                }
            } else {
                throw new RuntimeException("Unable to get info about AS" + as);
            }
        }
    }

    @Override
    public IPQueryInfo queryAddress(String address) {
        var request = HttpRequest.newBuilder()
                .uri(URI.create("https://bgp.he.net/ip/" + address))
                .header("Accept", "text/html")
                .GET()
                .build();

        try (var client = HttpClient.newHttpClient()) {
            var response = client.send(request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() != 200) {
                SystemLogger.error("Unable to get info about IP " + address + ", status " + response.statusCode(), CTX);
                return emptyIPInfo();
            }

            return parseIPInfo(response.body());
        } catch (Exception e) {
            SystemLogger.error("Failed to query info about IP " + address + ": " + e.getMessage(), CTX);
            return emptyIPInfo();
        }
    }

    /**
     * Parses IPv4 prefix entries from the Hurricane Electric API response.
     */
    private static @NotNull ArrayList<IPv4Subnet> getIPv4Subnets(InputStream inputStream) {
        var json = new JSONObject(new JSONTokener(inputStream));
        var array = json.getJSONArray("prefixes");
        var list = new ArrayList<IPv4Subnet>();
        array.forEach(obj -> {
            var jo = (JSONObject) obj;

            var prefix = jo.getString("Prefix");
            if (prefix.indexOf('.') == -1) {
                return;
            }
            list.add(new IPv4Subnet(prefix));

        });
        return list;
    }

    private final static String CTX = HEInfoProvider.class.getSimpleName();

    private static IPQueryInfo parseIPInfo(String html) {
        var asnSet = new LinkedHashSet<Integer>();
        var prefixes = new ArrayList<IPv4Subnet>();

        var rowMatcher = ROW_PATTERN.matcher(html);
        while (rowMatcher.find()) {
            var row = rowMatcher.group(1);

            var asMatcher = AS_PATTERN.matcher(row);
            if (!asMatcher.find()) {
                continue;
            }

            var prefixMatcher = PREFIX_PATTERN.matcher(row);
            if (!prefixMatcher.find()) {
                continue;
            }

            try {
                var asn = Integer.parseInt(asMatcher.group(1));
                asnSet.add(asn);

                var prefix = prefixMatcher.group(1).replace("\u00A0", "").trim();
                prefixes.add(new IPv4Subnet(prefix));
            } catch (Exception e) {
                SystemLogger.error("Unable to parse row: " + row, CTX);
            }
        }

        return IPQueryInfo.builder()
                .ASN(new ArrayList<>(asnSet))
                .prefixes(prefixes)
                .build();
    }

    private static IPQueryInfo emptyIPInfo() {
        return IPQueryInfo.builder()
                .ASN(Collections.emptyList())
                .prefixes(Collections.emptyList())
                .build();
    }

    private static final Pattern ROW_PATTERN = Pattern.compile("<tr>(.*?)</tr>", Pattern.DOTALL);
    private static final Pattern AS_PATTERN = Pattern.compile("<a\\s+href=\"/AS(\\d+)\">\\s*AS\\d+\\s*</a>");
    private static final Pattern PREFIX_PATTERN = Pattern.compile("<a\\s+href=\"/net/[^\"]+\">\\s*([0-9.]+/\\d+)\\s*</a>");
}
