package io.synor.hosting;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Synor Hosting SDK for Java.
 * Decentralized web hosting with domain management, DNS, and deployments.
 */
public class SynorHosting implements AutoCloseable {
    private static final String DEFAULT_ENDPOINT = "https://hosting.synor.io/v1";
    private static final Gson gson = new GsonBuilder().create();

    private final HostingConfig config;
    private final HttpClient httpClient;
    private final AtomicBoolean closed = new AtomicBoolean(false);

    public SynorHosting(String apiKey) {
        this(new HostingConfig(apiKey, DEFAULT_ENDPOINT, 60, 3, false));
    }

    public SynorHosting(HostingConfig config) {
        this.config = config;
        this.httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(config.getTimeoutSecs()))
                .build();
    }

    // ==================== Domain Operations ====================

    public CompletableFuture<DomainAvailability> checkAvailability(String name) {
        return request("GET", "/domains/check/" + encode(name), null)
                .thenApply(resp -> gson.fromJson(resp, DomainAvailability.class));
    }

    public CompletableFuture<Domain> registerDomain(String name, RegisterDomainOptions options) {
        Map<String, Object> body = new HashMap<>();
        body.put("name", name);
        if (options != null) {
            if (options.getYears() != null) body.put("years", options.getYears());
            if (options.getAutoRenew() != null) body.put("autoRenew", options.getAutoRenew());
        }
        return request("POST", "/domains", body)
                .thenApply(resp -> gson.fromJson(resp, Domain.class));
    }

    public CompletableFuture<Domain> getDomain(String name) {
        return request("GET", "/domains/" + encode(name), null)
                .thenApply(resp -> gson.fromJson(resp, Domain.class));
    }

    public CompletableFuture<List<Domain>> listDomains() {
        return request("GET", "/domains", null)
                .thenApply(resp -> {
                    Type type = new TypeToken<DomainsResponse>(){}.getType();
                    DomainsResponse result = gson.fromJson(resp, type);
                    return result.domains;
                });
    }

    public CompletableFuture<Domain> updateDomainRecord(String name, DomainRecord record) {
        return request("PUT", "/domains/" + encode(name) + "/record", record)
                .thenApply(resp -> gson.fromJson(resp, Domain.class));
    }

    public CompletableFuture<DomainRecord> resolveDomain(String name) {
        return request("GET", "/domains/" + encode(name) + "/resolve", null)
                .thenApply(resp -> gson.fromJson(resp, DomainRecord.class));
    }

    public CompletableFuture<Domain> renewDomain(String name, int years) {
        Map<String, Object> body = new HashMap<>();
        body.put("years", years);
        return request("POST", "/domains/" + encode(name) + "/renew", body)
                .thenApply(resp -> gson.fromJson(resp, Domain.class));
    }

    // ==================== DNS Operations ====================

    public CompletableFuture<DnsZone> getDnsZone(String domain) {
        return request("GET", "/dns/" + encode(domain), null)
                .thenApply(resp -> gson.fromJson(resp, DnsZone.class));
    }

    public CompletableFuture<DnsZone> setDnsRecords(String domain, List<DnsRecord> records) {
        Map<String, Object> body = new HashMap<>();
        body.put("records", records);
        return request("PUT", "/dns/" + encode(domain), body)
                .thenApply(resp -> gson.fromJson(resp, DnsZone.class));
    }

    public CompletableFuture<DnsZone> addDnsRecord(String domain, DnsRecord record) {
        return request("POST", "/dns/" + encode(domain) + "/records", record)
                .thenApply(resp -> gson.fromJson(resp, DnsZone.class));
    }

    public CompletableFuture<DnsZone> deleteDnsRecord(String domain, String recordType, String name) {
        String path = "/dns/" + encode(domain) + "/records/" + recordType + "/" + encode(name);
        return request("DELETE", path, null)
                .thenApply(resp -> gson.fromJson(resp, DnsZone.class));
    }

    // ==================== Deployment Operations ====================

    public CompletableFuture<Deployment> deploy(String cid, DeployOptions options) {
        Map<String, Object> body = new HashMap<>();
        body.put("cid", cid);
        if (options != null) {
            if (options.getDomain() != null) body.put("domain", options.getDomain());
            if (options.getSubdomain() != null) body.put("subdomain", options.getSubdomain());
            if (options.getSpa() != null) body.put("spa", options.getSpa());
            if (options.getCleanUrls() != null) body.put("cleanUrls", options.getCleanUrls());
        }
        return request("POST", "/deployments", body)
                .thenApply(resp -> gson.fromJson(resp, Deployment.class));
    }

    public CompletableFuture<Deployment> getDeployment(String id) {
        return request("GET", "/deployments/" + encode(id), null)
                .thenApply(resp -> gson.fromJson(resp, Deployment.class));
    }

    public CompletableFuture<List<Deployment>> listDeployments(String domain) {
        String path = domain != null ? "/deployments?domain=" + encode(domain) : "/deployments";
        return request("GET", path, null)
                .thenApply(resp -> {
                    Type type = new TypeToken<DeploymentsResponse>(){}.getType();
                    DeploymentsResponse result = gson.fromJson(resp, type);
                    return result.deployments;
                });
    }

    public CompletableFuture<Deployment> rollback(String domain, String deploymentId) {
        Map<String, Object> body = new HashMap<>();
        body.put("domain", domain);
        return request("POST", "/deployments/" + encode(deploymentId) + "/rollback", body)
                .thenApply(resp -> gson.fromJson(resp, Deployment.class));
    }

    public CompletableFuture<Void> deleteDeployment(String id) {
        return request("DELETE", "/deployments/" + encode(id), null)
                .thenApply(resp -> null);
    }

    // ==================== SSL Operations ====================

    public CompletableFuture<Certificate> provisionSsl(String domain, ProvisionSslOptions options) {
        return request("POST", "/ssl/" + encode(domain), options)
                .thenApply(resp -> gson.fromJson(resp, Certificate.class));
    }

    public CompletableFuture<Certificate> getCertificate(String domain) {
        return request("GET", "/ssl/" + encode(domain), null)
                .thenApply(resp -> gson.fromJson(resp, Certificate.class));
    }

    public CompletableFuture<Certificate> renewCertificate(String domain) {
        return request("POST", "/ssl/" + encode(domain) + "/renew", null)
                .thenApply(resp -> gson.fromJson(resp, Certificate.class));
    }

    public CompletableFuture<Void> deleteCertificate(String domain) {
        return request("DELETE", "/ssl/" + encode(domain), null)
                .thenApply(resp -> null);
    }

    // ==================== Site Configuration ====================

    public CompletableFuture<SiteConfig> getSiteConfig(String domain) {
        return request("GET", "/sites/" + encode(domain) + "/config", null)
                .thenApply(resp -> gson.fromJson(resp, SiteConfig.class));
    }

    public CompletableFuture<SiteConfig> updateSiteConfig(String domain, Map<String, Object> config) {
        return request("PATCH", "/sites/" + encode(domain) + "/config", config)
                .thenApply(resp -> gson.fromJson(resp, SiteConfig.class));
    }

    public CompletableFuture<Long> purgeCache(String domain, List<String> paths) {
        Map<String, Object> body = new HashMap<>();
        if (paths != null) body.put("paths", paths);
        return request("DELETE", "/sites/" + encode(domain) + "/cache", body)
                .thenApply(resp -> {
                    Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
                    return ((Number) map.get("purged")).longValue();
                });
    }

    // ==================== Analytics ====================

    public CompletableFuture<AnalyticsData> getAnalytics(String domain, AnalyticsOptions options) {
        StringBuilder path = new StringBuilder("/sites/" + encode(domain) + "/analytics");
        if (options != null) {
            StringBuilder params = new StringBuilder();
            if (options.getPeriod() != null) params.append("period=").append(encode(options.getPeriod()));
            if (options.getStart() != null) {
                if (params.length() > 0) params.append("&");
                params.append("start=").append(encode(options.getStart()));
            }
            if (options.getEnd() != null) {
                if (params.length() > 0) params.append("&");
                params.append("end=").append(encode(options.getEnd()));
            }
            if (params.length() > 0) path.append("?").append(params);
        }
        return request("GET", path.toString(), null)
                .thenApply(resp -> gson.fromJson(resp, AnalyticsData.class));
    }

    // ==================== Lifecycle ====================

    @Override
    public void close() {
        closed.set(true);
    }

    public boolean isClosed() {
        return closed.get();
    }

    public CompletableFuture<Boolean> healthCheck() {
        return request("GET", "/health", null)
                .thenApply(resp -> {
                    Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
                    return "healthy".equals(map.get("status"));
                })
                .exceptionally(e -> false);
    }

    // ==================== Private Methods ====================

    private CompletableFuture<String> request(String method, String path, Object body) {
        if (closed.get()) {
            return CompletableFuture.failedFuture(new HostingException("Client has been closed"));
        }

        return doRequest(method, path, body, 0);
    }

    private CompletableFuture<String> doRequest(String method, String path, Object body, int attempt) {
        String url = config.getEndpoint() + path;

        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .header("Authorization", "Bearer " + config.getApiKey())
                .header("Content-Type", "application/json")
                .header("X-SDK-Version", "java/0.1.0")
                .timeout(Duration.ofSeconds(config.getTimeoutSecs()));

        HttpRequest.BodyPublisher bodyPublisher = body != null
                ? HttpRequest.BodyPublishers.ofString(gson.toJson(body))
                : HttpRequest.BodyPublishers.noBody();

        switch (method) {
            case "GET": requestBuilder.GET(); break;
            case "POST": requestBuilder.POST(bodyPublisher); break;
            case "PUT": requestBuilder.PUT(bodyPublisher); break;
            case "PATCH": requestBuilder.method("PATCH", bodyPublisher); break;
            case "DELETE": requestBuilder.method("DELETE", bodyPublisher); break;
        }

        return httpClient.sendAsync(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
                .thenCompose(response -> {
                    if (response.statusCode() >= 400) {
                        Map<String, Object> errorBody = gson.fromJson(response.body(),
                                new TypeToken<Map<String, Object>>(){}.getType());
                        String message = errorBody != null && errorBody.get("message") != null
                                ? (String) errorBody.get("message") : "HTTP " + response.statusCode();
                        return CompletableFuture.failedFuture(
                                new HostingException(message, (String) errorBody.get("code"), response.statusCode()));
                    }
                    return CompletableFuture.completedFuture(response.body());
                })
                .exceptionally(e -> {
                    if (attempt < config.getRetries() - 1) {
                        try { Thread.sleep((long) Math.pow(2, attempt) * 1000); }
                        catch (InterruptedException ie) { Thread.currentThread().interrupt(); }
                        return doRequest(method, path, body, attempt + 1).join();
                    }
                    throw new RuntimeException(e);
                });
    }

    private static String encode(String value) {
        return java.net.URLEncoder.encode(value, java.nio.charset.StandardCharsets.UTF_8);
    }

    // Helper response types
    private static class DomainsResponse { List<Domain> domains; }
    private static class DeploymentsResponse { List<Deployment> deployments; }
}
