package io.synor.compiler;

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.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;

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

/**
 * Synor Compiler SDK Client
 *
 * Smart contract compilation, optimization, and ABI generation.
 *
 * Example:
 * <pre>{@code
 * var config = new CompilerConfig("your-api-key");
 * var compiler = new SynorCompiler(config);
 *
 * var result = compiler.compile(wasmBytes).join();
 * System.out.println("Optimized size: " + result.getOptimizedSize() + " bytes");
 * }</pre>
 */
public class SynorCompiler implements AutoCloseable {
    private final CompilerConfig config;
    private final HttpClient httpClient;
    private final Gson gson;
    private final AtomicBoolean closed = new AtomicBoolean(false);

    public final ContractsClient contracts;
    public final AbiClient abi;
    public final AnalysisClient analysis;
    public final ValidationClient validation;

    public SynorCompiler(CompilerConfig config) {
        this.config = config;
        this.httpClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofMillis(config.getTimeout()))
            .build();
        this.gson = new GsonBuilder()
            .setFieldNamingPolicy(com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
            .create();

        this.contracts = new ContractsClient(this);
        this.abi = new AbiClient(this);
        this.analysis = new AnalysisClient(this);
        this.validation = new ValidationClient(this);
    }

    public OptimizationLevel getDefaultOptimizationLevel() {
        return config.getOptimizationLevel();
    }

    public CompletableFuture<Boolean> healthCheck() {
        return get("/health", new TypeToken<Map<String, Object>>() {})
            .thenApply(result -> "healthy".equals(result.get("status")))
            .exceptionally(e -> false);
    }

    public CompletableFuture<Map<String, Object>> getInfo() {
        return get("/info", new TypeToken<Map<String, Object>>() {});
    }

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

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

    public CompletableFuture<CompilationResult> compile(byte[] wasm) {
        return compile(wasm, null);
    }

    public CompletableFuture<CompilationResult> compile(byte[] wasm, CompilationRequest request) {
        String wasmBase64 = Base64.getEncoder().encodeToString(wasm);

        Map<String, Object> body = new HashMap<>();
        body.put("wasm", wasmBase64);
        body.put("optimization_level", (request != null && request.getOptimizationLevel() != null
            ? request.getOptimizationLevel() : config.getOptimizationLevel()).toApiString());
        body.put("use_wasm_opt", request != null && request.getUseWasmOpt() != null
            ? request.getUseWasmOpt() : config.isUseWasmOpt());
        body.put("validate", request != null && request.getValidate() != null
            ? request.getValidate() : config.isValidate());
        body.put("extract_metadata", request != null && request.getExtractMetadata() != null
            ? request.getExtractMetadata() : config.isExtractMetadata());
        body.put("generate_abi", request != null && request.getGenerateAbi() != null
            ? request.getGenerateAbi() : config.isGenerateAbi());

        if (request != null && request.getStripOptions() != null) {
            body.put("strip_options", request.getStripOptions().toMap());
        }

        return post("/compile", body, new TypeToken<CompilationResult>() {});
    }

    <T> CompletableFuture<T> get(String path, TypeToken<T> typeToken) {
        checkClosed();

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(config.getEndpoint() + path))
            .header("Authorization", "Bearer " + config.getApiKey())
            .header("Content-Type", "application/json")
            .header("X-SDK-Version", "java/0.1.0")
            .GET()
            .timeout(Duration.ofMillis(config.getTimeout()))
            .build();

        return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(response -> handleResponse(response, typeToken));
    }

    <T> CompletableFuture<T> post(String path, Object body, TypeToken<T> typeToken) {
        checkClosed();

        String jsonBody = gson.toJson(body);

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(config.getEndpoint() + path))
            .header("Authorization", "Bearer " + config.getApiKey())
            .header("Content-Type", "application/json")
            .header("X-SDK-Version", "java/0.1.0")
            .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
            .timeout(Duration.ofMillis(config.getTimeout()))
            .build();

        return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(response -> handleResponse(response, typeToken));
    }

    private <T> T handleResponse(HttpResponse<String> response, TypeToken<T> typeToken) {
        if (response.statusCode() >= 400) {
            Map<String, Object> error;
            try {
                error = gson.fromJson(response.body(), new TypeToken<Map<String, Object>>() {}.getType());
            } catch (Exception e) {
                error = Map.of("message", response.body().isEmpty() ? "HTTP " + response.statusCode() : response.body());
            }
            throw new CompilerException(
                (String) error.getOrDefault("message", "HTTP " + response.statusCode()),
                (String) error.get("code"),
                response.statusCode()
            );
        }

        return gson.fromJson(response.body(), typeToken.getType());
    }

    private void checkClosed() {
        if (closed.get()) {
            throw new CompilerException("Client has been closed", "CLIENT_CLOSED", null);
        }
    }

    Gson getGson() {
        return gson;
    }
}

class ContractsClient {
    private final SynorCompiler compiler;

    ContractsClient(SynorCompiler compiler) {
        this.compiler = compiler;
    }

    public CompletableFuture<CompilationResult> compile(byte[] wasm) {
        return compiler.compile(wasm, null);
    }

    public CompletableFuture<CompilationResult> compile(byte[] wasm, CompilationRequest request) {
        return compiler.compile(wasm, request);
    }

    public CompletableFuture<CompilationResult> compileDev(byte[] wasm) {
        CompilationRequest request = new CompilationRequest();
        request.setOptimizationLevel(OptimizationLevel.NONE);
        request.setUseWasmOpt(false);
        request.setValidate(true);
        return compiler.compile(wasm, request);
    }

    public CompletableFuture<CompilationResult> compileProduction(byte[] wasm) {
        CompilationRequest request = new CompilationRequest();
        request.setOptimizationLevel(OptimizationLevel.AGGRESSIVE);
        request.setUseWasmOpt(true);
        request.setValidate(true);
        request.setExtractMetadata(true);
        request.setGenerateAbi(true);
        return compiler.compile(wasm, request);
    }

    public CompletableFuture<CompilationResult> get(String contractId) {
        return compiler.get("/contracts/" + contractId, new TypeToken<CompilationResult>() {});
    }

    public CompletableFuture<List<CompilationResult>> list(Integer limit, Integer offset) {
        StringBuilder path = new StringBuilder("/contracts");
        List<String> params = new ArrayList<>();
        if (limit != null) params.add("limit=" + limit);
        if (offset != null) params.add("offset=" + offset);
        if (!params.isEmpty()) path.append("?").append(String.join("&", params));

        return compiler.get(path.toString(), new TypeToken<Map<String, List<CompilationResult>>>() {})
            .thenApply(result -> result.getOrDefault("contracts", List.of()));
    }

    public CompletableFuture<byte[]> getCode(String contractId) {
        return compiler.get("/contracts/" + contractId + "/code", new TypeToken<Map<String, String>>() {})
            .thenApply(result -> Base64.getDecoder().decode(result.getOrDefault("code", "")));
    }
}

class AbiClient {
    private final SynorCompiler compiler;

    AbiClient(SynorCompiler compiler) {
        this.compiler = compiler;
    }

    public CompletableFuture<ContractAbi> extract(byte[] wasm) {
        String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
        return compiler.post("/abi/extract", Map.of("wasm", wasmBase64), new TypeToken<ContractAbi>() {});
    }

    public CompletableFuture<ContractAbi> get(String contractId) {
        return compiler.get("/contracts/" + contractId + "/abi", new TypeToken<ContractAbi>() {});
    }

    public CompletableFuture<String> encodeCall(FunctionAbi functionAbi, List<Object> args) {
        Map<String, Object> body = new HashMap<>();
        body.put("function", functionAbi);
        body.put("args", args);
        return compiler.post("/abi/encode", body, new TypeToken<Map<String, String>>() {})
            .thenApply(result -> result.getOrDefault("data", ""));
    }

    public CompletableFuture<List<Object>> decodeResult(FunctionAbi functionAbi, String data) {
        Map<String, Object> body = new HashMap<>();
        body.put("function", functionAbi);
        body.put("data", data);
        return compiler.post("/abi/decode", body, new TypeToken<Map<String, List<Object>>>() {})
            .thenApply(result -> result.getOrDefault("values", List.of()));
    }
}

class AnalysisClient {
    private final SynorCompiler compiler;

    AnalysisClient(SynorCompiler compiler) {
        this.compiler = compiler;
    }

    public CompletableFuture<ContractAnalysis> analyze(byte[] wasm) {
        String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
        return compiler.post("/analysis", Map.of("wasm", wasmBase64), new TypeToken<ContractAnalysis>() {});
    }

    public CompletableFuture<ContractAnalysis> get(String contractId) {
        return compiler.get("/contracts/" + contractId + "/analysis", new TypeToken<ContractAnalysis>() {});
    }

    public CompletableFuture<ContractMetadata> extractMetadata(byte[] wasm) {
        String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
        return compiler.post("/analysis/metadata", Map.of("wasm", wasmBase64), new TypeToken<ContractMetadata>() {});
    }

    public CompletableFuture<Long> estimateDeployGas(byte[] wasm) {
        String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
        return compiler.post("/analysis/estimate-gas", Map.of("wasm", wasmBase64), new TypeToken<Map<String, Long>>() {})
            .thenApply(result -> result.getOrDefault("gas", 0L));
    }

    public CompletableFuture<SecurityAnalysis> securityScan(byte[] wasm) {
        String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
        return compiler.post("/analysis/security", Map.of("wasm", wasmBase64), new TypeToken<Map<String, SecurityAnalysis>>() {})
            .thenApply(result -> result.get("security"));
    }
}

class ValidationClient {
    private final SynorCompiler compiler;

    ValidationClient(SynorCompiler compiler) {
        this.compiler = compiler;
    }

    public CompletableFuture<ValidationResult> validate(byte[] wasm) {
        String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
        return compiler.post("/validate", Map.of("wasm", wasmBase64), new TypeToken<ValidationResult>() {});
    }

    public CompletableFuture<Boolean> isValid(byte[] wasm) {
        return validate(wasm).thenApply(ValidationResult::isValid);
    }

    public CompletableFuture<List<String>> getErrors(byte[] wasm) {
        return validate(wasm).thenApply(result ->
            result.getErrors().stream().map(ValidationError::getMessage).toList()
        );
    }

    public CompletableFuture<Boolean> validateExports(byte[] wasm, List<String> requiredExports) {
        String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
        Map<String, Object> body = new HashMap<>();
        body.put("wasm", wasmBase64);
        body.put("required_exports", requiredExports);
        return compiler.post("/validate/exports", body, new TypeToken<Map<String, Boolean>>() {})
            .thenApply(result -> result.getOrDefault("valid", false));
    }

    public CompletableFuture<Boolean> validateMemory(byte[] wasm, int maxPages) {
        String wasmBase64 = Base64.getEncoder().encodeToString(wasm);
        Map<String, Object> body = new HashMap<>();
        body.put("wasm", wasmBase64);
        body.put("max_pages", maxPages);
        return compiler.post("/validate/memory", body, new TypeToken<Map<String, Boolean>>() {})
            .thenApply(result -> result.getOrDefault("valid", false));
    }
}
