package io.synor.database;

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

import java.io.IOException;
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 Database SDK for Java.
 * Multi-model database with Key-Value, Document, Vector, and Time Series stores.
 */
public class SynorDatabase implements AutoCloseable {
    private static final String DEFAULT_ENDPOINT = "https://db.synor.io/v1";
    private static final Gson gson = new GsonBuilder().create();

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

    // Sub-stores
    public final KeyValueStore kv;
    public final DocumentStore documents;
    public final VectorStore vectors;
    public final TimeSeriesStore timeseries;

    public SynorDatabase(String apiKey) {
        this(new DatabaseConfig(apiKey, DEFAULT_ENDPOINT, 60, 3, false));
    }

    public SynorDatabase(DatabaseConfig config) {
        this.config = config;
        this.httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(config.getTimeoutSecs()))
                .build();

        this.kv = new KeyValueStore(this);
        this.documents = new DocumentStore(this);
        this.vectors = new VectorStore(this);
        this.timeseries = new TimeSeriesStore(this);
    }

    // ==================== Key-Value Store ====================

    public static class KeyValueStore {
        private final SynorDatabase db;

        KeyValueStore(SynorDatabase db) {
            this.db = db;
        }

        public CompletableFuture<Object> get(String key) {
            return db.request("GET", "/kv/" + encode(key), null)
                    .thenApply(resp -> {
                        Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
                        return map.get("value");
                    });
        }

        public CompletableFuture<Void> set(String key, Object value, Integer ttl) {
            Map<String, Object> body = new HashMap<>();
            body.put("key", key);
            body.put("value", value);
            if (ttl != null) body.put("ttl", ttl);

            return db.request("PUT", "/kv/" + encode(key), body)
                    .thenApply(resp -> null);
        }

        public CompletableFuture<Void> delete(String key) {
            return db.request("DELETE", "/kv/" + encode(key), null)
                    .thenApply(resp -> null);
        }

        public CompletableFuture<List<KeyValue>> list(String prefix) {
            String path = "/kv?prefix=" + encode(prefix);
            return db.request("GET", path, null)
                    .thenApply(resp -> {
                        Type type = new TypeToken<ListResponse<KeyValue>>(){}.getType();
                        ListResponse<KeyValue> result = gson.fromJson(resp, type);
                        return result.items;
                    });
        }
    }

    // ==================== Document Store ====================

    public static class DocumentStore {
        private final SynorDatabase db;

        DocumentStore(SynorDatabase db) {
            this.db = db;
        }

        public CompletableFuture<String> create(String collection, Map<String, Object> document) {
            return db.request("POST", "/collections/" + encode(collection) + "/documents", document)
                    .thenApply(resp -> {
                        Map<String, Object> map = gson.fromJson(resp, new TypeToken<Map<String, Object>>(){}.getType());
                        return (String) map.get("id");
                    });
        }

        public CompletableFuture<Document> get(String collection, String id) {
            return db.request("GET", "/collections/" + encode(collection) + "/documents/" + encode(id), null)
                    .thenApply(resp -> gson.fromJson(resp, Document.class));
        }

        public CompletableFuture<Void> update(String collection, String id, Map<String, Object> update) {
            return db.request("PATCH", "/collections/" + encode(collection) + "/documents/" + encode(id), update)
                    .thenApply(resp -> null);
        }

        public CompletableFuture<Void> delete(String collection, String id) {
            return db.request("DELETE", "/collections/" + encode(collection) + "/documents/" + encode(id), null)
                    .thenApply(resp -> null);
        }

        public CompletableFuture<List<Document>> query(String collection, Query query) {
            return db.request("POST", "/collections/" + encode(collection) + "/query", query)
                    .thenApply(resp -> {
                        Type type = new TypeToken<ListResponse<Document>>(){}.getType();
                        ListResponse<Document> result = gson.fromJson(resp, type);
                        return result.documents;
                    });
        }
    }

    // ==================== Vector Store ====================

    public static class VectorStore {
        private final SynorDatabase db;

        VectorStore(SynorDatabase db) {
            this.db = db;
        }

        public CompletableFuture<Void> upsert(String collection, List<VectorEntry> vectors) {
            Map<String, Object> body = new HashMap<>();
            body.put("vectors", vectors);

            return db.request("POST", "/vectors/" + encode(collection) + "/upsert", body)
                    .thenApply(resp -> null);
        }

        public CompletableFuture<List<SearchResult>> search(String collection, double[] vector, int k) {
            Map<String, Object> body = new HashMap<>();
            body.put("vector", vector);
            body.put("k", k);

            return db.request("POST", "/vectors/" + encode(collection) + "/search", body)
                    .thenApply(resp -> {
                        Type type = new TypeToken<ListResponse<SearchResult>>(){}.getType();
                        ListResponse<SearchResult> result = gson.fromJson(resp, type);
                        return result.results;
                    });
        }

        public CompletableFuture<Void> delete(String collection, List<String> ids) {
            Map<String, Object> body = new HashMap<>();
            body.put("ids", ids);

            return db.request("DELETE", "/vectors/" + encode(collection), body)
                    .thenApply(resp -> null);
        }
    }

    // ==================== Time Series Store ====================

    public static class TimeSeriesStore {
        private final SynorDatabase db;

        TimeSeriesStore(SynorDatabase db) {
            this.db = db;
        }

        public CompletableFuture<Void> write(String series, List<DataPoint> points) {
            Map<String, Object> body = new HashMap<>();
            body.put("points", points);

            return db.request("POST", "/timeseries/" + encode(series) + "/write", body)
                    .thenApply(resp -> null);
        }

        public CompletableFuture<List<DataPoint>> query(String series, TimeRange range, Aggregation aggregation) {
            Map<String, Object> body = new HashMap<>();
            body.put("range", range);
            if (aggregation != null) body.put("aggregation", aggregation);

            return db.request("POST", "/timeseries/" + encode(series) + "/query", body)
                    .thenApply(resp -> {
                        Type type = new TypeToken<ListResponse<DataPoint>>(){}.getType();
                        ListResponse<DataPoint> result = gson.fromJson(resp, type);
                        return result.points;
                    });
        }
    }

    // ==================== 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 DatabaseException("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 DatabaseException(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 Types ====================

    private static class ListResponse<T> {
        List<T> items;
        List<T> documents;
        List<T> results;
        List<T> points;
    }
}
