package com.mosquito.project.sdk;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.mosquito.project.dto.ApiResponse;

class ApiClient {

    private final String baseUrl;
    private final String apiKey;
    private final HttpClient httpClient;
    private final ObjectMapper objectMapper;

    ApiClient(String baseUrl, String apiKey) {
        this.baseUrl = baseUrl;
        this.apiKey = apiKey;
        this.httpClient = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_1_1)
            .build();
        this.objectMapper = new ObjectMapper();
        this.objectMapper.registerModule(new JavaTimeModule());
    }

    <T> T get(String path, Class<T> responseType) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + path))
            .header("X-API-Key", apiKey)
            .GET()
            .build();

        return execute(request, objectMapper.getTypeFactory().constructType(responseType));
    }

    String getString(String path) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + path))
            .header("X-API-Key", apiKey)
            .GET()
            .build();

        try {
            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
            return response.body();
        } catch (IOException | InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Failed to GET " + path, e);
        }
    }

    byte[] getBytes(String path) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + path))
            .header("X-API-Key", apiKey)
            .GET()
            .build();

        try {
            HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());
            return response.body();
        } catch (IOException | InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Failed to GET bytes from " + path, e);
        }
    }

    <T> T post(String path, Map<String, Object> body, Class<T> responseType) {
        try {
            String jsonBody = objectMapper.writeValueAsString(body);

            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl + path))
                .header("X-API-Key", apiKey)
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
                .build();

            return execute(request, objectMapper.getTypeFactory().constructType(responseType));
        } catch (Exception e) {
            throw new RuntimeException("Failed to POST to " + path, e);
        }
    }

    <T> T put(String path, Map<String, Object> body, Class<T> responseType) {
        try {
            String jsonBody = objectMapper.writeValueAsString(body);

            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl + path))
                .header("X-API-Key", apiKey)
                .header("Content-Type", "application/json")
                .PUT(HttpRequest.BodyPublishers.ofString(jsonBody))
                .build();

            return execute(request, objectMapper.getTypeFactory().constructType(responseType));
        } catch (Exception e) {
            throw new RuntimeException("Failed to PUT to " + path, e);
        }
    }

    void delete(String path) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + path))
            .header("X-API-Key", apiKey)
            .DELETE()
            .build();

        try {
            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() < 200 || response.statusCode() >= 300) {
                throw new RuntimeException("API call failed with status " + response.statusCode() + ": " + response.body());
            }
        } catch (Exception e) {
            throw new RuntimeException("Failed to DELETE " + path, e);
        }
    }

    @SuppressWarnings("unchecked")
    <T> List<T> getList(String path, Class<T> elementType) {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + path))
            .header("X-API-Key", apiKey)
            .GET()
            .build();

        try {
            JavaType listType = objectMapper.getTypeFactory().constructCollectionType(List.class, elementType);
            List<T> result = execute(request, listType);
            return result == null ? new ArrayList<>() : result;
        } catch (Exception e) {
            throw new RuntimeException("Failed to parse list response from " + path, e);
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T execute(HttpRequest request, JavaType dataType) {
        try {
            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() >= 200 && response.statusCode() < 300) {
                return (T) unwrap(response.body(), dataType);
            } else {
                throw new RuntimeException("API call failed with status " + response.statusCode() + ": " + response.body());
            }
        } catch (IOException | InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("API call failed", e);
        }
    }

    private <T> T unwrap(String body, JavaType dataType) throws IOException {
        if (body == null || body.isBlank()) {
            return null;
        }
        JavaType envelopeType = objectMapper.getTypeFactory()
            .constructParametricType(ApiResponse.class, dataType);
        ApiResponse<T> response = objectMapper.readValue(body, envelopeType);
        if (response == null) {
            return null;
        }
        if (response.getCode() >= 400) {
            throw new RuntimeException("API call failed with code " + response.getCode() + ": " + response.getMessage());
        }
        return response.getData();
    }
}
