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.grid.Grid;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Pre;
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.component.upload.Upload;
import com.vaadin.flow.component.upload.receivers.MemoryBuffer;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource;
import de.votian.web.service.RestClientService;
import org.springframework.core.ParameterizedTypeReference;

import java.io.ByteArrayInputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Route(value = "imports", layout = MainLayout.class)
@PageTitle("Votian - Importe")
public class ImportView extends VerticalLayout {

    private static final DateTimeFormatter FILE_TIME_FORMAT = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");

    private final RestClientService restClient;

    private final ComboBox<ImportProcessRow> processField = new ComboBox<>("Prozess");
    private final ComboBox<CustomerOption> customerField = new ComboBox<>("Kunde");
    private final Button refreshButton = new Button("Aktualisieren");
    private final Button executeButton = new Button("Nativen Import ausfuehren");
    private final Grid<ImportFileRow> fileGrid = new Grid<>(ImportFileRow.class, false);
    private final MemoryBuffer uploadBuffer = new MemoryBuffer();
    private final Upload upload = new Upload(uploadBuffer);
    private final Pre processInfo = new Pre();
    private final Pre resultBox = new Pre();

    private List<ImportProcessRow> processes = List.of();
    private List<CustomerOption> customers = List.of();

    public ImportView(RestClientService restClient) {
        this.restClient = restClient;

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

        configureSelectors();
        configureUpload();
        configureGrid();
        configureResultBox();

        add(new H3("Importe"), buildToolbar(), buildWorkspace());
        expand(fileGrid);

        loadProcesses();
    }

    private void configureSelectors() {
        processField.setWidth("420px");
        processField.setItemLabelGenerator(ImportProcessRow::comboLabel);
        processField.addValueChangeListener(event -> {
            updateProcessPresentation();
            refreshFiles();
        });

        customerField.setWidth("360px");
        customerField.setItemLabelGenerator(CustomerOption::label);
        customerField.setVisible(false);
        customerField.addValueChangeListener(event -> updateExecuteState());

        refreshButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        refreshButton.addClickListener(event -> refreshFiles());

        executeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        executeButton.addClickListener(event -> executeSelectedImport());
        executeButton.setEnabled(false);
    }

    private void configureUpload() {
        upload.setMaxFiles(1);
        upload.setMaxFileSize(25 * 1024 * 1024);
        upload.setDropAllowed(true);
        upload.setUploadButton(new Button("Datei hochladen"));
        upload.addSucceededListener(event -> uploadSelectedFile(event.getFileName(), event.getMIMEType()));
        upload.addFileRejectedListener(event -> showError(event.getErrorMessage()));
    }

    private void configureGrid() {
        fileGrid.addColumn(ImportFileRow::fileName).setHeader("Datei").setFlexGrow(1);
        fileGrid.addColumn(row -> formatBytes(row.size())).setHeader("Groesse").setAutoWidth(true);
        fileGrid.addColumn(row -> row.modifiedAt() != null ? FILE_TIME_FORMAT.format(row.modifiedAt()) : "")
                .setHeader("Geaendert")
                .setAutoWidth(true);
        fileGrid.addComponentColumn(this::buildDownloadLink).setHeader("Download").setAutoWidth(true);
        fileGrid.addComponentColumn(this::buildDeleteButton).setHeader("Loeschen").setAutoWidth(true);
        fileGrid.setSizeFull();
        fileGrid.asSingleSelect().addValueChangeListener(event -> updateExecuteState());
    }

    private void configureResultBox() {
        processInfo.getStyle()
                .set("background", "var(--lumo-contrast-5pct)")
                .set("padding", "var(--lumo-space-m)")
                .set("margin", "0");
        resultBox.getStyle()
                .set("background", "var(--lumo-contrast-5pct)")
                .set("padding", "var(--lumo-space-m)")
                .set("margin", "0");
        processInfo.setText("Prozess auswaehlen");
        resultBox.setText("Noch kein Import ausgefuehrt.");
    }

    private HorizontalLayout buildToolbar() {
        HorizontalLayout toolbar = new HorizontalLayout(processField, customerField, upload, refreshButton, executeButton);
        toolbar.setAlignItems(Alignment.END);
        toolbar.setWidthFull();
        return toolbar;
    }

    private VerticalLayout buildWorkspace() {
        VerticalLayout workspace = new VerticalLayout(processInfo, fileGrid, resultBox);
        workspace.setPadding(false);
        workspace.setSpacing(true);
        workspace.setSizeFull();
        workspace.expand(fileGrid);
        return workspace;
    }

    private void loadProcesses() {
        try {
            processes = restClient.getList("/imports/processes", new ParameterizedTypeReference<List<ImportProcessRow>>() {});
            processField.setItems(processes);
            if (!processes.isEmpty()) {
                processField.setValue(processes.getFirst());
            }
        } catch (Exception ex) {
            showError("Importprozesse konnten nicht geladen werden: " + ex.getMessage());
        }
    }

    private void loadCustomersIfNeeded() {
        ImportProcessRow selectedProcess = processField.getValue();
        if (selectedProcess == null || !selectedProcess.nativeExecution() || !selectedProcess.customerRequired()) {
            customers = List.of();
            customerField.clear();
            customerField.setItems(List.of());
            customerField.setVisible(false);
            return;
        }
        if (!customers.isEmpty()) {
            customerField.setVisible(true);
            return;
        }
        try {
            customers = restClient.getList("/imports/customers", new ParameterizedTypeReference<List<CustomerOption>>() {});
            customers = customers != null ? customers : List.of();
            customerField.setItems(customers);
            customerField.setVisible(true);
            if (!customers.isEmpty()) {
                customerField.setValue(customers.getFirst());
            }
        } catch (Exception ex) {
            customers = List.of();
            customerField.setItems(List.of());
            customerField.setVisible(true);
            showError("Kunden fuer den Import konnten nicht geladen werden: " + ex.getMessage());
        }
    }

    private void updateProcessPresentation() {
        ImportProcessRow selectedProcess = processField.getValue();
        loadCustomersIfNeeded();
        if (selectedProcess == null) {
            processInfo.setText("Prozess auswaehlen");
            fileGrid.setItems(List.of());
            return;
        }
        String executionMode = selectedProcess.nativeExecution()
                ? "Nativer NG-Import verfuegbar"
                : "Archivierter Legacy-Sonderfall ohne nativen Parser";
        executeButton.setText(selectedProcess.nativeExecution() ? "Nativen Import ausfuehren" : "Archivierter Sonderfall");
        processInfo.setText(selectedProcess.label()
                + "\n\n"
                + "Status: " + selectedProcess.status()
                + "\n"
                + "Prioritaet: " + selectedProcess.priority()
                + "\n"
                + "Legacy-Quellen: " + joinValues(selectedProcess.legacySources())
                + "\n"
                + "NG-Nachfolger: " + fallback(selectedProcess.successorWorkspace(), "Import-Workspace")
                + "\n\n"
                + selectedProcess.description()
                + "\n\n"
                + "Entscheidung: " + fallback(selectedProcess.decisionSummary(), "-")
                + "\n\n"
                + executionMode
                + "\n\n"
                + selectedProcess.schemaHint());
        updateExecuteState();
    }

    private void refreshFiles() {
        ImportProcessRow selectedProcess = processField.getValue();
        if (selectedProcess == null) {
            fileGrid.setItems(List.of());
            updateExecuteState();
            return;
        }
        try {
            List<ImportFileRow> files = restClient.getList(
                    "/imports/processes/" + selectedProcess.key() + "/files",
                    new ParameterizedTypeReference<List<ImportFileRow>>() {});
            fileGrid.setItems(files != null ? files : List.of());
        } catch (Exception ex) {
            fileGrid.setItems(List.of());
            showError("Dateiliste konnte nicht geladen werden: " + ex.getMessage());
        }
        updateExecuteState();
    }

    private void uploadSelectedFile(String fileName, String mimeType) {
        ImportProcessRow selectedProcess = processField.getValue();
        if (selectedProcess == null) {
            showError("Bitte zuerst einen Prozess auswaehlen.");
            upload.clearFileList();
            return;
        }
        try {
            byte[] content = uploadBuffer.getInputStream().readAllBytes();
            restClient.postMultipart(
                    "/imports/processes/" + selectedProcess.key() + "/files",
                    content,
                    fileName,
                    mimeType,
                    Collections.emptyMap(),
                    ImportFileRow.class
            );
            showSuccess("Datei gespeichert");
            refreshFiles();
        } catch (Exception ex) {
            showError("Upload fehlgeschlagen: " + ex.getMessage());
        } finally {
            upload.clearFileList();
        }
    }

    private void executeSelectedImport() {
        ImportProcessRow selectedProcess = processField.getValue();
        ImportFileRow selectedFile = fileGrid.asSingleSelect().getValue();
        CustomerOption selectedCustomer = customerField.getValue();
        if (selectedProcess == null || selectedFile == null) {
            showError("Bitte Prozess und Datei auswaehlen.");
            return;
        }
        if (selectedProcess.customerRequired() && selectedCustomer == null) {
            showError("Bitte einen Kunden auswaehlen.");
            return;
        }
        try {
            Map<String, Object> payload = new LinkedHashMap<>();
            payload.put("fileName", selectedFile.fileName());
            if (selectedCustomer != null) {
                payload.put("customerId", selectedCustomer.id());
            }
            ImportExecutionResult executionResult = restClient.post(
                    "/imports/processes/" + selectedProcess.key() + "/execute",
                    payload,
                    ImportExecutionResult.class
            );
            showExecutionResult(executionResult);
            refreshFiles();
        } catch (Exception ex) {
            showError("Import konnte nicht gestartet werden: " + ex.getMessage());
        }
    }

    private Anchor buildDownloadLink(ImportFileRow row) {
        ImportProcessRow selectedProcess = processField.getValue();
        String encodedName = URLEncoder.encode(row.fileName(), StandardCharsets.UTF_8);
        StreamResource resource = new StreamResource(
                row.fileName(),
                () -> new ByteArrayInputStream(restClient.getBytes(
                        "/imports/processes/" + selectedProcess.key() + "/files/content?name=" + encodedName
                ))
        );
        Anchor anchor = new Anchor(resource, "");
        anchor.getElement().setAttribute("download", true);
        Button button = new Button("Download");
        button.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        anchor.add(button);
        return anchor;
    }

    private Button buildDeleteButton(ImportFileRow row) {
        Button button = new Button("Loeschen");
        button.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
        button.addClickListener(event -> {
            ImportProcessRow selectedProcess = processField.getValue();
            if (selectedProcess == null) {
                return;
            }
            try {
                String encodedName = URLEncoder.encode(row.fileName(), StandardCharsets.UTF_8);
                restClient.delete("/imports/processes/" + selectedProcess.key() + "/files?name=" + encodedName);
                showSuccess("Datei geloescht");
                refreshFiles();
            } catch (Exception ex) {
                showError("Datei konnte nicht geloescht werden: " + ex.getMessage());
            }
        });
        return button;
    }

    private void updateExecuteState() {
        ImportProcessRow selectedProcess = processField.getValue();
        ImportFileRow selectedFile = fileGrid.asSingleSelect().getValue();
        boolean enabled = selectedProcess != null
                && selectedProcess.nativeExecution()
                && selectedFile != null
                && (!selectedProcess.customerRequired() || customerField.getValue() != null);
        executeButton.setEnabled(enabled);
    }

    private void showExecutionResult(ImportExecutionResult result) {
        if (result == null) {
            resultBox.setText("Keine Rueckgabe vom Importdienst erhalten.");
            return;
        }
        StringBuilder text = new StringBuilder();
        text.append(result.success ? "Erfolg" : "Fehler").append('\n');
        if (result.summary != null) {
            text.append(result.summary).append('\n');
        }
        text.append('\n')
                .append("Angelegt: ").append(result.createdCount).append('\n')
                .append("Aktualisiert: ").append(result.updatedCount).append('\n')
                .append("Geloescht: ").append(result.deletedCount).append('\n')
                .append("Ignoriert: ").append(result.ignoredCount);
        if (result.messages != null && !result.messages.isEmpty()) {
            text.append("\n\nHinweise:\n");
            result.messages.forEach(message -> text.append("- ").append(message).append('\n'));
        }
        resultBox.setText(text.toString().trim());
        if (result.success) {
            showSuccess("Import ausgefuehrt");
        } else {
            showError(result.summary != null ? result.summary : "Import fehlgeschlagen");
        }
    }

    private String formatBytes(long size) {
        if (size >= 1024 * 1024) {
            return String.format("%.1f MB", size / (1024d * 1024d));
        }
        if (size >= 1024) {
            return String.format("%.1f KB", size / 1024d);
        }
        return size + " B";
    }

    private void showSuccess(String message) {
        Notification notification = Notification.show(message, 2500, Notification.Position.BOTTOM_START);
        notification.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
    }

    private void showError(String message) {
        Notification notification = Notification.show(message, 4000, Notification.Position.BOTTOM_START);
        notification.addThemeVariants(NotificationVariant.LUMO_ERROR);
    }

    private String joinValues(List<String> values) {
        if (values == null || values.isEmpty()) {
            return "-";
        }
        return String.join(", ", values);
    }

    private String fallback(String value, String fallback) {
        return value == null || value.isBlank() ? fallback : value;
    }

    private record ImportProcessRow(String key,
                                    String label,
                                    String description,
                                    String schemaHint,
                                    boolean nativeExecution,
                                    boolean customerRequired,
                                    String status,
                                    String priority,
                                    String decisionSummary,
                                    String successorWorkspace,
                                    List<String> legacySources) {
        private String comboLabel() {
            return label + " [" + status + " | " + priority + "]";
        }
    }

    private record ImportFileRow(String fileName, long size, LocalDateTime modifiedAt) {
    }

    private record CustomerOption(Long id, String eid, String companyName) {
        private String label() {
            return (eid != null ? eid + " - " : "") + (companyName != null ? companyName : "Kunde " + id);
        }
    }

    private static class ImportExecutionResult {
        public boolean success;
        public String summary;
        public int createdCount;
        public int updatedCount;
        public int deletedCount;
        public int ignoredCount;
        public List<String> messages;
    }
}
