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.icon.VaadinIcon;
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.server.StreamResource;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
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.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Route(value = "jobs", layout = MainLayout.class)
@PageTitle("Votian - Auftraege")
public class JobListView extends VerticalLayout {

    private final RestClientService restClient;
    private final SessionData sessionData;

    private final TextField searchField = new TextField();
    private final DatePicker fromDate = new DatePicker("Von");
    private final DatePicker toDate = new DatePicker("Bis");
    private final ComboBox<StatusOption> statusField = new ComboBox<>("Status");
    private final ComboBox<ExportOption> exportField = new ComboBox<>("Export");
    private final ComboBox<CostCenterOption> costCenterField = new ComboBox<>("Kostenstelle");
    private final TextField courierField = new TextField("Kurier");
    private final Anchor csvExportLink = new Anchor();
    private final Button csvExportButton = new Button("CSV exportieren");

    private final Grid<JobRow> grid = new Grid<>(JobRow.class, false);

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

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

        configureFilters();
        configureGrid();
        configureExportLink();

        Button searchButton = new Button("Suchen", VaadinIcon.SEARCH.create(), event -> {
            updateExportLink();
            loadJobs();
        });
        searchButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

        Button resetButton = new Button("Zuruecksetzen", event -> resetFilters());
        Button newButton = new Button("Neuer Auftrag", VaadinIcon.PLUS.create(),
                event -> getUI().ifPresent(ui -> ui.navigate(JobDetailView.class)));
        newButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        newButton.setVisible(sessionData.canManageJobs());

        Button batchButton = new Button("Listenerfassung", VaadinIcon.TABLE.create(),
                event -> getUI().ifPresent(ui -> ui.navigate(JobBatchView.class)));
        batchButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        batchButton.setVisible(sessionData.canBatchJobs());

        HorizontalLayout toolbar = new HorizontalLayout(
                searchField,
                fromDate,
                toDate,
                statusField,
                exportField,
                costCenterField,
                courierField,
                csvExportLink,
                searchButton,
                resetButton,
                newButton,
                batchButton
        );
        toolbar.setAlignItems(Alignment.BASELINE);
        toolbar.setWidthFull();

        add(toolbar, grid);
        expand(grid);

        loadCostCenters();
        loadJobs();
    }

    private void configureFilters() {
        searchField.setPlaceholder("Auftrag, Kommission, Kunde ...");
        searchField.setPrefixComponent(VaadinIcon.SEARCH.create());
        searchField.setValueChangeMode(ValueChangeMode.LAZY);
        searchField.setWidth("260px");
        searchField.addValueChangeListener(event -> {
            updateExportLink();
            loadJobs();
        });

        fromDate.setValue(LocalDate.now());
        fromDate.addValueChangeListener(event -> updateExportLink());
        toDate.setValue(LocalDate.now());
        toDate.addValueChangeListener(event -> updateExportLink());

        statusField.setItems(StatusOption.values());
        statusField.setItemLabelGenerator(StatusOption::label);
        statusField.setValue(StatusOption.ALL);
        statusField.setWidth("170px");
        statusField.addValueChangeListener(event -> updateExportLink());

        exportField.setItems(ExportOption.values());
        exportField.setItemLabelGenerator(ExportOption::label);
        exportField.setValue(ExportOption.ALL);
        exportField.setWidth("160px");
        exportField.addValueChangeListener(event -> updateExportLink());

        costCenterField.setItemLabelGenerator(CostCenterOption::label);
        costCenterField.setWidth("260px");
        costCenterField.addValueChangeListener(event -> updateExportLink());

        courierField.setWidth("180px");
        courierField.setValueChangeMode(ValueChangeMode.LAZY);
        courierField.addValueChangeListener(event -> updateExportLink());
    }

    private void configureExportLink() {
        csvExportButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        csvExportLink.add(csvExportButton);
        csvExportLink.getElement().setAttribute("download", true);
        csvExportLink.setVisible(sessionData.canExportJobs());
        updateExportLink();
    }

    private void configureGrid() {
        grid.addColumn(JobRow::id).setHeader("Auftrag").setSortable(true).setAutoWidth(true).setFrozen(true);
        grid.addColumn(row -> statusLabel(row.status())).setHeader("Status").setAutoWidth(true);
        grid.addColumn(row -> formatDateTime(row.orderTime())).setHeader("Auftragszeit").setSortable(true).setAutoWidth(true);
        grid.addColumn(JobRow::customerName).setHeader("Kunde").setFlexGrow(1);
        grid.addColumn(JobRow::costCenterName).setHeader("Kostenstelle").setAutoWidth(true);
        grid.addColumn(JobRow::commissionNo).setHeader("Kommission").setAutoWidth(true);
        grid.addColumn(JobRow::pickupName).setHeader("Abholung").setFlexGrow(1);
        grid.addColumn(JobRow::deliveryName).setHeader("Zustellung").setFlexGrow(1);
        grid.addColumn(JobRow::courierSid).setHeader("Kurier SID").setAutoWidth(true);
        grid.addColumn(row -> formatAmount(row.totalPrice())).setHeader("Gesamtpreis").setAutoWidth(true);
        grid.addColumn(row -> row.exported() ? "Ja" : "Nein").setHeader("Export").setAutoWidth(true);
        grid.addColumn(row -> vehicleLabel(row.vehicleTypeId())).setHeader("Fahrzeug").setAutoWidth(true);
        grid.setSizeFull();

        grid.addItemClickListener(event -> {
            if (sessionData.canViewJobs()) {
                getUI().ifPresent(ui -> ui.navigate("jobs/" + event.getItem().id()));
            }
        });
    }

    private void loadCostCenters() {
        try {
            List<LinkedHashMap<String, Object>> data = restClient.getList(
                    "/jobs/filter-costcenters?hqId=" + sessionData.getHeadquartersId(),
                    new ParameterizedTypeReference<>() {}
            );
            List<CostCenterOption> options = new ArrayList<>();
            options.add(new CostCenterOption(null, "Alle Kostenstellen"));
            for (Map<String, Object> row : data) {
                options.add(new CostCenterOption(
                        toLong(row.get("id")),
                        str(row.get("path")).isBlank()
                                ? str(row.get("name"))
                                : str(row.get("path")) + " / " + str(row.get("name"))
                ));
            }
            costCenterField.setItems(options);
            costCenterField.setValue(options.get(0));
            updateExportLink();
        } catch (Exception e) {
            Notification.show("Kostenstellenfilter konnte nicht geladen werden.", 4000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void loadJobs() {
        if (sessionData.getHeadquartersId() == null || fromDate.getValue() == null || toDate.getValue() == null) {
            return;
        }

        try {
            StringBuilder path = new StringBuilder("/jobs/list?hqId=")
                    .append(sessionData.getHeadquartersId())
                    .append("&from=").append(fromDate.getValue())
                    .append("&to=").append(toDate.getValue());

            if (!searchField.getValue().isBlank()) {
                path.append("&term=").append(encode(searchField.getValue()));
            }
            if (statusField.getValue() != null && statusField.getValue().value() != null) {
                path.append("&status=").append(statusField.getValue().value());
            }
            if (costCenterField.getValue() != null && costCenterField.getValue().id() != null) {
                path.append("&costCenterId=").append(costCenterField.getValue().id());
            }
            if (!courierField.getValue().isBlank()) {
                path.append("&courierSid=").append(encode(courierField.getValue()));
            }
            if (exportField.getValue() != null && exportField.getValue().value() != null) {
                path.append("&exported=").append(exportField.getValue().value());
            }

            List<LinkedHashMap<String, Object>> data = restClient.getList(
                    path.toString(),
                    new ParameterizedTypeReference<>() {}
            );
            List<JobRow> rows = new ArrayList<>();
            for (Map<String, Object> row : data) {
                rows.add(new JobRow(
                        toLong(row.get("id")),
                        toInteger(row.get("status")),
                        str(row.get("orderTime")),
                        str(row.get("customerName")),
                        str(row.get("costCenterName")),
                        str(row.get("commissionNo")),
                        str(row.get("pickupName")),
                        str(row.get("deliveryName")),
                        str(row.get("courierSid")),
                        toInteger(row.get("vehicleTypeId")),
                        toBigDecimal(row.get("totalPrice")),
                        Boolean.TRUE.equals(row.get("exported")) || "true".equalsIgnoreCase(str(row.get("exported")))
                ));
            }
            grid.setItems(rows);
        } catch (Exception e) {
            Notification.show("Auftragsliste konnte nicht geladen werden: " + e.getMessage(),
                            5000, Notification.Position.MIDDLE)
                    .addThemeVariants(NotificationVariant.LUMO_ERROR);
        }
    }

    private void resetFilters() {
        searchField.clear();
        fromDate.setValue(LocalDate.now());
        toDate.setValue(LocalDate.now());
        statusField.setValue(StatusOption.ALL);
        exportField.setValue(ExportOption.ALL);
        if (!costCenterField.getListDataView().getItems().toList().isEmpty()) {
            costCenterField.setValue(costCenterField.getListDataView().getItems().findFirst().orElse(null));
        }
        courierField.clear();
        updateExportLink();
        loadJobs();
    }

    private void updateExportLink() {
        boolean enabled = sessionData.canExportJobs()
                && sessionData.getHeadquartersId() != null
                && fromDate.getValue() != null
                && toDate.getValue() != null;
        csvExportButton.setEnabled(enabled);
        if (!enabled) {
            csvExportLink.setHref("");
            return;
        }

        StreamResource csv = new StreamResource(
                "jobs_" + fromDate.getValue() + "_" + toDate.getValue() + ".csv",
                () -> new ByteArrayInputStream(restClient.getBytes(buildExportPath()))
        );
        csvExportLink.setHref(csv);
    }

    private String buildExportPath() {
        StringBuilder path = new StringBuilder("/jobs/export/csv?hqId=")
                .append(sessionData.getHeadquartersId())
                .append("&from=").append(fromDate.getValue())
                .append("&to=").append(toDate.getValue());

        if (!searchField.getValue().isBlank()) {
            path.append("&term=").append(encode(searchField.getValue()));
        }
        if (statusField.getValue() != null && statusField.getValue().value() != null) {
            path.append("&status=").append(statusField.getValue().value());
        }
        if (costCenterField.getValue() != null && costCenterField.getValue().id() != null) {
            path.append("&costCenterId=").append(costCenterField.getValue().id());
        }
        if (!courierField.getValue().isBlank()) {
            path.append("&courierSid=").append(encode(courierField.getValue()));
        }
        if (exportField.getValue() != null && exportField.getValue().value() != null) {
            path.append("&exported=").append(exportField.getValue().value());
        }
        return path.toString();
    }

    private String encode(String value) {
        return java.net.URLEncoder.encode(value, java.nio.charset.StandardCharsets.UTF_8);
    }

    private String statusLabel(Integer status) {
        if (status == null) {
            return "";
        }
        return switch (status) {
            case 0 -> "Blockiert";
            case 1 -> "Zugewiesen";
            case 2 -> "In Zustellung";
            case 8 -> "Zugestellt";
            case 9 -> "Abgeschlossen";
            case 10 -> "Storniert";
            default -> String.valueOf(status);
        };
    }

    private String vehicleLabel(Integer vehicleTypeId) {
        return vehicleTypeId != null ? "Typ " + vehicleTypeId : "";
    }

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

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

    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 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 String str(Object value) {
        return value != null ? value.toString() : "";
    }

    private record JobRow(Long id, Integer status, String orderTime, String customerName, String costCenterName,
                          String commissionNo, String pickupName, String deliveryName, String courierSid,
                          Integer vehicleTypeId, BigDecimal totalPrice, boolean exported) {
    }

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

    private enum StatusOption {
        ALL("Alle", null),
        BLOCKED("Blockiert", 0),
        ASSIGNED("Zugewiesen", 1),
        ON_DELIVERY("In Zustellung", 2),
        DELIVERED("Zugestellt", 8),
        COMPLETED("Abgeschlossen", 9),
        CANCELLED("Storniert", 10);

        private final String label;
        private final Integer value;

        StatusOption(String label, Integer value) {
            this.label = label;
            this.value = value;
        }

        private String label() {
            return label;
        }

        private Integer value() {
            return value;
        }
    }

    private enum ExportOption {
        ALL("Alle", null),
        NOT_EXPORTED("Nicht exportiert", false),
        EXPORTED("Exportiert", true);

        private final String label;
        private final Boolean value;

        ExportOption(String label, Boolean value) {
            this.label = label;
            this.value = value;
        }

        private String label() {
            return label;
        }

        private Boolean value() {
            return value;
        }
    }
}
