package at.procon.eventhub.tachographfilesession.service;

import at.procon.eventhub.config.EventHubProperties;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.CookieManager;
import java.net.CookiePolicy;
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.time.Duration;
import java.util.Base64;
import org.springframework.stereotype.Component;

@Component
public class LegalRequirementsClient {

    private final EventHubProperties properties;
    private final ObjectMapper objectMapper;

    public LegalRequirementsClient(EventHubProperties properties, ObjectMapper objectMapper) {
        this.properties = properties;
        this.objectMapper = objectMapper;
    }

    public LegalRequirementsUploadResult uploadTachographFile(byte[] fileBytes, String fileName) {
        EventHubProperties.LegalRequirements config = properties.getTachographFileSession().getLegalRequirements();
        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(config.getConnectTimeout())
                .cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ALL))
                .build();
        try {
            String dataPackageId = uploadDataPackage(client, config, fileBytes, fileName);
            String xml = downloadXml(client, config, dataPackageId);
            return new LegalRequirementsUploadResult(dataPackageId, xml);
        } finally {
            if (config.isResetSessionAfterUse()) {
                resetSessionQuietly(client, config);
            }
        }
    }

    private String uploadDataPackage(HttpClient client, EventHubProperties.LegalRequirements config, byte[] fileBytes, String fileName) {
        try {
            String payload = objectMapper.createObjectNode()
                    .put("FileName", fileName)
                    .putNull("CompressionMediaType")
                    .put("Data", Base64.getEncoder().encodeToString(fileBytes))
                    .toString();
            HttpRequest request = requestBuilder(config.getBaseUrl() + "/DataPackages/Upload", config.getReadTimeout())
                    .header("Content-Type", "application/json")
                    .header("Authorization", basicAuth(config))
                    .POST(HttpRequest.BodyPublishers.ofString(payload, StandardCharsets.UTF_8))
                    .build();
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
            if (response.statusCode() / 100 != 2) {
                throw new LegalRequirementsUploadException("LegalRequirements upload failed with status " + response.statusCode());
            }
            JsonNode root = objectMapper.readTree(response.body());
            String dataPackageId = text(root, "ID");
            if (dataPackageId == null) {
                dataPackageId = text(root, "Id");
            }
            if (dataPackageId == null && root.has("value") && root.get("value").isObject()) {
                dataPackageId = text(root.get("value"), "DataPackageID");
            }
            if (dataPackageId == null) {
                throw new LegalRequirementsUploadException("LegalRequirements upload response did not contain DataPackageID.");
            }
            return dataPackageId;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new LegalRequirementsUploadException("Failed to upload tachograph file to LegalRequirements.", e);
        } catch (IOException e) {
            throw new LegalRequirementsUploadException("Failed to upload tachograph file to LegalRequirements.", e);
        }
    }

    private String downloadXml(HttpClient client, EventHubProperties.LegalRequirements config, String dataPackageId) {
        try {
            HttpRequest request = requestBuilder(config.getBaseUrl() + "/DataPackages/" + dataPackageId + "/Xml", config.getReadTimeout())
                    .header("Authorization", basicAuth(config))
                    .GET()
                    .build();
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
            if (response.statusCode() / 100 != 2) {
                throw new LegalRequirementsXmlDownloadException("LegalRequirements XML download failed with status " + response.statusCode());
            }
            return response.body();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new LegalRequirementsXmlDownloadException("Failed to download processed tachograph XML.", e);
        } catch (IOException e) {
            throw new LegalRequirementsXmlDownloadException("Failed to download processed tachograph XML.", e);
        }
    }

    private void resetSessionQuietly(HttpClient client, EventHubProperties.LegalRequirements config) {
        try {
            HttpRequest request = requestBuilder(config.getBaseUrl() + "/LegalRequirementsResults/ResetSession", config.getReadTimeout())
                    .header("Authorization", basicAuth(config))
                    .POST(HttpRequest.BodyPublishers.noBody())
                    .build();
            client.send(request, HttpResponse.BodyHandlers.discarding());
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
        } catch (IOException ignored) {
        }
    }

    private HttpRequest.Builder requestBuilder(String url, Duration timeout) {
        return HttpRequest.newBuilder(URI.create(url)).timeout(timeout);
    }

    private String basicAuth(EventHubProperties.LegalRequirements config) {
        String user = config.getUsername() == null ? "" : config.getUsername();
        String password = config.getPassword() == null ? "" : config.getPassword();
        String token = Base64.getEncoder().encodeToString((user + ":" + password).getBytes(StandardCharsets.UTF_8));
        return "Basic " + token;
    }

    private String text(JsonNode node, String field) {
        if (node == null || !node.has(field) || node.get(field).isNull()) {
            return null;
        }
        return node.get(field).asText();
    }
}
