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

import lombok.*;
import ru.kirillius.java.utils.events.EventListener;
import ru.kirillius.json.JSONArrayProperty;
import ru.kirillius.json.JSONProperty;
import ru.kirillius.json.JSONSerializable;
import ru.kirillius.pf.sdn.External.API.TDNSAPI;
import ru.kirillius.pf.sdn.core.AbstractComponent;
import ru.kirillius.pf.sdn.core.Context;
import ru.kirillius.pf.sdn.core.Networking.NetworkResourceBundle;
import ru.kirillius.utils.logging.SystemLogger;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Component that synchronises Technitium DNS forwarder zones with managed domain lists.
 */
public final class TDNS extends AbstractComponent<TDNS.TechnitiumConfig> {

    private final static String CTX = TDNS.class.getSimpleName();
    private final EventListener<NetworkResourceBundle> subscription;

    /**
     * Subscribes to network updates to keep Technitium DNS forwarder zones in sync.
     */
    public TDNS(Context context) {
        super(context);
        subscription = context.getEventsHandler().getNetworkManagerUpdateEvent().add(bundle -> updateSubnets(bundle.getDomains()));
    }

    /**
     * Updates Technitium DNS servers to match the provided domain list.
     */
    private void updateSubnets(List<String> domains) {
        for (var instance : config.instances) {
            SystemLogger.message("Updating zones on DNS server " + instance.server, CTX);
            try (var api = new TDNSAPI(instance.server, instance.token)) {
                var existingForwardZones = api.getZones().stream()
                        .filter(zoneResponse -> zoneResponse.getType() == TDNSAPI.ZoneType.Forwarder)
                        .map(TDNSAPI.ZoneResponse::getName)
                        .collect(Collectors.toCollection(ArrayList::new));
                existingForwardZones.forEach(zoneName -> {
                    if (!domains.contains(zoneName)) {
                        //delete zone
                        SystemLogger.message("Deleting zone " + zoneName, CTX);
                        api.deleteZone(zoneName);
                    }
                });

                domains.forEach(zoneName -> {
                    if (!existingForwardZones.contains(zoneName)) {
                        SystemLogger.message("Creating FWD zone " + zoneName, CTX);
                        api.createForwarderZone(zoneName, instance.forwarder);
                    }
                });
                SystemLogger.message("Instance is updated", CTX);
            } catch (Exception e) {
                SystemLogger.error("Error happened on DNS server " + instance.server + " sync", CTX, e);
            }
        }

        SystemLogger.message("Update is completed", CTX);

    }


    /**
     * Removes the event subscription when the component is closed.
     */
    @Override
    public void close() throws IOException {
        context.getEventsHandler().getNetworkManagerUpdateEvent().remove(subscription);

    }

    /**
     * Configuration of Technitium DNS instances managed by the component.
     */
    @JSONSerializable
    public static class TechnitiumConfig {

        @Getter
        @Setter
        @JSONArrayProperty(type = Entry.class)
        private List<Entry> instances = new ArrayList<>();

        /**
         * Describes a single DNS server endpoint and its access credentials.
         */
        @Builder
        @AllArgsConstructor
        @NoArgsConstructor
        @JSONSerializable
        public static class Entry {
            @Getter
            @Setter
            @JSONProperty
            private String forwarder = "127.0.0.1";

            @Getter
            @Setter
            @JSONProperty
            private String server = "http://127.0.0.1:5380";

            @Getter
            @Setter
            @JSONProperty
            private String token = "notoken";
        }
    }
}
