package ru.kirillius.pf.sdn.web.RPC;

import lombok.Builder;
import lombok.Getter;
import org.json.JSONArray;
import org.json.JSONObject;
import ru.kirillius.json.DefaultPropertySerializer;
import ru.kirillius.json.JSONArrayProperty;
import ru.kirillius.json.JSONSerializable;
import ru.kirillius.json.JSONUtility;
import ru.kirillius.json.rpc.Annotations.JRPCArgument;
import ru.kirillius.json.rpc.Annotations.JRPCMethod;
import ru.kirillius.pf.sdn.External.API.LocalFilesystemSubscription;
import ru.kirillius.pf.sdn.core.Context;
import ru.kirillius.pf.sdn.core.Networking.BGPInfoService;
import ru.kirillius.pf.sdn.core.Networking.IPv4Subnet;
import ru.kirillius.pf.sdn.core.Networking.NetworkResourceBundle;
import ru.kirillius.pf.sdn.core.Networking.NetworkingService;
import ru.kirillius.pf.sdn.core.Subscription.SubscriptionService;
import ru.kirillius.pf.sdn.core.Util.DomainUtil;
import ru.kirillius.pf.sdn.core.Util.IPv4Util;
import ru.kirillius.pf.sdn.core.Util.Wait;
import ru.kirillius.pf.sdn.web.ProtectedMethod;

import java.io.*;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

/**
 * JSON-RPC handler exposing operations on the network aggregation service.
 */
public class NetworkManager implements RPC {
    private final Context context;

    /**
     * Creates the handler bound to the application context.
     */
    public NetworkManager(Context context) {
        this.context = context;
    }

    /**
     * Indicates whether the networking service is currently recomputing resources.
     */
    @JRPCMethod
    @ProtectedMethod
    public boolean isUpdating() {
        return context.getServiceManager().getService(NetworkingService.class).isUpdatingNow();
    }

    /**
     * Triggers an update of networking resources.
     */
    @JRPCMethod
    @ProtectedMethod
    public void triggerUpdate(@JRPCArgument(name = "ignoreCache") boolean ignoreCache) {
        context.getServiceManager().getService(NetworkingService.class).triggerUpdate(ignoreCache);
    }

    /**
     * Returns the latest aggregated network resource bundle.
     */
    @JRPCMethod
    @ProtectedMethod
    public JSONObject getOutputResources() {
        return JSONUtility.serializeStructure(context.getServiceManager().getService(NetworkingService.class).getOutputResources());
    }



    @JRPCMethod
    @ProtectedMethod
    public JSONObject discoverBlockedDomainResources(@JRPCArgument(name = "domain") String domain, @JRPCArgument(name = "server") String nameserver) {

        var addresses = DomainUtil.lookup(domain, nameserver);

        var discoveredASN = new HashSet<Integer>();
        var discoveredSubnets = new HashSet<IPv4Subnet>();

        var infoService = context.getServiceManager().getService(BGPInfoService.class);

        addresses.forEach(address -> {
            if (discoveredSubnets.stream().anyMatch(subnet -> subnet.contains(address))) {
                return;
            }

            var future = infoService.getAddressInfo(address);
            try {
                Wait.until(() -> future.isDone() || future.isCancelled());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            if (!future.isDone()) {
                return;
            }
            try {
                var info = future.get();
                discoveredASN.addAll(info.getASN());
                discoveredSubnets.addAll(info.getPrefixes());
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        });

        var merged = IPv4Util.summarySubnets(discoveredSubnets, 100);

        return JSONUtility.serializeStructure(
                DomainQueryInfo.builder()
                        .addresses(addresses)
                        .ASN(discoveredASN.stream().toList())
                        .subnets(merged.getResult())
                        .build()
        );
    }

    @Builder
    @JSONSerializable
    public static class DomainQueryInfo {
        @Getter
        @JSONArrayProperty(type = String.class)
        private List<String> addresses;
        @JSONArrayProperty(type = IPv4Subnet.class, serializer = IPv4Subnet.Serializer.class)
        @Getter
        private List<IPv4Subnet> subnets;
        @Getter
        @JSONArrayProperty(type = Integer.class)
        private List<Integer> ASN;
    }
}
