package org.example.backend.service;

import static org.example.InvoicesView.DEFAULT_DUE_DATE_DURATION;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.mail.util.ByteArrayDataSource;

import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.MultiPartEmail;
import org.apache.deltaspike.core.api.config.ConfigProperty;
import org.example.backend.Invoice;
import org.example.backend.InvoiceRow;
import org.example.backend.Invoicer;
import org.example.backend.UserSession;

import fr.opensagres.xdocreport.converter.ConverterTypeTo;
import fr.opensagres.xdocreport.converter.ConverterTypeVia;
import fr.opensagres.xdocreport.converter.Options;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;

/**
 *
 * @author Matti Tahvonen
 */
@Stateless
public class InvoiceFacade {

    @Inject
    InvoiceRepository repo;

    @Inject
    InvoicerRepository invoicerRepo;

    @Inject
    @ConfigProperty(name = "jpa-invoicer.smtp.username")
    private String smtpUsername;
    @Inject
    @ConfigProperty(name = "jpa-invoicer.smtp.password")
    private String smtpPassword;

    @Inject
    @ConfigProperty(name = "jpa-invoicer.smtp.hostname")
    private String smtpHostname;

    @Inject
    @ConfigProperty(name = "jpa-invoicer.smtp.port")
    private Integer smtpPort;

    @Inject
    @ConfigProperty(name = "jpa-invoicer.smtp.from")
    private String smtpFrom;

    @Inject
    @ConfigProperty(name = "jpa-invoicer.smtp.subject")
    private String smtpSubject;

    @Inject
    @ConfigProperty(name = "jpa-invoicer.smtp.message")
    private String smtpMessage;

    public List<Invoice> findAll(Invoicer value) {
        return repo.findAll();
    }

    public Invoice createFor(Invoicer invoicer) {
        invoicer = invoicerRepo.findBy(invoicer.getId());
        Invoice invoice = new Invoice();
        invoice.setInvoiceNumber(invoicer.getAndIcrementNextInvoiceNumber());
        invoice.setInvoicer(invoicer);
        invoice.setInvoiceDate(new Date());
        invoice.setDueDate(new Date(
                System.currentTimeMillis() + DEFAULT_DUE_DATE_DURATION));
        invoice.setLastEditor(session.getUser());
        invoicerRepo.save(invoicer);
        repo.save(invoice);
        return invoice;
    }

    @Inject
    UserSession session;

    public Invoice save(Invoice entity) {
        entity.setLastEditor(session.getUser());
        return repo.save(entity);
    }

    public void writeAsOdt(Invoice invoice, OutputStream out) {
        Locale.setDefault(new Locale("fi"));
        invoice = repo.findBy(invoice.getId());
        invoice.getInvoiceRows().size();
        invoice.getInvoicer().getBankAccount();
        invoice.getInvoicer().getBankAccount();
        invoice.getInvoicer().getEmail();
        invoice.getInvoicer().getAddress();
        invoice.getInvoicer().getPhone();

        try {
            // 1) Load ODT file by filling Velocity template engine and cache
            // it to the registry
            InputStream in = getTemplate(invoice.getInvoicer());
            IXDocReport report = XDocReportRegistry.getRegistry().
                    loadReport(in, TemplateEngineKind.Freemarker);

            FieldsMetadata metadata = report.createFieldsMetadata();
            metadata.load("r", InvoiceRow.class, true);

            IContext ctx = report.createContext();
            ctx.put("invoice", invoice);
            ctx.put("to", invoice.getTo());
            ctx.put("r", invoice.getInvoiceRows());
            ctx.
                    put("sender", invoicerRepo.findBy(invoice.getInvoicer().
                                    getId()));

            // 4) Generate report by merging Java model with the ODT
            report.process(ctx, out);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    protected static InputStream getTemplate(Invoicer invoicer) {
        if(invoicer.getTemplate() == null) {
            return getDefaultTemplate();
        } else {
            return new ByteArrayInputStream(invoicer.getTemplate());
        }
    }

    public static InputStream getDefaultTemplate() {
        return Invoice.class.getResourceAsStream("/tmpl.odt");
    }

    public void writeAsPdf(Invoice invoice, OutputStream out) {
        Locale.setDefault(new Locale("fi"));
        invoice = repo.findBy(invoice.getId());
        
        try {
            
            // Get template stream (either the default or overridden by the user)
            InputStream in = getTemplate(invoice.getInvoicer());

            // Prepare the IXDocReport instance based on the template, using
            // Freemarker template engine
            IXDocReport report = XDocReportRegistry.getRegistry().
                    loadReport(in, TemplateEngineKind.Freemarker);
            
            // Define what we want to do (PDF file from ODF template)
            Options options = Options.getTo(ConverterTypeTo.PDF).via(
                    ConverterTypeVia.ODFDOM);

            // Add properties to the context
            IContext ctx = report.createContext();
            ctx.put("invoice", invoice);
            ctx.put("to", invoice.getTo());
            ctx.put("sender", invoice.getInvoicer());
            // instruct XDocReport to inspect InvoiceRow entity as well
            // which is given as list and iterated in a table
            FieldsMetadata metadata = report.createFieldsMetadata();
            metadata.load("r", InvoiceRow.class, true);
            ctx.put("r", invoice.getInvoiceRows());
            
            // Write the PDF file to output stream
            report.convert(ctx, options, out);
            out.close();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public void remove(Invoice invoice) {
        repo.remove(repo.findBy(invoice.getId()));
    }

    public void sendInvoice(final Invoice invoice) throws EmailException, IOException {
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            writeAsPdf(invoice, out);
            ByteArrayDataSource dataSource =
                    new ByteArrayDataSource(out.toByteArray(), "application/pdf");
            String fileName = "invoice_" + invoice.getInvoiceNumber() + ".pdf";

            MultiPartEmail email = new MultiPartEmail();
            email.setAuthentication(smtpUsername, smtpPassword);
            email.setHostName(smtpHostname);
            email.setSmtpPort(smtpPort);
            email.setFrom(smtpFrom);
            email.addTo(invoice.getInvoicer().getEmail());
            email.setSubject(smtpSubject);
            email.setMsg(smtpMessage);
            email.attach(dataSource, fileName, "Invoice");
            email.send();
        }
    }
}