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

import lombok.Getter;
import org.json.JSONObject;
import ru.kirillius.json.JSONProperty;
import ru.kirillius.json.JSONSerializable;
import ru.kirillius.json.JSONUtility;

import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.*;

/**
 * Thin client for the Technitium DNS HTTP API used to manage forwarder zones.
 */
public class TDNSAPI implements Closeable {
    private final String server;
    private final String authToken;
    private final HttpClient httpClient;

    /**
     * Creates an API client targeting the specified Technitium server.
     */
    public TDNSAPI(String server, String authToken) {
        this.server = server;
        httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build();
        this.authToken = authToken;
    }

    /**
     * Performs a GET request to the Technitium API with authentication parameters.
     */
    private JSONObject getRequest(String api, Map<String, String> additionalParams) {
        var params = new HashMap<>(additionalParams);
        var joiner = new StringJoiner("&");
        params.put("token", authToken);
        params.forEach((key, value) -> joiner.add(key + "=" + URLEncoder.encode(value, StandardCharsets.UTF_8)));
        var url = server + api;
        if (!params.isEmpty()) {
            url += "?" + joiner;
        }
        var request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .header("Accept", "application/json").GET().build();

        try {
            var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() == 200) {
                var result = new JSONObject(response.body());
                if (result.getString("status").equals("ok")) {
                    return result.getJSONObject("response");
                } else {
                    throw new RuntimeException("API error: " + result.getString("errorMessage"));
                }
            } else {
                throw new RuntimeException("API HTTP error: " + response.statusCode());
            }
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException("API internal error", e);
        }
    }

    /**
     * Supported zone types returned by the API.
     */
    public enum ZoneType {
        Primary, Secondary, Stub, Forwarder, SecondaryForwarder, Catalog, SecondaryCatalog
    }

    /**
     * Transfer-object describing DNS zone metadata received from Technitium.
     */
    @JSONSerializable
    public static class ZoneResponse {
        @JSONProperty
        @Getter
        private String name;
        @JSONProperty
        @Getter
        private ZoneType type;
        @JSONProperty(required = false)
        @Getter
        private String dnssecStatus;
        @JSONProperty
        @Getter
        private long soaSerial;
        @JSONProperty(required = false)
        @Getter
        private Instant expiry;
        @JSONProperty(required = false)
        @Getter
        private boolean isExpired;
        @JSONProperty(required = false)
        @Getter
        private boolean syncFailed;
        @JSONProperty
        @Getter
        private boolean disabled;
        @JSONProperty
        @Getter
        private Instant lastModified;
    }

    /**
     * Lists zones from the Technitium server.
     */
    public List<ZoneResponse> getZones() {
        var request = getRequest("/api/zones/list", Collections.emptyMap());
        return JSONUtility.deserializeCollection(request.getJSONArray("zones"), ZoneResponse.class, null).toList();
    }

    /**
     * Creates a forwarder zone pointing to the supplied upstream server.
     */
    public void createForwarderZone(String zoneName, String forwarder) {
        var params = new HashMap<String, String>();
        params.put("zone", zoneName);
        params.put("type", ZoneType.Forwarder.name());
        params.put("forwarder", forwarder);
        getRequest("/api/zones/create", params);
    }



    /**
     * Deletes the specified zone from the server.
     */
    public void deleteZone(String zoneName) {
        var params = new HashMap<String, String>();
        params.put("zone", zoneName);
        getRequest("/api/zones/delete", params);
    }


    /**
     * Closes the underlying HTTP client.
     */
    @Override
    public void close() throws IOException {
        httpClient.close();
    }
}
