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

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

import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;

/**
 * JavaFX application entry point for the PDF-Umbenenner GUI inbound adapter.
 * <p>
 * The application starts the editor shell in a clean, unloaded state unless Bootstrap
 * has provided a preloaded startup context. The visible editor surface is delegated to
 * {@link GuiConfigurationEditorWorkspace}.
 *
 * <p>The window title is kept in sync with the workspace's dirty state via the
 * {@code titleUpdateListener} hook. The close-request handler is installed through
 * {@link GuiConfigurationEditorWorkspace#installCloseRequestHandler(Stage)} so that
 * unsaved changes are protected when the user tries to close the window.
 *
 * <p>Beim Schließen des Fensters wird die Anwendung in den Windows System-Tray minimiert.
 * Über das Tray-Kontextmenü kann das Fenster wieder geöffnet oder die Anwendung beendet werden.
 *
 * <p>Nach dem Anzeigen des Hauptfensters startet eine zentrale {@link GuiStatusRefreshTimeline}
 * (1 Hz), die den aktuellen Scheduler-Status liest und alle betroffenen Tabs aktualisiert.
 * Die Timeline wird beim Beenden der Anwendung gestoppt.
 */
public class PdfUmbenennerGuiApplication extends Application {

    private static final Logger LOG = LogManager.getLogger(PdfUmbenennerGuiApplication.class);
    private static final double DEFAULT_WIDTH = 1100;
    private static final double DEFAULT_HEIGHT = 800;

    private SystemTrayManager trayManager;
    private GuiConfigurationEditorWorkspace workspace;
    private GuiStartupContext guiStartupContext;
    private GuiStatusRefreshTimeline refreshTimeline;

    /**
     * Creates a new instance of the JavaFX application.
     */
    public PdfUmbenennerGuiApplication() {
        // Required by JavaFX runtime for reflective instantiation.
    }

    /**
     * Initializes and shows the primary stage.
     * <p>
     * Lädt die Anwendungs-Icons in allen verfügbaren Größen und setzt sie am Fenster.
     * Wires the workspace title-update listener to the stage title so any dirty-state change
     * causes an immediate window-title refresh. Installs the close-request handler that
     * guards unsaved changes and minimizes the window to the system tray instead of closing.
     * <p>
     * Startet nach dem Anzeigen des Fensters die zentrale Status-Refresh-Timeline.
     *
     * @param primaryStage the primary stage provided by the JavaFX runtime; never {@code null}
     */
    @Override
    public void start(Stage primaryStage) {
        LOG.info("GUI: JavaFX-Oberfläche wird initialisiert.");

        // Anwendungs-Icons laden; JavaFX wählt je nach Kontext automatisch die passende Größe
        primaryStage.getIcons().addAll(
                new Image(getClass().getResourceAsStream("/icons/Icon16.png")),
                new Image(getClass().getResourceAsStream("/icons/Icon32.png")),
                new Image(getClass().getResourceAsStream("/icons/Icon64.png")),
                new Image(getClass().getResourceAsStream("/icons/Icon128.png"))
        );

        guiStartupContext = GuiStartupContextHolder.currentOrBlank();
        workspace = new GuiConfigurationEditorWorkspace(guiStartupContext);

        // Wire the title-update listener so the stage title stays in sync with the dirty state.
        workspace.titleUpdateListener = primaryStage::setTitle;

        // Statuszeile anlegen und mit dem Workspace verdrahten
        GuiStatusBar statusBar = new GuiStatusBar(guiStartupContext.applicationVersion());
        workspace.statusBarStateListener = statusBar::applyEditorState;

        // Menüleiste mit Datenbank-Menü („Neue Datenbank anlegen…")
        MenuBar menuBar = buildMenuBar(workspace);

        // Statuszeile unterhalb des Workspace-Inhalts einbetten
        BorderPane outerLayout = new BorderPane();
        outerLayout.setTop(menuBar);
        outerLayout.setCenter(workspace.root());
        outerLayout.setBottom(statusBar.root());

        Scene scene = new Scene(outerLayout, DEFAULT_WIDTH, DEFAULT_HEIGHT);
        primaryStage.setTitle(GuiWindowTitleFormatter.format(workspace.editorState()));
        primaryStage.setScene(scene);

        // Install the close-request handler that protects unsaved changes.
        workspace.installCloseRequestHandler(primaryStage);

        // System-Tray aktivieren: JavaFX-Runtime nicht beenden wenn Fenster versteckt wird
        Platform.setImplicitExit(false);
        trayManager = new SystemTrayManager(primaryStage);
        if (trayManager.install()) {
            installTrayCloseHandler(primaryStage, workspace);
        }

        // Scheduler-Close-Guard als äußerste Schicht: verhindert Beenden während Scheduler aktiv
        installSchedulerCloseGuard(primaryStage);

        primaryStage.setMaximized(true);
        primaryStage.show();

        // Versuche, die zuletzt geladene Konfigurationsdatei automatisch zu laden.
        workspace.autoLoadLastConfiguration();

        // Zentrale Status-Refresh-Timeline starten (1 Hz)
        refreshTimeline = new GuiStatusRefreshTimeline(
                guiStartupContext.schedulerControlUseCase(),
                this::refreshAllTabStates);
        refreshTimeline.start();

        LOG.info("GUI: Hauptfenster erfolgreich angezeigt.");
    }

    /**
     * Called by the JavaFX runtime when the application is stopping.
     * <p>
     * Stoppt die Status-Refresh-Timeline, entfernt das System-Tray-Icon und loggt das Beenden.
     */
    @Override
    public void stop() {
        LOG.info("GUI: JavaFX-Anwendung wird beendet.");
        if (refreshTimeline != null) {
            refreshTimeline.stop();
        }
        if (trayManager != null) {
            trayManager.remove();
        }
    }

    /**
     * Liest den aktuellen Scheduler-Status und aktualisiert alle betroffenen Tabs.
     * <p>
     * Wird von der {@link GuiStatusRefreshTimeline} im Sekundentakt auf dem JavaFX
     * Application Thread aufgerufen. Wenn kein {@link SchedulerControlUseCase} vorhanden
     * ist, wird der Aufruf ohne Fehler übersprungen.
     */
    private void refreshAllTabStates() {
        // Den Use Case nicht aus dem unveränderlichen GuiStartupContext lesen, sondern
        // den zur Laufzeit (z. B. durch Auto-Load) verdrahteten Use Case verwenden.
        // Andernfalls bliebe der Stop-Button dauerhaft deaktiviert, weil updateStatus
        // nie aufgerufen würde.
        workspace.refreshSchedulerStatus();
    }

    /**
     * Baut die Menüleiste für das Hauptfenster auf.
     * <p>
     * Aktuell enthält sie genau einen Eintrag: das Menü „Datenbank" mit der Aktion
     * „Neue Datenbank anlegen…". Diese delegiert an
     * {@link GuiConfigurationEditorWorkspace#requestCreateNewDatabase()}.
     * <p>
     * Der Menüpunkt ist deaktiviert, solange ein Verarbeitungslauf aktiv ist oder
     * bereits eine DB-Anlage läuft. Die Reaktivierung erfolgt automatisch, sobald
     * der Workspace die DB-Busy-Sperre wieder aufhebt.
     *
     * @param workspace der Workspace, an den die Aktionen delegieren; nie {@code null}
     * @return die fertig konfigurierte Menüleiste
     */
    private MenuBar buildMenuBar(GuiConfigurationEditorWorkspace workspace) {
        Menu databaseMenu = new Menu("Datenbank");
        MenuItem createNewItem = new MenuItem("Neue Datenbank anlegen…");
        createNewItem.setOnAction(event -> workspace.requestCreateNewDatabase());
        // Sperre während eines aktiven Verarbeitungslaufs oder einer laufenden DB-Anlage
        createNewItem.disableProperty().bind(workspace.batchRunRunningProperty()
                .or(workspace.dbBusyForDatabaseCreationProperty()));
        databaseMenu.getItems().add(createNewItem);
        return new MenuBar(databaseMenu);
    }

    /**
     * Legt den Scheduler-Close-Guard als äußerste Schicht des Close-Request-Handlers an.
     * <p>
     * Ist kein {@link de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase}
     * vorhanden, bleibt der bestehende Handler unverändert. Ist der Scheduler aktiv
     * (Zustand != {@code STOPPED}), wird das Schließen verhindert und ein
     * Informationsdialog angezeigt. Ist der Scheduler gestoppt, wird der bisherige
     * Handler (SystemTray + Workspace-Dirty-Guard) aufgerufen.
     *
     * @param stage das primäre Fenster; darf nicht {@code null} sein
     */
    private void installSchedulerCloseGuard(Stage stage) {
        EventHandler<WindowEvent> existingHandler = stage.getOnCloseRequest();
        stage.setOnCloseRequest(event -> {
            // Use Case dynamisch über den Workspace lesen, weil der Scheduler erst
            // nach erfolgreichem Datei-Öffnen (z. B. Auto-Load) verdrahtet wird und
            // damit nicht zwingend im unveränderlichen GuiStartupContext steht.
            if (workspace.isSchedulerActive()) {
                event.consume();
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setTitle("Anwendung kann nicht beendet werden");
                alert.setHeaderText(null);
                alert.setContentText(
                        "Ein Lauf ist aktiv oder der Scheduler läuft.\n"
                        + "Bitte beende den Scheduler bzw. warte auf das Ende des Laufs.");
                alert.showAndWait();
                return;
            }
            if (existingHandler != null) {
                existingHandler.handle(event);
            }
        });
    }

    /**
     * Legt einen Close-Request-Handler an, der bei sauberem Zustand das Fenster in den
     * System-Tray minimiert statt es zu schließen.
     * <p>
     * Der vom Workspace installierte Handler wird dabei vorrangig aufgerufen. Nur wenn
     * er das Event nicht konsumiert (sauberer Zustand, keine laufenden Operationen),
     * greift dieser Handler und versteckt das Fenster.
     *
     * @param stage     das primäre Fenster
     * @param workspace der Workspace-Handler, der bereits installiert wurde
     */
    private void installTrayCloseHandler(Stage stage, GuiConfigurationEditorWorkspace workspace) {
        EventHandler<WindowEvent> workspaceHandler = stage.getOnCloseRequest();
        stage.setOnCloseRequest(event -> {
            // Workspace-Handler zuerst: prüft Dirty-State, laufende Operationen usw.
            if (workspaceHandler != null) {
                workspaceHandler.handle(event);
            }
            // Wurde das Event nicht konsumiert, ist der Zustand sauber: Fenster in Tray verstecken
            if (!event.isConsumed()) {
                event.consume();
                LOG.info("GUI: Fenster wird in den System-Tray minimiert.");
                stage.hide();
            }
        });
    }
}
