package org.example.petshopdesktop.api;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.example.petshopdesktop.auth.UserSession;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.SequenceInputStream;
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.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.UUID;

public class ApiClient {
    private static final ApiClient INSTANCE = new ApiClient();
    private final HttpClient httpClient;
    private final ObjectMapper objectMapper;
    private final String baseUrl;

    private ApiClient() {
        this.httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .build();
        this.objectMapper = new ObjectMapper();
        this.objectMapper.registerModule(new JavaTimeModule());
        this.objectMapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        this.baseUrl = ApiConfig.getInstance().getBaseUrl();
    }

    public static ApiClient getInstance() {
        return INSTANCE;
    }

    public <T> T get(String path, Class<T> responseClass) throws Exception {
        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl + path))
                .GET()
                .timeout(Duration.ofSeconds(30));

        addAuthHeader(builder);

        HttpRequest request = builder.build();
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

        return handleResponse(response, responseClass);
    }

    public byte[] getBytes(String path) throws Exception {
        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl + path))
                .GET()
                .timeout(Duration.ofSeconds(30));

        addAuthHeader(builder);

        HttpRequest request = builder.build();
        HttpResponse<byte[]> response = httpClient.send(request, HttpResponse.BodyHandlers.ofByteArray());

        int statusCode = response.statusCode();
        if (statusCode == 200 || statusCode == 201) {
            return response.body();
        } else if (statusCode == 401) {
            throw new RuntimeException("Authentication failed. Please log in again.");
        } else if (statusCode == 403) {
            throw new RuntimeException("Access restricted. You don't have permission to perform this action.");
        } else if (statusCode == 404) {
            throw new RuntimeException("File not found.");
        } else {
            throw new RuntimeException("Request failed with status " + statusCode);
        }
    }

    public String getRawResponse(String path) throws Exception {
        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl + path))
                .GET()
                .timeout(Duration.ofSeconds(30));

        addAuthHeader(builder);

        HttpRequest request = builder.build();
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() == 200 || response.statusCode() == 201) {
            return response.body();
        } else if (response.statusCode() == 401) {
            throw new RuntimeException("Authentication failed. Please log in again.");
        } else if (response.statusCode() == 403) {
            throw new RuntimeException("Access restricted. You don't have permission to perform this action.");
        } else {
            throw new RuntimeException(parseErrorMessage(response));
        }
    }

    public <T> T post(String path, Object requestBody, Class<T> responseClass) throws Exception {
        String jsonBody = objectMapper.writeValueAsString(requestBody);

        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl + path))
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
                .timeout(Duration.ofSeconds(30));

        addAuthHeader(builder);

        HttpRequest request = builder.build();
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

        return handleResponse(response, responseClass);
    }

    public <T> T postMultipart(String path, String partName, Path filePath, Class<T> responseClass) throws Exception {
        String boundary = "----PetShopDesktop" + UUID.randomUUID();
        String mimeType = Files.probeContentType(filePath);
        if (mimeType == null || mimeType.isBlank()) {
            mimeType = "application/octet-stream";
        }

        byte[] fileBytes = Files.readAllBytes(filePath);
        String fileName = filePath.getFileName().toString();
        byte[] prefix = ("--" + boundary + "\r\n"
            + "Content-Disposition: form-data; name=\"" + partName + "\"; filename=\"" + fileName + "\"\r\n"
            + "Content-Type: " + mimeType + "\r\n\r\n").getBytes(StandardCharsets.UTF_8);
        byte[] suffix = ("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8);
        byte[] body = new byte[prefix.length + fileBytes.length + suffix.length];
        System.arraycopy(prefix, 0, body, 0, prefix.length);
        System.arraycopy(fileBytes, 0, body, prefix.length, fileBytes.length);
        System.arraycopy(suffix, 0, body, prefix.length + fileBytes.length, suffix.length);

        HttpRequest.Builder builder = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + path))
            .header("Content-Type", "multipart/form-data; boundary=" + boundary)
            .POST(HttpRequest.BodyPublishers.ofByteArray(body))
            .timeout(Duration.ofSeconds(30));

        addAuthHeader(builder);

        HttpResponse<String> response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());
        return handleResponse(response, responseClass);
    }

    public <T> T postMultipartWithText(String path, String filePartName, Path filePath,
                                        String textPartName, String textContent,
                                        Class<T> responseClass) throws Exception {
        String boundary = "----PetShopDesktop" + UUID.randomUUID();
        String mimeType = Files.probeContentType(filePath);
        if (mimeType == null || mimeType.isBlank()) mimeType = "application/octet-stream";

        String fileName = filePath.getFileName().toString();

        byte[] fileHeader = ("--" + boundary + "\r\nContent-Disposition: form-data; name=\"" + filePartName
                + "\"; filename=\"" + fileName + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n")
                .getBytes(StandardCharsets.UTF_8);

        ByteArrayOutputStream tail = new ByteArrayOutputStream();
        tail.write("\r\n".getBytes(StandardCharsets.UTF_8));
        if (textContent != null && !textContent.isBlank()) {
            tail.write(("--" + boundary + "\r\nContent-Disposition: form-data; name=\"" + textPartName
                    + "\"\r\n\r\n" + textContent + "\r\n").getBytes(StandardCharsets.UTF_8));
        }
        tail.write(("--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8));

        InputStream body = new SequenceInputStream(
                java.util.Collections.enumeration(java.util.Arrays.asList(
                        new ByteArrayInputStream(fileHeader),
                        Files.newInputStream(filePath),
                        new ByteArrayInputStream(tail.toByteArray())
                ))
        );

        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl + path))
                .header("Content-Type", "multipart/form-data; boundary=" + boundary)
                .POST(HttpRequest.BodyPublishers.ofInputStream(() -> body))
                .timeout(Duration.ofSeconds(120));
        addAuthHeader(builder);
        HttpResponse<String> response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());
        return handleResponse(response, responseClass);
    }

    public <T> T put(String path, Object requestBody, Class<T> responseClass) throws Exception {
        String jsonBody = objectMapper.writeValueAsString(requestBody);

        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl + path))
                .header("Content-Type", "application/json")
                .PUT(HttpRequest.BodyPublishers.ofString(jsonBody))
                .timeout(Duration.ofSeconds(30));

        addAuthHeader(builder);

        HttpRequest request = builder.build();
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

        return handleResponse(response, responseClass);
    }

    public void delete(String path) throws Exception {
        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl + path))
                .DELETE()
                .timeout(Duration.ofSeconds(30));

        addAuthHeader(builder);

        HttpRequest request = builder.build();
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() != 204 && response.statusCode() != 200) {
            throw new RuntimeException(parseErrorMessage(response));
        }
    }

    public void deleteWithBody(String path, Object requestBody) throws Exception {
        String jsonBody = objectMapper.writeValueAsString(requestBody);

        HttpRequest.Builder builder = HttpRequest.newBuilder()
                .uri(URI.create(baseUrl + path))
                .header("Content-Type", "application/json")
                .method("DELETE", HttpRequest.BodyPublishers.ofString(jsonBody))
                .timeout(Duration.ofSeconds(30));

        addAuthHeader(builder);

        HttpRequest request = builder.build();
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() != 204 && response.statusCode() != 200) {
            throw new RuntimeException(parseErrorMessage(response));
        }
    }

    private void addAuthHeader(HttpRequest.Builder builder) {
        String token = UserSession.getInstance().getJwtToken();
        if (token != null && !token.isEmpty()) {
            builder.header("Authorization", "Bearer " + token);
        }
    }

    private <T> T handleResponse(HttpResponse<String> response, Class<T> responseClass) throws Exception {
        int statusCode = response.statusCode();

        if (statusCode == 200 || statusCode == 201) {
            if (response.body() == null || response.body().isEmpty()) {
                return null;
            }
            return objectMapper.readValue(response.body(), responseClass);
        } else if (statusCode == 204) {
            return null;
        } else if (statusCode == 401) {
            throw new RuntimeException("Authentication failed. Please log in again.");
        } else if (statusCode == 403) {
            throw new RuntimeException("Access restricted. You don't have permission to perform this action.");
        } else {
            throw new RuntimeException(parseErrorMessage(response));
        }
    }

    private String parseErrorMessage(HttpResponse<String> response) {
        try {
            if (response.body() != null && !response.body().isEmpty()) {
                var errorNode = objectMapper.readTree(response.body());
                if (errorNode.has("errors")) {
                    StringBuilder sb = new StringBuilder();
                    errorNode.get("errors").fields().forEachRemaining(entry -> {
                        String errorText = entry.getValue().asText();
                        if (errorText != null && !errorText.isBlank()) {
                            sb.append(errorText).append("\n");
                        }
                    });
                    if (sb.length() > 0) {
                        String message = errorNode.has("message") ? errorNode.get("message").asText() : null;
                        return (message != null && !message.isBlank() ? message + "\n" : "") + sb.toString().trim();
                    }
                }
                if (errorNode.has("message")) {
                    return errorNode.get("message").asText();
                }
            }
        } catch (Exception e) {
            System.err.println("Error parsing error message: " + e.getMessage());
        }
        return "Request failed with status " + response.statusCode();
    }

    public ObjectMapper getObjectMapper() {
        return objectMapper;
    }
}
