package de.assecutor.votianlt.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientResponseException;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;

/**
 * Client for the external PDF Tool (ZUGFeRD converter). Sends an invoice PDF
 * together with the structured invoice data to {@code POST /api/invoices/sign}
 * and receives a ZIP file containing the signed e-invoice (PDF/A-3 with
 * embedded EN16931 XML), the factur-x.xml and the Mustang validation report.
 */
@Service
@Slf4j
public class PdfToolService {

    /** Response header of the PDF Tool with the Mustang validation result. */
    private static final String VALIDATION_HEADER = "X-Zugferd-Valid";

    private final RestClient restClient;
    private final ObjectMapper objectMapper;

    public PdfToolService(@Value("${app.pdftool.base-url:http://localhost:8083}") String baseUrl,
            ObjectMapper objectMapper) {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(5000);
        // PDF/A conversion and veraPDF validation can take a while
        requestFactory.setReadTimeout(120000);
        this.restClient = RestClient.builder().baseUrl(baseUrl).requestFactory(requestFactory).build();
        this.objectMapper = objectMapper;
    }

    /** Party (sender or recipient) of the invoice metadata. */
    public record Party(String name, String street, String zip, String city, String countryCode, String vatId,
            String email, String phone) {
    }

    /** One invoice line of the invoice metadata. */
    public record LineItem(String description, BigDecimal quantity, BigDecimal unitPriceNet, BigDecimal vatPercent) {
    }

    /** Invoice metadata as expected by the PDF Tool (EN16931 fields). */
    public record InvoiceMetadata(String invoiceNumber, LocalDate issueDate, LocalDate deliveryDate, LocalDate dueDate,
            String currency, String paymentTerms, String buyerReference, String iban, String bic, Party sender,
            Party recipient, List<LineItem> items) {
    }

    /** Result of a sign call: the ZIP, its file name and the validation flag. */
    public record SignedInvoice(byte[] zip, String fileName, boolean valid) {
    }

    /**
     * Converts the given invoice PDF into a ZUGFeRD e-invoice. Returns the ZIP
     * provided by the PDF Tool; throws with the tool's error message if the
     * conversion is rejected (e.g. incomplete metadata). A failed Mustang
     * validation is reported by the tool as HTTP 422 but still carries the ZIP
     * (including the validation report) in the body; it is returned as a
     * {@link SignedInvoice} with {@code valid=false} instead of throwing.
     */
    public SignedInvoice signInvoice(byte[] pdfBytes, InvoiceMetadata metadata) throws Exception {
        String metadataJson = objectMapper.writeValueAsString(metadata);

        MultipartBodyBuilder body = new MultipartBodyBuilder();
        body.part("file", new ByteArrayResource(pdfBytes) {
            @Override
            public String getFilename() {
                return metadata.invoiceNumber() + ".pdf";
            }
        }, MediaType.APPLICATION_PDF);
        body.part("metadata", metadataJson, MediaType.APPLICATION_JSON);

        try {
            ResponseEntity<byte[]> response = restClient.post().uri("/api/invoices/sign")
                    .contentType(MediaType.MULTIPART_FORM_DATA).body(body.build()).retrieve()
                    .toEntity(byte[].class);
            return toSignedInvoice(response.getBody(), response.getHeaders(), metadata);
        } catch (RestClientResponseException ex) {
            if (ex.getStatusCode().value() == 422 && ex.getResponseBodyAsByteArray().length > 0) {
                return toSignedInvoice(ex.getResponseBodyAsByteArray(), ex.getResponseHeaders(), metadata);
            }
            throw new IllegalStateException(extractErrorMessage(ex), ex);
        }
    }

    /** Builds the result from the response ZIP, file name and validation header. */
    private SignedInvoice toSignedInvoice(byte[] zip, org.springframework.http.HttpHeaders headers,
            InvoiceMetadata metadata) {
        if (zip == null || zip.length == 0) {
            throw new IllegalStateException("PDF Tool returned an empty response");
        }
        String fileName = headers != null ? headers.getContentDisposition().getFilename() : null;
        if (fileName == null || fileName.isBlank()) {
            fileName = metadata.invoiceNumber() + "-zugferd.zip";
        }
        boolean valid = headers != null && Boolean.parseBoolean(headers.getFirst(VALIDATION_HEADER));
        return new SignedInvoice(zip, fileName, valid);
    }

    /** The PDF Tool reports errors as {@code {"error": "..."}} with HTTP 400. */
    private String extractErrorMessage(RestClientResponseException ex) {
        try {
            JsonNode node = objectMapper.readTree(ex.getResponseBodyAsString());
            if (node.hasNonNull("error")) {
                return node.get("error").asText();
            }
        } catch (Exception ignored) {
            // fall through to the generic message
        }
        return ex.getStatusText() + " (" + ex.getStatusCode().value() + ")";
    }
}
