package de.votian.web.view;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.H4;
import com.vaadin.flow.component.html.Pre;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource;
import de.votian.web.model.SessionData;
import de.votian.web.service.RestClientService;
import org.springframework.core.ParameterizedTypeReference;

import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Route(value = "invoices", layout = MainLayout.class)
@PageTitle("Votian - Rechnungen")
public class InvoiceOverviewView extends VerticalLayout {

    private static final long ALL_COST_CENTERS_ID = -1L;

    private final RestClientService restClient;
    private final SessionData sessionData;

    private final DatePicker fromDate = new DatePicker("Von");
    private final DatePicker toDate = new DatePicker("Bis");
    private final ComboBox<CostCenterOption> costCenterField = new ComboBox<>("Kostenstelle");

    private final Span totalJobs = new Span("-");
    private final Span billableJobs = new Span("-");
    private final Span totalRevenue = new Span("-");
    private final Span totalCourierCosts = new Span("-");
    private final Span margin = new Span("-");

    private final Grid<CustomerSummaryRow> customerGrid = new Grid<>(CustomerSummaryRow.class, false);
    private final Grid<InvoiceRow> invoiceGrid = new Grid<>(InvoiceRow.class, false);

    private final Span detailJob = new Span("-");
    private final Span detailCustomer = new Span("-");
    private final Span detailCostCenter = new Span("-");
    private final Span detailTimes = new Span("-");
    private final Span detailRoute = new Span("-");
    private final Span detailCourier = new Span("-");
    private final Span detailStatus = new Span("-");
    private final Pre serviceDetails = new Pre();
    private final Pre invoiceText = new Pre();
    private final Button openJobButton = new Button("Auftrag oeffnen");
    private final Anchor csvExportLink = new Anchor();
    private final Anchor pdfExportLink = new Anchor();
    private final Button csvExportButton = new Button("CSV exportieren");
    private final Button pdfExportButton = new Button("PDF exportieren");

    private CustomerSummaryRow selectedCustomer;
    private InvoiceRow selectedInvoice;

    public InvoiceOverviewView(RestClientService restClient, SessionData sessionData) {
        this.restClient = restClient;
        this.sessionData = sessionData;

        setSizeFull();
        setPadding(true);
        setSpacing(true);

        configureFilters();
        configureCustomerGrid();
        configureInvoiceGrid();
        configureDetails();
        configureExportLinks();

        HorizontalLayout filterBar = buildFilterBar();
        HorizontalLayout cards = buildCards();
        HorizontalLayout workspace = buildWorkspace();

        add(new H3("Rechnungen"), filterBar, cards, workspace);
        expand(workspace);

        loadWorkspace();
    }

    private void configureExportLinks() {
        csvExportButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        pdfExportButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        csvExportLink.add(csvExportButton);
        pdfExportLink.add(pdfExportButton);
        csvExportLink.getElement().setAttribute("download", true);
        pdfExportLink.getElement().setAttribute("download", true);
        updateExportLinks();
    }

    private void configureFilters() {
        fromDate.setValue(LocalDate.now().withDayOfMonth(1));
        toDate.setValue(LocalDate.now());
        costCenterField.setItemLabelGenerator(CostCenterOption::label);
        costCenterField.setWidth("360px");
        costCenterField.addValueChangeListener(event -> {
            if (selectedCustomer != null) {
                loadInvoiceItems();
            }
        });
    }

    private HorizontalLayout buildFilterBar() {
        Button reloadButton = new Button("Neu laden", event -> loadWorkspace());
        reloadButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

        HorizontalLayout filterBar = new HorizontalLayout(fromDate, toDate, reloadButton);
        filterBar.setAlignItems(Alignment.BASELINE);
        return filterBar;
    }

    private HorizontalLayout buildCards() {
        HorizontalLayout cards = new HorizontalLayout(
                createCard("Auftraege", totalJobs),
                createCard("Abrechenbar", billableJobs),
                createCard("Umsatz", totalRevenue),
                createCard("Kurierkosten", totalCourierCosts),
                createCard("Deckungsbeitrag", margin)
        );
        cards.setWidthFull();
        return cards;
    }

    private HorizontalLayout buildWorkspace() {
        VerticalLayout customerPanel = new VerticalLayout(new H4("Kunden mit Rechnungsfaellen"), customerGrid);
        customerPanel.setPadding(false);
        customerPanel.setSpacing(true);
        customerPanel.setWidth("360px");
        customerPanel.setHeightFull();
        customerPanel.expand(customerGrid);

        Button refreshItemsButton = new Button("Details laden", event -> loadInvoiceItems());
        HorizontalLayout invoiceToolbar = new HorizontalLayout(costCenterField, refreshItemsButton, csvExportLink, pdfExportLink);
        invoiceToolbar.setAlignItems(Alignment.BASELINE);

        VerticalLayout detailPanel = buildDetailPanel();

        VerticalLayout invoicePanel = new VerticalLayout(new H4("Rechnungsdetails"), invoiceToolbar, invoiceGrid, detailPanel);
        invoicePanel.setPadding(false);
        invoicePanel.setSpacing(true);
        invoicePanel.setSizeFull();
        invoicePanel.expand(invoiceGrid);

        HorizontalLayout workspace = new HorizontalLayout(customerPanel, invoicePanel);
        workspace.setSizeFull();
        workspace.expand(invoicePanel);
        return workspace;
    }

    private void configureCustomerGrid() {
        customerGrid.addColumn(CustomerSummaryRow::customerName).setHeader("Kunde").setFlexGrow(1);
        customerGrid.addColumn(CustomerSummaryRow::customerEid).setHeader("EID").setAutoWidth(true);
        customerGrid.addColumn(CustomerSummaryRow::invoiceableJobs).setHeader("Faelle").setAutoWidth(true);
        customerGrid.addColumn(row -> formatAmount(row.totalRevenue())).setHeader("Umsatz").setAutoWidth(true);
        customerGrid.setSizeFull();
        customerGrid.asSingleSelect().addValueChangeListener(event -> {
            selectedCustomer = event.getValue();
            selectedInvoice = null;
            clearDetail();
            if (selectedCustomer != null) {
                loadCostCenters(selectedCustomer.customerId());
                loadInvoiceItems();
            } else {
                costCenterField.clear();
                costCenterField.setItems(List.of());
                invoiceGrid.setItems(List.of());
            }
        });
    }

    private void configureInvoiceGrid() {
        invoiceGrid.addColumn(InvoiceRow::jobId).setHeader("Auftrag").setAutoWidth(true).setFrozen(true);
        invoiceGrid.addColumn(InvoiceRow::finishTime).setHeader("Abschluss").setAutoWidth(true);
        invoiceGrid.addColumn(InvoiceRow::costCenterName).setHeader("Kostenstelle").setAutoWidth(true);
        invoiceGrid.addColumn(InvoiceRow::commissionNo).setHeader("Kommission").setAutoWidth(true);
        invoiceGrid.addColumn(InvoiceRow::pickupName).setHeader("Abholung").setFlexGrow(1);
        invoiceGrid.addColumn(InvoiceRow::deliveryName).setHeader("Zustellung").setFlexGrow(1);
        invoiceGrid.addColumn(InvoiceRow::courierSid).setHeader("Kurier").setAutoWidth(true);
        invoiceGrid.addColumn(row -> formatAmount(row.totalPrice())).setHeader("Umsatz").setAutoWidth(true);
        invoiceGrid.addColumn(row -> formatAmount(row.courierPrice())).setHeader("Kurierkosten").setAutoWidth(true);
        invoiceGrid.addColumn(row -> formatAmount(row.margin())).setHeader("Marge").setAutoWidth(true);
        invoiceGrid.addColumn(row -> row.exported() ? "Ja" : "Nein").setHeader("Export").setAutoWidth(true);
        invoiceGrid.setSizeFull();
        invoiceGrid.asSingleSelect().addValueChangeListener(event -> {
            selectedInvoice = event.getValue();
            updateDetail();
        });
    }

    private void configureDetails() {
        configureTextBlock(serviceDetails);
        configureTextBlock(invoiceText);
        openJobButton.setEnabled(false);
        openJobButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        openJobButton.addClickListener(event -> {
            if (selectedInvoice != null && sessionData.canViewJobs()) {
                getUI().ifPresent(ui -> ui.navigate("jobs/" + selectedInvoice.jobId()));
            }
        });
        clearDetail();
    }

    private VerticalLayout buildDetailPanel() {
        VerticalLayout detailPanel = new VerticalLayout();
        detailPanel.setPadding(true);
        detailPanel.setSpacing(true);
        detailPanel.getStyle()
                .set("border", "1px solid var(--lumo-contrast-20pct)")
                .set("border-radius", "var(--lumo-border-radius-l)");

        detailPanel.add(
                new H4("Ausgewaehlter Rechnungsfall"),
                detailLine("Auftrag", detailJob),
                detailLine("Kunde", detailCustomer),
                detailLine("Kostenstelle", detailCostCenter),
                detailLine("Zeiten", detailTimes),
                detailLine("Route", detailRoute),
                detailLine("Kurier", detailCourier),
                detailLine("Status", detailStatus),
                new H4("Leistungen"),
                serviceDetails,
                new H4("Rechnungstext"),
                invoiceText,
                openJobButton
        );
        return detailPanel;
    }

    private HorizontalLayout detailLine(String label, Span value) {
        Span headline = new Span(label + ":");
        headline.getStyle().set("font-weight", "600");
        HorizontalLayout line = new HorizontalLayout(headline, value);
        line.setPadding(false);
        line.setSpacing(true);
        return line;
    }

    private VerticalLayout createCard(String title, Span value) {
        VerticalLayout card = new VerticalLayout();
        card.setPadding(true);
        card.setSpacing(false);
        card.getStyle()
                .set("border", "1px solid var(--lumo-contrast-20pct)")
                .set("border-radius", "var(--lumo-border-radius-l)")
                .set("min-width", "180px");

        H3 headline = new H3(title);
        headline.getStyle().set("margin", "0");
        value.getStyle().set("font-size", "var(--lumo-font-size-xl)").set("font-weight", "700");
        card.add(headline, value);
        return card;
    }

    private void loadWorkspace() {
        if (sessionData.getHeadquartersId() == null) {
            return;
        }

        loadSummary();
        loadCustomers();
    }

    private void loadSummary() {
        try {
            @SuppressWarnings("unchecked")
            Map<String, Object> data = restClient.get(
                    "/jobs/invoice-summary?hqId=" + sessionData.getHeadquartersId()
                            + "&from=" + fromDate.getValue() + "&to=" + toDate.getValue(),
                    LinkedHashMap.class
            );
            totalJobs.setText(str(data.get("totalJobs")));
            billableJobs.setText(str(data.get("billableJobs")));
            totalRevenue.setText(str(data.get("totalRevenue")));
            totalCourierCosts.setText(str(data.get("totalCourierCosts")));
            margin.setText(str(data.get("contributionMargin")));
        } catch (Exception e) {
            Notification.show("Rechnungssummary konnte nicht geladen werden.", 4000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void loadCustomers() {
        try {
            List<LinkedHashMap<String, Object>> data = restClient.getList(
                    "/invoices/customers?hqId=" + sessionData.getHeadquartersId()
                            + "&from=" + fromDate.getValue() + "&to=" + toDate.getValue(),
                    new ParameterizedTypeReference<>() {}
            );
            List<CustomerSummaryRow> rows = new ArrayList<>();
            for (Map<String, Object> row : data) {
                rows.add(new CustomerSummaryRow(
                        toLong(row.get("customerId")),
                        str(row.get("customerName")),
                        str(row.get("customerEid")),
                        toLong(row.get("invoiceableJobs")) != null ? toLong(row.get("invoiceableJobs")) : 0L,
                        toBigDecimal(row.get("totalRevenue"))
                ));
            }
            rows.sort(Comparator.comparing(CustomerSummaryRow::customerName, String.CASE_INSENSITIVE_ORDER));
            customerGrid.setItems(rows);
            if (!rows.isEmpty()) {
                customerGrid.select(rows.get(0));
            } else {
                selectedCustomer = null;
                invoiceGrid.setItems(List.of());
                costCenterField.setItems(List.of());
                clearDetail();
            }
            updateExportLinks();
        } catch (Exception e) {
            Notification.show("Rechnungskunden konnten nicht geladen werden.", 4000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void loadCostCenters(Long customerId) {
        try {
            List<LinkedHashMap<String, Object>> data = restClient.getList(
                    "/customers/" + customerId + "/costcenters/visible",
                    new ParameterizedTypeReference<>() {}
            );
            List<CostCenterOption> options = new ArrayList<>();
            options.add(new CostCenterOption(ALL_COST_CENTERS_ID, "Alle sichtbaren Kostenstellen"));
            for (Map<String, Object> row : data) {
                Long id = toLong(row.get("id"));
                if (id != null && (!sessionData.isCustomerUser() || sessionData.hasCostCenterAccess(id))) {
                    options.add(new CostCenterOption(id, str(row.get("name"))));
                }
            }
            costCenterField.setItems(options);
            costCenterField.setValue(options.get(0));
            updateExportLinks();
        } catch (Exception e) {
            Notification.show("Kostenstellen konnten nicht geladen werden.", 4000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void loadInvoiceItems() {
        if (selectedCustomer == null) {
            return;
        }

        try {
            CostCenterOption selectedCostCenter = costCenterField.getValue();
            StringBuilder path = new StringBuilder("/invoices/items?hqId=")
                    .append(sessionData.getHeadquartersId())
                    .append("&customerId=").append(selectedCustomer.customerId())
                    .append("&from=").append(fromDate.getValue())
                    .append("&to=").append(toDate.getValue());
            if (selectedCostCenter != null && selectedCostCenter.id() != ALL_COST_CENTERS_ID) {
                path.append("&costCenterId=").append(selectedCostCenter.id());
            }

            List<LinkedHashMap<String, Object>> data = restClient.getList(
                    path.toString(),
                    new ParameterizedTypeReference<>() {}
            );
            List<InvoiceRow> rows = new ArrayList<>();
            for (Map<String, Object> row : data) {
                rows.add(new InvoiceRow(
                        toLong(row.get("jobId")),
                        toLong(row.get("costCenterId")),
                        str(row.get("costCenterName")),
                        str(row.get("customerName")),
                        str(row.get("orderTime")),
                        str(row.get("finishTime")),
                        str(row.get("courierSid")),
                        str(row.get("tourName")),
                        str(row.get("commissionNo")),
                        str(row.get("pickupName")),
                        str(row.get("deliveryName")),
                        toInteger(row.get("status")),
                        toInteger(row.get("incomplete")),
                        toBigDecimal(row.get("totalPrice")),
                        toBigDecimal(row.get("courierPrice")),
                        toBigDecimal(row.get("margin")),
                        Boolean.TRUE.equals(row.get("exported")) || "true".equalsIgnoreCase(str(row.get("exported"))),
                        blank(row.get("serviceDetails")),
                        blank(row.get("invoiceText"))
                ));
            }
            invoiceGrid.setItems(rows);
            invoiceGrid.asSingleSelect().clear();
            clearDetail();
            if (!rows.isEmpty()) {
                invoiceGrid.select(rows.get(0));
            }
            updateExportLinks();
        } catch (Exception e) {
            Notification.show("Rechnungsdetails konnten nicht geladen werden.", 4000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void updateExportLinks() {
        boolean enabled = selectedCustomer != null && sessionData.canExportInvoices();
        csvExportButton.setEnabled(enabled);
        pdfExportButton.setEnabled(enabled);
        if (!enabled) {
            csvExportLink.setHref("");
            pdfExportLink.setHref("");
            return;
        }

        csvExportLink.setHref(exportResource("csv"));
        pdfExportLink.setHref(exportResource("pdf"));
    }

    private StreamResource exportResource(String format) {
        String fileName = "invoices_" + selectedCustomer.customerId() + "_" + fromDate.getValue() + "_" + toDate.getValue() + "." + format;
        return new StreamResource(fileName, () -> new ByteArrayInputStream(restClient.getBytes(buildExportPath(format))));
    }

    private String buildExportPath(String format) {
        StringBuilder path = new StringBuilder("/invoices/export/")
                .append(format)
                .append("?hqId=").append(sessionData.getHeadquartersId())
                .append("&customerId=").append(selectedCustomer.customerId())
                .append("&from=").append(fromDate.getValue())
                .append("&to=").append(toDate.getValue());
        CostCenterOption selectedCostCenter = costCenterField.getValue();
        if (selectedCostCenter != null && selectedCostCenter.id() != ALL_COST_CENTERS_ID) {
            path.append("&costCenterId=").append(selectedCostCenter.id());
        }
        return path.toString();
    }

    private void updateDetail() {
        if (selectedInvoice == null) {
            clearDetail();
            return;
        }

        detailJob.setText(str(selectedInvoice.jobId()));
        detailCustomer.setText(selectedInvoice.customerName());
        detailCostCenter.setText(selectedInvoice.costCenterName());
        detailTimes.setText(formatDateTime(selectedInvoice.orderTime()) + " / " + formatDateTime(selectedInvoice.finishTime()));
        detailRoute.setText(selectedInvoice.pickupName() + " -> " + selectedInvoice.deliveryName());
        detailCourier.setText(str(selectedInvoice.courierSid()));
        detailStatus.setText(statusLabel(selectedInvoice.status(), selectedInvoice.incomplete(), selectedInvoice.exported()));
        serviceDetails.setText(selectedInvoice.serviceDetails().isBlank() ? "(keine Leistungen)" : selectedInvoice.serviceDetails());
        invoiceText.setText(selectedInvoice.invoiceText().isBlank() ? "(kein Rechnungstext)" : selectedInvoice.invoiceText());
        openJobButton.setEnabled(sessionData.canViewJobs());
    }

    private void clearDetail() {
        detailJob.setText("-");
        detailCustomer.setText("-");
        detailCostCenter.setText("-");
        detailTimes.setText("-");
        detailRoute.setText("-");
        detailCourier.setText("-");
        detailStatus.setText("-");
        serviceDetails.setText("(keine Leistungen)");
        invoiceText.setText("(kein Rechnungsfall ausgewaehlt)");
        openJobButton.setEnabled(false);
    }

    private void configureTextBlock(Pre block) {
        block.setWidthFull();
        block.getStyle()
                .set("background", "var(--lumo-contrast-5pct)")
                .set("padding", "var(--lumo-space-m)")
                .set("border-radius", "var(--lumo-border-radius-m)")
                .set("white-space", "pre-wrap")
                .set("margin", "0");
    }

    private String statusLabel(Integer status, Integer incomplete, boolean exported) {
        String base = switch (status != null ? status : -1) {
            case 0 -> "Blockiert";
            case 1 -> "Zugewiesen";
            case 2 -> "In Zustellung";
            case 8 -> "Zugestellt";
            case 9 -> "Abgeschlossen";
            case 10 -> "Storniert";
            default -> status != null ? String.valueOf(status) : "-";
        };
        if (Boolean.TRUE.equals(exported)) {
            return base + ", exportiert";
        }
        if (incomplete != null && incomplete > 0) {
            return base + ", unvollstaendig";
        }
        return base;
    }

    private String formatDateTime(String value) {
        return value == null || value.isBlank() ? "-" : value.replace("T", " ");
    }

    private String formatAmount(BigDecimal value) {
        return value != null ? value.toPlainString() : "-";
    }

    private BigDecimal toBigDecimal(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof BigDecimal decimal) {
            return decimal;
        }
        try {
            return new BigDecimal(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private Long toLong(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number number) {
            return number.longValue();
        }
        try {
            return Long.parseLong(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private Integer toInteger(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number number) {
            return number.intValue();
        }
        try {
            return Integer.parseInt(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private String str(Object value) {
        return value != null ? value.toString() : "-";
    }

    private String blank(Object value) {
        return value != null ? value.toString() : "";
    }

    private record CustomerSummaryRow(Long customerId, String customerName, String customerEid,
                                      long invoiceableJobs, BigDecimal totalRevenue) { }

    private record CostCenterOption(Long id, String label) { }

    private record InvoiceRow(Long jobId, Long costCenterId, String costCenterName, String customerName,
                              String orderTime, String finishTime, String courierSid, String tourName,
                              String commissionNo, String pickupName, String deliveryName,
                              Integer status, Integer incomplete, BigDecimal totalPrice,
                              BigDecimal courierPrice, BigDecimal margin, boolean exported,
                              String serviceDetails, String invoiceText) { }
}
