package io.synor.economics;

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 Economics SDK for Java.
 * Pricing, billing, staking, and discount operations.
 */
public class SynorEconomics implements AutoCloseable {
    private static final String DEFAULT_ENDPOINT = "https://economics.synor.io/v1";
    private static final Gson gson = new GsonBuilder().create();

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

    public SynorEconomics(String apiKey) {
        this(new EconomicsConfig(apiKey, DEFAULT_ENDPOINT, 60, 3, false));
    }

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

    // ==================== Pricing Operations ====================

    public CompletableFuture<Price> getPrice(ServiceType service, UsageMetrics usage) {
        Map<String, Object> body = new HashMap<>();
        body.put("service", service.name().toLowerCase());
        body.put("usage", usage);
        return request("POST", "/pricing/calculate", body)
                .thenApply(resp -> gson.fromJson(resp, Price.class));
    }

    public CompletableFuture<CostEstimate> estimateCost(UsagePlan plan) {
        Map<String, Object> body = new HashMap<>();
        body.put("service", plan.getService().name().toLowerCase());
        body.put("estimatedUsage", plan.getEstimatedUsage());
        body.put("period", plan.getPeriod().name().toLowerCase());
        return request("POST", "/pricing/estimate", body)
                .thenApply(resp -> gson.fromJson(resp, CostEstimate.class));
    }

    public CompletableFuture<List<PricingTier>> getPricingTiers(ServiceType service) {
        return request("GET", "/pricing/tiers/" + service.name().toLowerCase(), null)
                .thenApply(resp -> {
                    Type type = new TypeToken<TiersResponse>(){}.getType();
                    TiersResponse result = gson.fromJson(resp, type);
                    return result.tiers;
                });
    }

    // ==================== Billing Operations ====================

    public CompletableFuture<Usage> getUsage(BillingPeriod period) {
        String path = "/billing/usage";
        if (period != null) {
            path += "?period=" + period.name().toLowerCase();
        }
        return request("GET", path, null)
                .thenApply(resp -> gson.fromJson(resp, Usage.class));
    }

    public CompletableFuture<Usage> getUsageByDateRange(long startDate, long endDate) {
        String path = "/billing/usage?startDate=" + startDate + "&endDate=" + endDate;
        return request("GET", path, null)
                .thenApply(resp -> gson.fromJson(resp, Usage.class));
    }

    public CompletableFuture<List<Invoice>> getInvoices() {
        return request("GET", "/billing/invoices", null)
                .thenApply(resp -> {
                    Type type = new TypeToken<InvoicesResponse>(){}.getType();
                    InvoicesResponse result = gson.fromJson(resp, type);
                    return result.invoices;
                });
    }

    public CompletableFuture<Invoice> getInvoice(String invoiceId) {
        return request("GET", "/billing/invoices/" + encode(invoiceId), null)
                .thenApply(resp -> gson.fromJson(resp, Invoice.class));
    }

    public CompletableFuture<byte[]> downloadInvoice(String invoiceId) {
        return request("GET", "/billing/invoices/" + encode(invoiceId) + "/pdf", null)
                .thenApply(resp -> {
                    Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
                    String base64 = (String) map.get("data");
                    return java.util.Base64.getDecoder().decode(base64);
                });
    }

    public CompletableFuture<AccountBalance> getBalance() {
        return request("GET", "/billing/balance", null)
                .thenApply(resp -> gson.fromJson(resp, AccountBalance.class));
    }

    public CompletableFuture<Void> addCredits(String amount, String paymentMethod) {
        Map<String, Object> body = new HashMap<>();
        body.put("amount", amount);
        body.put("paymentMethod", paymentMethod);
        return request("POST", "/billing/credits", body)
                .thenApply(resp -> null);
    }

    // ==================== Staking Operations ====================

    public CompletableFuture<StakeReceipt> stake(String amount, Integer durationDays) {
        Map<String, Object> body = new HashMap<>();
        body.put("amount", amount);
        if (durationDays != null) {
            body.put("durationDays", durationDays);
        }
        return request("POST", "/staking/stake", body)
                .thenApply(resp -> gson.fromJson(resp, StakeReceipt.class));
    }

    public CompletableFuture<UnstakeReceipt> unstake(String stakeId) {
        return request("POST", "/staking/unstake/" + encode(stakeId), null)
                .thenApply(resp -> gson.fromJson(resp, UnstakeReceipt.class));
    }

    public CompletableFuture<List<StakeInfo>> getStakes() {
        return request("GET", "/staking/stakes", null)
                .thenApply(resp -> {
                    Type type = new TypeToken<StakesResponse>(){}.getType();
                    StakesResponse result = gson.fromJson(resp, type);
                    return result.stakes;
                });
    }

    public CompletableFuture<StakeInfo> getStake(String stakeId) {
        return request("GET", "/staking/stakes/" + encode(stakeId), null)
                .thenApply(resp -> gson.fromJson(resp, StakeInfo.class));
    }

    public CompletableFuture<Rewards> getStakingRewards() {
        return request("GET", "/staking/rewards", null)
                .thenApply(resp -> gson.fromJson(resp, Rewards.class));
    }

    public CompletableFuture<String> claimRewards() {
        return request("POST", "/staking/rewards/claim", null)
                .thenApply(resp -> {
                    Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
                    return (String) map.get("txHash");
                });
    }

    public CompletableFuture<Double> getCurrentApy() {
        return request("GET", "/staking/apy", null)
                .thenApply(resp -> {
                    Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
                    return ((Number) map.get("apy")).doubleValue();
                });
    }

    // ==================== Discount Operations ====================

    public CompletableFuture<AppliedDiscount> applyDiscount(String code) {
        Map<String, Object> body = new HashMap<>();
        body.put("code", code);
        return request("POST", "/discounts/apply", body)
                .thenApply(resp -> gson.fromJson(resp, AppliedDiscount.class));
    }

    public CompletableFuture<List<Discount>> getAvailableDiscounts() {
        return request("GET", "/discounts/available", null)
                .thenApply(resp -> {
                    Type type = new TypeToken<DiscountsResponse>(){}.getType();
                    DiscountsResponse result = gson.fromJson(resp, type);
                    return result.discounts;
                });
    }

    public CompletableFuture<Discount> validateDiscount(String code) {
        return request("GET", "/discounts/validate/" + encode(code), null)
                .thenApply(resp -> gson.fromJson(resp, Discount.class));
    }

    public CompletableFuture<List<AppliedDiscount>> getAppliedDiscounts() {
        return request("GET", "/discounts/applied", null)
                .thenApply(resp -> {
                    Type type = new TypeToken<AppliedDiscountsResponse>(){}.getType();
                    AppliedDiscountsResponse result = gson.fromJson(resp, type);
                    return result.discounts;
                });
    }

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

    // ==================== 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 EconomicsException("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 builder = 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 bodyPub = body != null
                ? HttpRequest.BodyPublishers.ofString(gson.toJson(body))
                : HttpRequest.BodyPublishers.noBody();

        switch (method) {
            case "GET": builder.GET(); break;
            case "POST": builder.POST(bodyPub); break;
            case "PUT": builder.PUT(bodyPub); break;
            case "DELETE": builder.method("DELETE", bodyPub); break;
        }

        return httpClient.sendAsync(builder.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();
                        String code = errorBody != null ? (String) errorBody.get("code") : null;
                        return CompletableFuture.failedFuture(
                                new EconomicsException(message, code, response.statusCode()));
                    }
                    return CompletableFuture.completedFuture(response.body());
                })
                .exceptionally(e -> {
                    if (attempt < config.getRetries() - 1) {
                        sleep((long) Math.pow(2, attempt) * 1000);
                        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);
    }

    private static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }

    // Helper response types
    private static class TiersResponse { List<PricingTier> tiers; }
    private static class InvoicesResponse { List<Invoice> invoices; }
    private static class StakesResponse { List<StakeInfo> stakes; }
    private static class DiscountsResponse { List<Discount> discounts; }
    private static class AppliedDiscountsResponse { List<AppliedDiscount> discounts; }
}
