package io.synor.mining;

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;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Synor Mining SDK for Java.
 * Pool connections, block templates, hashrate stats, and GPU management.
 */
public class SynorMining implements AutoCloseable {
    private static final String DEFAULT_ENDPOINT = "https://mining.synor.io/v1";
    private static final Gson gson = new GsonBuilder().create();

    private final MiningConfig config;
    private final HttpClient httpClient;
    private final AtomicBoolean closed = new AtomicBoolean(false);
    private final AtomicReference<StratumConnection> activeConnection = new AtomicReference<>();

    public SynorMining(String apiKey) {
        this(new MiningConfig(apiKey, DEFAULT_ENDPOINT, 60, 3, false));
    }

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

    // ==================== Pool Operations ====================

    public CompletableFuture<StratumConnection> connect(PoolConfig pool) {
        Map<String, Object> body = new HashMap<>();
        body.put("url", pool.getUrl());
        body.put("user", pool.getUser());
        if (pool.getPassword() != null) body.put("password", pool.getPassword());
        if (pool.getAlgorithm() != null) body.put("algorithm", pool.getAlgorithm());
        if (pool.getDifficulty() != null) body.put("difficulty", pool.getDifficulty());
        return request("POST", "/pool/connect", body)
                .thenApply(resp -> {
                    StratumConnection conn = gson.fromJson(resp, StratumConnection.class);
                    activeConnection.set(conn);
                    return conn;
                });
    }

    public CompletableFuture<Void> disconnect() {
        StratumConnection conn = activeConnection.get();
        if (conn == null) {
            return CompletableFuture.completedFuture(null);
        }
        return request("POST", "/pool/disconnect", null)
                .thenApply(resp -> {
                    activeConnection.set(null);
                    return null;
                });
    }

    public CompletableFuture<StratumConnection> getConnection() {
        return request("GET", "/pool/connection", null)
                .thenApply(resp -> {
                    StratumConnection conn = gson.fromJson(resp, StratumConnection.class);
                    activeConnection.set(conn);
                    return conn;
                });
    }

    public CompletableFuture<PoolStats> getPoolStats() {
        return request("GET", "/pool/stats", null)
                .thenApply(resp -> gson.fromJson(resp, PoolStats.class));
    }

    public boolean isConnected() {
        StratumConnection conn = activeConnection.get();
        return conn != null && conn.getStatus() == ConnectionStatus.connected;
    }

    // ==================== Mining Operations ====================

    public CompletableFuture<BlockTemplate> getBlockTemplate() {
        return request("GET", "/mining/template", null)
                .thenApply(resp -> gson.fromJson(resp, BlockTemplate.class));
    }

    public CompletableFuture<SubmitResult> submitWork(MinedWork work) {
        Map<String, Object> body = new HashMap<>();
        body.put("templateId", work.getTemplateId());
        body.put("nonce", work.getNonce());
        body.put("extraNonce", work.getExtraNonce());
        body.put("timestamp", work.getTimestamp());
        body.put("hash", work.getHash());
        return request("POST", "/mining/submit", body)
                .thenApply(resp -> gson.fromJson(resp, SubmitResult.class));
    }

    public CompletableFuture<Void> startMining(String algorithm) {
        Map<String, Object> body = new HashMap<>();
        if (algorithm != null) body.put("algorithm", algorithm);
        return request("POST", "/mining/start", body)
                .thenApply(resp -> null);
    }

    public CompletableFuture<Void> stopMining() {
        return request("POST", "/mining/stop", null)
                .thenApply(resp -> null);
    }

    public CompletableFuture<Boolean> isMining() {
        return request("GET", "/mining/status", null)
                .thenApply(resp -> {
                    Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
                    return Boolean.TRUE.equals(map.get("mining"));
                });
    }

    // ==================== Stats Operations ====================

    public CompletableFuture<Hashrate> getHashrate() {
        return request("GET", "/stats/hashrate", null)
                .thenApply(resp -> gson.fromJson(resp, Hashrate.class));
    }

    public CompletableFuture<MiningStats> getStats() {
        return request("GET", "/stats", null)
                .thenApply(resp -> gson.fromJson(resp, MiningStats.class));
    }

    public CompletableFuture<Earnings> getEarnings(TimePeriod period) {
        String path = "/stats/earnings";
        if (period != null) {
            path += "?period=" + period.name().toLowerCase();
        }
        return request("GET", path, null)
                .thenApply(resp -> gson.fromJson(resp, Earnings.class));
    }

    public CompletableFuture<ShareStats> getShareStats() {
        return request("GET", "/stats/shares", null)
                .thenApply(resp -> gson.fromJson(resp, ShareStats.class));
    }

    // ==================== Device Operations ====================

    public CompletableFuture<List<MiningDevice>> listDevices() {
        return request("GET", "/devices", null)
                .thenApply(resp -> {
                    Type type = new TypeToken<DevicesResponse>(){}.getType();
                    DevicesResponse result = gson.fromJson(resp, type);
                    return result.devices;
                });
    }

    public CompletableFuture<MiningDevice> getDevice(String deviceId) {
        return request("GET", "/devices/" + encode(deviceId), null)
                .thenApply(resp -> gson.fromJson(resp, MiningDevice.class));
    }

    public CompletableFuture<MiningDevice> setDeviceConfig(String deviceId, DeviceConfig deviceConfig) {
        Map<String, Object> body = new HashMap<>();
        body.put("enabled", deviceConfig.isEnabled());
        if (deviceConfig.getIntensity() != null) body.put("intensity", deviceConfig.getIntensity());
        if (deviceConfig.getPowerLimit() != null) body.put("powerLimit", deviceConfig.getPowerLimit());
        if (deviceConfig.getCoreClockOffset() != null) body.put("coreClockOffset", deviceConfig.getCoreClockOffset());
        if (deviceConfig.getMemoryClockOffset() != null) body.put("memoryClockOffset", deviceConfig.getMemoryClockOffset());
        if (deviceConfig.getFanSpeed() != null) body.put("fanSpeed", deviceConfig.getFanSpeed());
        return request("PUT", "/devices/" + encode(deviceId) + "/config", body)
                .thenApply(resp -> gson.fromJson(resp, MiningDevice.class));
    }

    public CompletableFuture<Void> enableDevice(String deviceId) {
        DeviceConfig cfg = new DeviceConfig();
        cfg.setEnabled(true);
        return setDeviceConfig(deviceId, cfg).thenApply(d -> null);
    }

    public CompletableFuture<Void> disableDevice(String deviceId) {
        DeviceConfig cfg = new DeviceConfig();
        cfg.setEnabled(false);
        return setDeviceConfig(deviceId, cfg).thenApply(d -> null);
    }

    // ==================== Worker Operations ====================

    public CompletableFuture<List<WorkerInfo>> listWorkers() {
        return request("GET", "/workers", null)
                .thenApply(resp -> {
                    Type type = new TypeToken<WorkersResponse>(){}.getType();
                    WorkersResponse result = gson.fromJson(resp, type);
                    return result.workers;
                });
    }

    public CompletableFuture<WorkerInfo> getWorker(String workerId) {
        return request("GET", "/workers/" + encode(workerId), null)
                .thenApply(resp -> gson.fromJson(resp, WorkerInfo.class));
    }

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

    // ==================== Algorithm Operations ====================

    public CompletableFuture<List<MiningAlgorithm>> listAlgorithms() {
        return request("GET", "/algorithms", null)
                .thenApply(resp -> {
                    Type type = new TypeToken<AlgorithmsResponse>(){}.getType();
                    AlgorithmsResponse result = gson.fromJson(resp, type);
                    return result.algorithms;
                });
    }

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

    public CompletableFuture<Void> switchAlgorithm(String algorithm) {
        return request("POST", "/algorithms/switch", Map.of("algorithm", algorithm))
                .thenApply(resp -> null);
    }

    public CompletableFuture<MiningAlgorithm> getMostProfitable() {
        return request("GET", "/algorithms/profitable", null)
                .thenApply(resp -> gson.fromJson(resp, MiningAlgorithm.class));
    }

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

    @Override
    public void close() {
        if (closed.compareAndSet(false, true)) {
            activeConnection.set(null);
        }
    }

    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 MiningException("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 MiningException(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 DevicesResponse { List<MiningDevice> devices; }
    private static class WorkersResponse { List<WorkerInfo> workers; }
    private static class AlgorithmsResponse { List<MiningAlgorithm> algorithms; }
}
