package de.gecheckt.pdf.umbenenner.adapter.in.gui;

import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase;
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerSessionTotals;
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStartException;
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerState;
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus;
import de.gecheckt.pdf.umbenenner.application.port.out.RunSummary;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.control.Tab;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;

/**
 * Fünfter Haupt-Tab des JavaFX-Editorfensters: die Scheduler-Steuerungsansicht.
 * <p>
 * Zeigt den aktuellen Zustand des automatischen Schedulers und erlaubt dessen
 * Steuerung über {@link SchedulerControlUseCase}. Der Tab-Inhalt wird im Sekundentakt
 * durch {@link #updateStatus(SchedulerStatus)} aktualisiert, das von der zentralen
 * {@link GuiStatusRefreshTimeline} aufgerufen wird.
 *
 * <h2>Bereiche</h2>
 * <ul>
 *   <li><strong>Scheduler-Steuerung</strong>: Status-Anzeige (● Aktiv / ○ Gestoppt),
 *       Start-/Stopp-Schaltflächen, Countdown bis zum nächsten Lauf,
 *       Letzter-Lauf-Info, Fehlermeldung und Intervall-Konfiguration.</li>
 * </ul>
 *
 * <h2>Threading</h2>
 * <p>Alle öffentlichen Methoden müssen auf dem JavaFX Application Thread aufgerufen
 * werden. Start-, Stopp- und Speichern-Aktionen werden auf einem dedizierten
 * Hintergrund-Worker-Thread ({@code gui-scheduler-control}) ausgeführt.
 */
public final class GuiSchedulerTab {
    private static final String HEADER_LABEL_STYLE = "-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #7f8c8d;";



    private static final Logger LOG = LogManager.getLogger(GuiSchedulerTab.class);

    private static final String TAB_TITLE = "Scheduler";

    /** Mindestwert für das konfigurierbare Ausführungsintervall. */
    static final int MIN_INTERVAL_SECONDS = 30;

    private static final DateTimeFormatter TIME_FORMATTER =
            DateTimeFormatter.ofPattern("HH:mm").withZone(ZoneId.systemDefault());

    private final Tab tab = new Tab(TAB_TITLE);
    // Not final: may be updated via onSchedulerAvailable after the tab was created without a use
    // case (e.g., when auto-load initialises the scheduler after the workspace was already built).
    // Declared volatile so worker-thread reads (executeStart/Stop) see the write from the FX thread.
    private volatile Optional<SchedulerControlUseCase> schedulerUseCase;
    private final Supplier<Boolean> isConfigDirty;

    // -------------------------------------------------------------------------
    // Bereich 1: Scheduler-Steuerung
    // -------------------------------------------------------------------------

    private final Label statusLabel = new Label("○ Gestoppt");
    private final Button startButton = new Button("Scheduler starten");
    private final Button stopButton = new Button("Scheduler stoppen");
    private final Label nextTickLabel = new Label();
    private final Label lastRunLabel = new Label("Noch kein Lauf in dieser Sitzung.");
    private final Label sessionTotalsLabel = new Label();
    private final Label lastErrorLabel = new Label();
    private final TextField intervalField = new TextField();
    private final Label intervalValidationLabel = new Label();

    private final ExecutorService workerExecutor = Executors.newSingleThreadExecutor(r -> {
        Thread t = new Thread(r, "gui-scheduler-control");
        t.setDaemon(true);
        return t;
    });

    /**
     * Erstellt einen neuen Scheduler-Tab.
     *
     * @param schedulerUseCase optionaler Use Case zur Scheduler-Steuerung;
     *                          {@code null} wird als leer behandelt
     * @param isConfigDirty    Supplier der {@code true} zurückgibt wenn der
     *                          Konfigurationseditor ungespeicherte Änderungen hat;
     *                          {@code null} wird als immer {@code false} behandelt
     */
    public GuiSchedulerTab(
            Optional<SchedulerControlUseCase> schedulerUseCase,
            Supplier<Boolean> isConfigDirty) {
        this.schedulerUseCase = Objects.requireNonNullElse(schedulerUseCase, Optional.empty());
        this.isConfigDirty = isConfigDirty != null ? isConfigDirty : () -> false;
        tab.setClosable(false);
        buildUi();
        applyInitialState();
    }

    /**
     * Liefert den JavaFX-Tab-Knoten für den Einhang in das {@code TabPane}.
     *
     * @return Tab-Knoten; nie {@code null}
     */
    public Tab tab() {
        return tab;
    }

    /**
     * Macht den Scheduler-Use-Case für diesen Tab verfügbar, nachdem er nach einem
     * erfolgreichen Datei-Öffnen initialisiert wurde.
     * <p>
     * Wird vom Workspace auf dem JavaFX Application Thread aufgerufen, nachdem der
     * {@link GuiApplicationContextInitializer} auf einem Hintergrund-Thread einen
     * {@link SchedulerControlUseCase} geliefert hat. Hat keine Wirkung, wenn bereits
     * ein Use Case vorhanden ist.
     * <p>
     * Muss auf dem JavaFX Application Thread aufgerufen werden.
     *
     * @param useCase der neu initialisierte Use Case; darf nicht {@code null} sein
     */
    public void onSchedulerAvailable(SchedulerControlUseCase useCase) {
        if (schedulerUseCase.isPresent()) {
            return;
        }
        schedulerUseCase = Optional.of(useCase);
        intervalField.setText(String.valueOf(useCase.getIntervalSeconds()));
        intervalField.setEditable(true);
        intervalField.setDisable(false);
        startButton.setDisable(false);
        startButton.setTooltip(null);
    }

    /**
     * Gibt den aktuell verdrahteten Scheduler-Use-Case zurück.
     * <p>
     * Wird von der zentralen Status-Refresh-Timeline benötigt, weil der Use Case
     * erst nach erfolgreichem Datei-Öffnen verfügbar wird (z. B. durch Auto-Load
     * der zuletzt geladenen Konfiguration) und damit nicht im
     * unveränderlichen {@code GuiStartupContext} steht.
     *
     * @return aktueller Use Case oder {@code Optional.empty()} wenn keiner verdrahtet ist
     */
    public Optional<SchedulerControlUseCase> currentSchedulerUseCase() {
        return schedulerUseCase;
    }

    /**
     * Aktualisiert alle Tab-Elemente anhand des aktuellen Scheduler-Status.
     * <p>
     * Wird von der {@link GuiStatusRefreshTimeline} im Sekundentakt auf dem
     * JavaFX Application Thread aufgerufen. Implementiert alle in der Spezifikation
     * definierten Button-Zustände, Label-Texte und Sichtbarkeitsregeln.
     *
     * @param status aktueller Scheduler-Status; darf nicht {@code null} sein
     */
    public void updateStatus(SchedulerStatus status) {
        updateStatusLabel(status);
        updateButtons(status);
        updateNextTickLabel(status);
        updateLastRunLabel(status);
        updateSessionTotalsLabel(status);
        updateLastErrorLabel(status);
        updateIntervalFieldEditability(status);
    }

    // -------------------------------------------------------------------------
    // UI-Aufbau
    // -------------------------------------------------------------------------

    private void buildUi() {
        VBox controlArea = buildControlArea();
        tab.setContent(controlArea);
        wireActions();
    }

    private VBox buildControlArea() {
        statusLabel.setStyle(HEADER_LABEL_STYLE);

        stopButton.setDisable(true);
        HBox buttonBox = new HBox(10, startButton, stopButton);

        nextTickLabel.setVisible(false);
        nextTickLabel.setManaged(false);

        lastRunLabel.setWrapText(true);

        sessionTotalsLabel.setWrapText(true);
        sessionTotalsLabel.setStyle("-fx-text-fill: #7f8c8d;");
        sessionTotalsLabel.setVisible(false);
        sessionTotalsLabel.setManaged(false);

        lastErrorLabel.setStyle("-fx-text-fill: #c0392b;");
        lastErrorLabel.setWrapText(true);
        lastErrorLabel.setVisible(false);
        lastErrorLabel.setManaged(false);

        Label intervalLabel = new Label("Intervall (Sekunden):");
        intervalField.setPrefColumnCount(10);
        HBox intervalBox = new HBox(10, intervalLabel, intervalField);
        intervalBox.setAlignment(Pos.CENTER_LEFT);

        intervalValidationLabel.setStyle("-fx-text-fill: #c0392b; -fx-font-size: 11px;");
        intervalValidationLabel.setWrapText(true);
        intervalValidationLabel.setVisible(false);
        intervalValidationLabel.setManaged(false);

        VBox controlArea = new VBox(12,
                statusLabel,
                buttonBox,
                nextTickLabel,
                lastRunLabel,
                sessionTotalsLabel,
                lastErrorLabel,
                new Separator(),
                intervalBox,
                intervalValidationLabel);
        controlArea.setPadding(new Insets(16));
        return controlArea;
    }

    private void wireActions() {
        startButton.setOnAction(e -> executeStart());
        stopButton.setOnAction(e -> executeStop());

        intervalField.focusedProperty().addListener((obs, wasFocused, focused) -> {
            if (!focused) {
                validateAndSaveInterval();
            }
        });
    }

    private void applyInitialState() {
        if (schedulerUseCase.isEmpty()) {
            startButton.setDisable(true);
            startButton.setTooltip(new Tooltip("Anwendung nicht laufbereit"));
            stopButton.setDisable(true);
            intervalField.setEditable(false);
            intervalField.setDisable(true);
        } else {
            intervalField.setText(String.valueOf(schedulerUseCase.get().getIntervalSeconds()));
        }
    }

    // -------------------------------------------------------------------------
    // updateStatus-Hilfsmethoden
    // -------------------------------------------------------------------------

    private void updateStatusLabel(SchedulerStatus status) {
        switch (status.state()) {
            case STOPPED -> {
                statusLabel.setText("○ Gestoppt");
                statusLabel.setStyle(HEADER_LABEL_STYLE);
            }
            case STARTING -> {
                statusLabel.setText("⟳ Wird gestartet…");
                statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #e67e22;");
            }
            case RUNNING_IDLE -> {
                statusLabel.setText("● Aktiv");
                statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #27ae60;");
            }
            case RUNNING_BATCH_ACTIVE -> {
                statusLabel.setText("● Aktiv – Lauf aktiv");
                statusLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: #27ae60;");
            }
            case STOPPING_BATCH_ACTIVE -> {
                statusLabel.setText("○ Gestoppt – aktueller Lauf läuft noch");
                statusLabel.setStyle(HEADER_LABEL_STYLE);
            }
        }
    }

    private void updateButtons(SchedulerStatus status) {
        boolean noUseCase = schedulerUseCase.isEmpty();
        boolean configDirty = Boolean.TRUE.equals(isConfigDirty.get());

        switch (status.state()) {
            case STOPPED -> {
                stopButton.setDisable(true);
                if (noUseCase) {
                    startButton.setDisable(true);
                    startButton.setTooltip(new Tooltip("Anwendung nicht laufbereit"));
                } else if (configDirty) {
                    startButton.setDisable(true);
                    startButton.setTooltip(new Tooltip("Bitte Konfiguration speichern"));
                } else {
                    startButton.setDisable(false);
                    startButton.setTooltip(null);
                }
            }
            case STARTING -> {
                startButton.setDisable(true);
                stopButton.setDisable(true);
            }
            case RUNNING_IDLE, RUNNING_BATCH_ACTIVE -> {
                startButton.setDisable(true);
                startButton.setTooltip(null);
                stopButton.setDisable(false);
            }
            case STOPPING_BATCH_ACTIVE -> {
                startButton.setDisable(true);
                stopButton.setDisable(true);
            }
        }
    }

    private void updateNextTickLabel(SchedulerStatus status) {
        if (status.state() == SchedulerState.RUNNING_IDLE && status.nextTickAt().isPresent()) {
            long remaining = ChronoUnit.SECONDS.between(Instant.now(), status.nextTickAt().get());
            if (remaining > 0) {
                long minutes = remaining / 60;
                long seconds = remaining % 60;
                nextTickLabel.setText(String.format("Nächster Lauf in: %02d:%02d", minutes, seconds));
            } else {
                nextTickLabel.setText("Lauf steht bevor…");
            }
            nextTickLabel.setVisible(true);
            nextTickLabel.setManaged(true);
        } else {
            nextTickLabel.setVisible(false);
            nextTickLabel.setManaged(false);
        }
    }

    private void updateLastRunLabel(SchedulerStatus status) {
        if (status.lastRunEndedAt().isPresent() && status.lastRunSummary().isPresent()) {
            Instant endedAt = status.lastRunEndedAt().get();
            RunSummary summary = status.lastRunSummary().get();
            String timeStr = TIME_FORMATTER.format(endedAt);
            boolean noDocuments = summary.successCount() == 0
                    && summary.failedCount() == 0;
            if (noDocuments) {
                lastRunLabel.setText("Letzter Lauf: " + timeStr + " – keine neuen Dokumente");
            } else {
                lastRunLabel.setText("Letzter Lauf: " + timeStr + " – "
                        + summary.successCount() + " verarbeitet, "
                        + summary.failedCount() + " Fehler");
            }
        } else {
            lastRunLabel.setText("Noch kein Lauf in dieser Sitzung.");
        }
    }

    private void updateSessionTotalsLabel(SchedulerStatus status) {
        Optional<SchedulerSessionTotals> totals = status.sessionTotals();
        if (totals.isPresent()) {
            SchedulerSessionTotals t = totals.get();
            sessionTotalsLabel.setText("Seit Scheduler-Start: "
                    + t.successCount() + " verarbeitet, "
                    + t.failedCount() + " Fehler");
            sessionTotalsLabel.setVisible(true);
            sessionTotalsLabel.setManaged(true);
        } else {
            sessionTotalsLabel.setVisible(false);
            sessionTotalsLabel.setManaged(false);
        }
    }

    private void updateLastErrorLabel(SchedulerStatus status) {
        Optional<String> lastError = status.lastError();
        if (lastError.isPresent() && !lastError.get().isBlank()) {
            lastErrorLabel.setText("Fehler: " + lastError.get());
            lastErrorLabel.setVisible(true);
            lastErrorLabel.setManaged(true);
        } else {
            lastErrorLabel.setVisible(false);
            lastErrorLabel.setManaged(false);
        }
    }

    private void updateIntervalFieldEditability(SchedulerStatus status) {
        boolean editable = status.state() == SchedulerState.STOPPED
                && schedulerUseCase.isPresent()
                && !Boolean.TRUE.equals(isConfigDirty.get());
        intervalField.setEditable(editable);
        intervalField.setDisable(!editable);
    }

    // -------------------------------------------------------------------------
    // Aktions-Handler
    // -------------------------------------------------------------------------

    private void executeStart() {
        LOG.info("GUI: Scheduler-Start angefordert.");
        startButton.setDisable(true);
        stopButton.setDisable(true);
        workerExecutor.submit(() -> schedulerUseCase.ifPresent(uc -> {
            try {
                uc.start();
                LOG.info("GUI: Scheduler erfolgreich gestartet.");
            } catch (SchedulerStartException e) {
                LOG.warn("GUI: Scheduler-Start fehlgeschlagen: {}", e.getMessage());
                Platform.runLater(() -> showStartErrorAlert(e.getMessage()));
            } catch (RuntimeException e) {
                LOG.error("GUI: Unerwarteter Fehler beim Starten des Schedulers.", e);
                Platform.runLater(() -> showStartErrorAlert("Unerwarteter Fehler: " + e.getMessage()));
            }
        }));
    }

    private void executeStop() {
        LOG.info("GUI: Scheduler-Stopp angefordert.");
        startButton.setDisable(true);
        stopButton.setDisable(true);
        workerExecutor.submit(() -> schedulerUseCase.ifPresent(uc -> {
            try {
                uc.stop();
                LOG.info("GUI: Scheduler gestoppt.");
            } catch (RuntimeException e) {
                LOG.error("GUI: Unerwarteter Fehler beim Stoppen des Schedulers.", e);
            }
        }));
    }

    private void validateAndSaveInterval() {
        String text = intervalField.getText() == null ? "" : intervalField.getText().trim();
        try {
            int value = Integer.parseInt(text);
            if (value < MIN_INTERVAL_SECONDS) {
                showIntervalValidationError(
                        "Mindestintervall ist " + MIN_INTERVAL_SECONDS + " Sekunden.");
            } else {
                hideIntervalValidationError();
                workerExecutor.submit(() -> schedulerUseCase.ifPresent(uc -> {
                    try {
                        uc.saveIntervalSeconds(value);
                    } catch (RuntimeException e) {
                        LOG.warn("GUI: Fehler beim Speichern des Scheduler-Intervalls: {}", e.getMessage());
                        Platform.runLater(() -> showIntervalValidationError(
                                "Speichern fehlgeschlagen: " + e.getMessage()));
                    }
                }));
            }
        } catch (NumberFormatException e) {
            showIntervalValidationError("Bitte eine ganze Zahl eingeben.");
        }
    }

    private void showIntervalValidationError(String message) {
        intervalValidationLabel.setText(message);
        intervalValidationLabel.setVisible(true);
        intervalValidationLabel.setManaged(true);
    }

    private void hideIntervalValidationError() {
        intervalValidationLabel.setVisible(false);
        intervalValidationLabel.setManaged(false);
    }

    private static void showStartErrorAlert(String message) {
        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle("Scheduler-Start fehlgeschlagen");
        alert.setHeaderText("Der Scheduler konnte nicht gestartet werden.");
        alert.setContentText(message != null ? message : "Unbekannter Fehler.");
        alert.showAndWait();
    }
}
