package com.cos.service;

import com.cos.entity.CertificateOfSponsorship;
import org.apache.fop.apps.*;
import org.apache.xmlgraphics.util.MimeConstants;

import javax.enterprise.context.ApplicationScoped;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.net.URL;
import java.time.format.DateTimeFormatter;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Service responsible for generating PDF documents from CoS data
 * using Apache FOP 2.3 and XSL-FO transformation.
 */
@ApplicationScoped
public class PdfGenerationService {

    private static final Logger LOGGER = Logger.getLogger(PdfGenerationService.class.getName());
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd MMMM yyyy");
    private static final DateTimeFormatter DATE_FORMATTER_SHORT = DateTimeFormatter.ofPattern("dd/MM/yyyy");

    /**
     * Generate a PDF byte array for the given CoS entity.
     *
     * @param cos the CertificateOfSponsorship entity
     * @return byte array of the generated PDF
     * @throws Exception if PDF generation fails
     */
    public byte[] generatePdf(CertificateOfSponsorship cos) throws Exception {
        // 1. Build the XML data document from the entity
        String xmlData = buildXmlData(cos);

        // 2. Load the XSL-FO stylesheet from classpath
        URL xslUrl = getClass().getClassLoader().getResource("xsl/cos-fo.xsl");
        if (xslUrl == null) {
            throw new FileNotFoundException("XSL-FO stylesheet not found: xsl/cos-fo.xsl");
        }

        // 3. Configure FOP
        FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
        FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
        foUserAgent.setProducer("Certificate of Sponsorship System");
        foUserAgent.setCreator("UK Visas & Immigration - CoS Application");
        foUserAgent.setAuthor("CoS System");

        // 4. Generate PDF into a ByteArrayOutputStream
        ByteArrayOutputStream pdfOut = new ByteArrayOutputStream();

        try {
            Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, pdfOut);

            // 5. Setup XSLT transformer
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer(
                    new StreamSource(xslUrl.openStream()));

            // 6. Set transformation parameters
            transformer.setParameter("documentTitle", "Certificate of Sponsorship");

            // 7. Transform XML → XSL-FO → PDF
            Source xmlSource = new StreamSource(new StringReader(xmlData));
            Result result = new SAXResult(fop.getDefaultHandler());
            transformer.transform(xmlSource, result);

        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Error generating PDF for CoS: " + cos.getCertificateNumber(), e);
            throw e;
        }

        return pdfOut.toByteArray();
    }

    /**
     * Build an XML string representation of the CoS entity for XSL-FO transformation.
     */
    private String buildXmlData(CertificateOfSponsorship cos) {
        StringBuilder xml = new StringBuilder();
        xml.append("<?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?>\n");
        xml.append("<certificateOfSponsorship>\n");

        // Tier and Category
        xml.append("  <tierAndCategory>\n");
        xml.append("    <value>").append(escapeXml(cos.getTierAndCategory())).append("</value>\n");
        xml.append("  </tierAndCategory>\n");

        // Certificate Status
        xml.append("  <certStatus>\n");
        xml.append("    <sponsorLicenceNumber>").append(escapeXml(cos.getSponsorLicenceNumber())).append("</sponsorLicenceNumber>\n");
        xml.append("    <sponsorName>").append(escapeXml(cos.getSponsorName())).append("</sponsorName>\n");
        xml.append("    <certificateNumber>").append(escapeXml(cos.getCertificateNumber())).append("</certificateNumber>\n");
        xml.append("    <currentCertStatus>").append(escapeXml(cos.getCurrentCertStatus())).append("</currentCertStatus>\n");
        xml.append("    <currentCertStatusDate>").append(formatDate(cos.getCurrentCertStatusDate())).append("</currentCertStatusDate>\n");
        xml.append("    <dateAssigned>").append(formatDate(cos.getDateAssigned())).append("</dateAssigned>\n");
        xml.append("    <expiryDateUseBy>").append(formatDate(cos.getExpiryDateUseBy())).append("</expiryDateUseBy>\n");
        xml.append("    <sponsorshipWithdrawn>").append(escapeXml(cos.getSponsorshipWithdrawn())).append("</sponsorshipWithdrawn>\n");
        xml.append("    <sponsorNote>").append(escapeXml(cos.getSponsorNote())).append("</sponsorNote>\n");
        xml.append("    <migrantApplicationStatus>").append(escapeXml(cos.getMigrantApplicationStatus())).append("</migrantApplicationStatus>\n");
        xml.append("  </certStatus>\n");

        // Personal Information
        xml.append("  <personalInfo>\n");
        xml.append("    <familyName>").append(escapeXml(cos.getFamilyName())).append("</familyName>\n");
        xml.append("    <givenNames>").append(escapeXml(cos.getGivenNames())).append("</givenNames>\n");
        xml.append("    <otherNames>").append(escapeXml(cos.getOtherNames())).append("</otherNames>\n");
        xml.append("    <nationality>").append(escapeXml(cos.getNationality())).append("</nationality>\n");
        xml.append("    <placeOfBirth>").append(escapeXml(cos.getPlaceOfBirth())).append("</placeOfBirth>\n");
        xml.append("    <countryOfBirth>").append(escapeXml(cos.getCountryOfBirth())).append("</countryOfBirth>\n");
        xml.append("    <dateOfBirth>").append(formatDateShort(cos.getDateOfBirth())).append("</dateOfBirth>\n");
        xml.append("    <gender>").append(escapeXml(cos.getGender())).append("</gender>\n");
        xml.append("    <countryOfResidence>").append(escapeXml(cos.getCountryOfResidence())).append("</countryOfResidence>\n");
        xml.append("  </personalInfo>\n");

        // Passport
        xml.append("  <passport>\n");
        xml.append("    <passportNumber>").append(escapeXml(cos.getPassportNumber())).append("</passportNumber>\n");
        xml.append("    <issueDate>").append(formatDate(cos.getPassportIssueDate())).append("</issueDate>\n");
        xml.append("    <expiryDate>").append(formatDate(cos.getPassportExpiryDate())).append("</expiryDate>\n");
        xml.append("    <placeOfIssue>").append(escapeXml(cos.getPlaceOfIssuePassport())).append("</placeOfIssue>\n");
        xml.append("  </passport>\n");

        // Home Address
        xml.append("  <homeAddress>\n");
        xml.append("    <address>").append(escapeXml(cos.getHomeAddress())).append("</address>\n");
        xml.append("    <cityTown>").append(escapeXml(cos.getHomeCityTown())).append("</cityTown>\n");
        xml.append("    <countyArea>").append(escapeXml(cos.getHomeCountyArea())).append("</countyArea>\n");
        xml.append("    <postcode>").append(escapeXml(cos.getHomePostcode())).append("</postcode>\n");
        xml.append("    <country>").append(escapeXml(cos.getHomeCountry())).append("</country>\n");
        xml.append("  </homeAddress>\n");

        // Identification Numbers
        xml.append("  <identification>\n");
        xml.append("    <ukIdCardNumber>").append(escapeXml(cos.getUkIdCardNumber())).append("</ukIdCardNumber>\n");
        xml.append("    <ukNationalInsuranceNumber>").append(escapeXml(cos.getUkNationalInsuranceNumber())).append("</ukNationalInsuranceNumber>\n");
        xml.append("    <nationalIdCardNumber>").append(escapeXml(cos.getNationalIdCardNumber())).append("</nationalIdCardNumber>\n");
        xml.append("    <employeeNumber>").append(escapeXml(cos.getEmployeeNumber())).append("</employeeNumber>\n");
        xml.append("  </identification>\n");

        // Work Dates
        xml.append("  <workDates>\n");
        xml.append("    <startDate>").append(formatDate(cos.getWorkStartDate())).append("</startDate>\n");
        xml.append("    <endDate>").append(formatDate(cos.getWorkEndDate())).append("</endDate>\n");
        xml.append("    <leaveReenterUk>").append(escapeXml(cos.getLeaveReenterUk())).append("</leaveReenterUk>\n");
        xml.append("    <totalWeeklyHours>").append(cos.getTotalWeeklyHours() != null ? cos.getTotalWeeklyHours() : "").append("</totalWeeklyHours>\n");
        xml.append("  </workDates>\n");

        // Main Work Address
        xml.append("  <workAddress>\n");
        xml.append("    <address>").append(escapeXml(cos.getWorkAddress())).append("</address>\n");
        xml.append("    <cityTown>").append(escapeXml(cos.getWorkCityTown())).append("</cityTown>\n");
        xml.append("    <countyArea>").append(escapeXml(cos.getWorkCountyArea())).append("</countyArea>\n");
        xml.append("    <postcode>").append(escapeXml(cos.getWorkPostcode())).append("</postcode>\n");
        xml.append("  </workAddress>\n");

        // Employment
        xml.append("  <employment>\n");
        xml.append("    <jobTitle>").append(escapeXml(cos.getJobTitle())).append("</jobTitle>\n");
        xml.append("    <jobType>").append(escapeXml(cos.getJobType())).append("</jobType>\n");
        xml.append("    <jobDescription>").append(escapeXml(cos.getJobDescription())).append("</jobDescription>\n");
        xml.append("    <newEntrant>").append(escapeXml(cos.getNewEntrant())).append("</newEntrant>\n");
        xml.append("    <grossSalary>").append(cos.getGrossSalary() != null ? String.format("%.2f", cos.getGrossSalary()) : "").append("</grossSalary>\n");
        xml.append("    <salaryPeriod>").append(escapeXml(cos.getSalaryPeriod())).append("</salaryPeriod>\n");
        xml.append("    <appropriateSkillLevel>").append(escapeXml(cos.getAppropriateSkillLevel())).append("</appropriateSkillLevel>\n");
        xml.append("    <certifyMaintenance>").append(escapeXml(cos.getCertifyMaintenance())).append("</certifyMaintenance>\n");
        xml.append("    <atasRequired>").append(escapeXml(cos.getAtasRequired())).append("</atasRequired>\n");
        xml.append("  </employment>\n");

        // PAYE
        xml.append("  <paye>\n");
        xml.append("    <payeReferenceSupplied>").append(escapeXml(cos.getPayeReferenceSupplied())).append("</payeReferenceSupplied>\n");
        xml.append("    <payeReferenceNumber>").append(escapeXml(cos.getPayeReferenceNumber())).append("</payeReferenceNumber>\n");
        xml.append("  </paye>\n");

        // PhD
        xml.append("  <phd>\n");
        xml.append("    <phdLevelRequired>").append(escapeXml(cos.getPhdLevelRequired())).append("</phdLevelRequired>\n");
        xml.append("  </phd>\n");

        xml.append("</certificateOfSponsorship>\n");
        return xml.toString();
    }

    private String escapeXml(String value) {
        if (value == null) return "";
        return value.replace("&", "&amp;")
                    .replace("<", "&lt;")
                    .replace(">", "&gt;")
                    .replace("&quot;", "&quot;")
                    .replace("'", "&apos;");
    }

    private String formatDate(java.time.LocalDate date) {
        if (date == null) return "";
        return date.format(DATE_FORMATTER);
    }

    private String formatDateShort(java.time.LocalDate date) {
        if (date == null) return "";
        return date.format(DATE_FORMATTER_SHORT);
    }
}