package io.synor.storage;

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.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

/**
 * Synor Storage SDK for Java.
 *
 * Decentralized storage, pinning, and content retrieval.
 *
 * Example:
 * <pre>{@code
 * SynorStorage storage = new SynorStorage("your-api-key");
 * UploadResponse result = storage.upload("Hello, World!".getBytes()).join();
 * System.out.println("CID: " + result.getCid());
 * }</pre>
 */
public class SynorStorage {
    private static final String DEFAULT_ENDPOINT = "https://storage.synor.cc/api/v1";
    private static final String DEFAULT_GATEWAY = "https://gateway.synor.cc";

    private final StorageConfig config;
    private final HttpClient httpClient;
    private final Gson gson;

    public SynorStorage(String apiKey) {
        this(StorageConfig.builder().apiKey(apiKey).build());
    }

    public SynorStorage(StorageConfig config) {
        this.config = config;
        this.httpClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(config.getTimeoutSeconds()))
            .build();
        this.gson = new GsonBuilder()
            .setFieldNamingPolicy(com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
            .create();
    }

    /** Upload content. */
    public CompletableFuture<UploadResponse> upload(byte[] data) {
        return upload(data, UploadOptions.defaults());
    }

    /** Upload content with options. */
    public CompletableFuture<UploadResponse> upload(byte[] data, UploadOptions options) {
        String boundary = "----SynorBoundary" + System.currentTimeMillis();
        String body = "--" + boundary + "\r\n"
            + "Content-Disposition: form-data; name=\"file\"; filename=\"file\"\r\n"
            + "Content-Type: application/octet-stream\r\n\r\n"
            + new String(data) + "\r\n"
            + "--" + boundary + "--\r\n";

        StringBuilder path = new StringBuilder("/upload?");
        path.append("pin=").append(options.isPin());
        if (options.isWrapWithDirectory()) path.append("&wrapWithDirectory=true");
        path.append("&cidVersion=").append(options.getCidVersion());
        path.append("&hashAlgorithm=").append(options.getHashAlgorithm().getValue());

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(config.getEndpoint() + path))
            .header("Authorization", "Bearer " + config.getApiKey())
            .header("Content-Type", "multipart/form-data; boundary=" + boundary)
            .timeout(Duration.ofSeconds(config.getTimeoutSeconds()))
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

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

    /** Download content by CID. */
    public CompletableFuture<byte[]> download(String cid) {
        return download(cid, DownloadOptions.defaults());
    }

    /** Download content with options. */
    public CompletableFuture<byte[]> download(String cid, DownloadOptions options) {
        StringBuilder path = new StringBuilder("/content/" + cid);
        if (options.getOffset() != null || options.getLength() != null) {
            path.append("?");
            if (options.getOffset() != null) path.append("offset=").append(options.getOffset()).append("&");
            if (options.getLength() != null) path.append("length=").append(options.getLength());
        }

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(config.getEndpoint() + path))
            .header("Authorization", "Bearer " + config.getApiKey())
            .timeout(Duration.ofSeconds(config.getTimeoutSeconds()))
            .GET()
            .build();

        return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofByteArray())
            .thenApply(response -> {
                if (response.statusCode() >= 400) {
                    throw new StorageException("Download failed", response.statusCode(), null);
                }
                return response.body();
            });
    }

    /** Pin content. */
    public CompletableFuture<Pin> pin(PinRequest pinRequest) {
        return post("/pins", pinRequest, Pin.class);
    }

    /** Unpin content. */
    public CompletableFuture<Void> unpin(String cid) {
        return delete("/pins/" + cid);
    }

    /** Get pin status. */
    public CompletableFuture<Pin> getPinStatus(String cid) {
        return get("/pins/" + cid, Pin.class);
    }

    /** List pins. */
    public CompletableFuture<ListPinsResponse> listPins(ListPinsOptions options) {
        StringBuilder path = new StringBuilder("/pins?");
        if (options.getLimit() != null) path.append("limit=").append(options.getLimit()).append("&");
        if (options.getOffset() != null) path.append("offset=").append(options.getOffset());
        return get(path.toString(), ListPinsResponse.class);
    }

    /** Get gateway URL. */
    public GatewayUrl getGatewayUrl(String cid, String path) {
        String fullPath = path != null ? "/" + cid + "/" + path : "/" + cid;
        return new GatewayUrl(config.getGateway() + "/ipfs" + fullPath, cid, path);
    }

    /** Create CAR file. */
    public CompletableFuture<CarFile> createCar(List<FileEntry> files) {
        return post("/car/create", Map.of("files", files), CarFile.class);
    }

    /** Import CAR file. */
    public CompletableFuture<ImportCarResponse> importCar(byte[] carData, boolean pin) {
        String encoded = Base64.getEncoder().encodeToString(carData);
        return post("/car/import", Map.of("car", encoded, "pin", pin), ImportCarResponse.class);
    }

    /** Create directory. */
    public CompletableFuture<UploadResponse> createDirectory(List<FileEntry> files) {
        return post("/directory", Map.of("files", files), UploadResponse.class);
    }

    /** List directory. */
    public CompletableFuture<List<DirectoryEntry>> listDirectory(String cid, String path) {
        String apiPath = "/directory/" + cid + (path != null ? "?path=" + path : "");
        return get(apiPath, ListDirectoryResponse.class)
            .thenApply(ListDirectoryResponse::getEntries);
    }

    /** Get storage stats. */
    public CompletableFuture<StorageStats> getStats() {
        return get("/stats", StorageStats.class);
    }

    /** Check if content exists. */
    public CompletableFuture<Boolean> exists(String cid) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(config.getEndpoint() + "/content/" + cid))
            .header("Authorization", "Bearer " + config.getApiKey())
            .method("HEAD", HttpRequest.BodyPublishers.noBody())
            .build();

        return httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding())
            .thenApply(response -> response.statusCode() == 200);
    }

    private <T> CompletableFuture<T> get(String path, Class<T> responseType) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(config.getEndpoint() + path))
            .header("Authorization", "Bearer " + config.getApiKey())
            .header("Content-Type", "application/json")
            .timeout(Duration.ofSeconds(config.getTimeoutSeconds()))
            .GET()
            .build();

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

    private <T> CompletableFuture<T> post(String path, Object body, Class<T> responseType) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(config.getEndpoint() + path))
            .header("Authorization", "Bearer " + config.getApiKey())
            .header("Content-Type", "application/json")
            .timeout(Duration.ofSeconds(config.getTimeoutSeconds()))
            .POST(HttpRequest.BodyPublishers.ofString(gson.toJson(body)))
            .build();

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

    private CompletableFuture<Void> delete(String path) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(config.getEndpoint() + path))
            .header("Authorization", "Bearer " + config.getApiKey())
            .timeout(Duration.ofSeconds(config.getTimeoutSeconds()))
            .DELETE()
            .build();

        return httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding())
            .thenApply(response -> {
                if (response.statusCode() >= 400) {
                    throw new StorageException("Delete failed", response.statusCode(), null);
                }
                return null;
            });
    }

    private <T> T handleResponse(HttpResponse<String> response, Class<T> responseType) {
        if (response.statusCode() >= 400) {
            ErrorResponse error = gson.fromJson(response.body(), ErrorResponse.class);
            throw new StorageException(
                error != null ? error.getMessage() : "Unknown error",
                response.statusCode(),
                error != null ? error.getCode() : null
            );
        }
        return gson.fromJson(response.body(), responseType);
    }

    private record ListDirectoryResponse(List<DirectoryEntry> entries) { List<DirectoryEntry> getEntries() { return entries; } }
    private record ErrorResponse(String message, String code) { String getMessage() { return message; } String getCode() { return code; } }
}
