package sd.dashboard;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import sd.config.SimulationConfig;
import sd.model.VehicleType;

/**
 * Interface Gráfica (GUI) baseada em JavaFX para visualização de telemetria em tempo real.
 * <p>
 * Esta classe atua como a camada de apresentação (View) do sistema. Implementa o padrão
 * <i>Observer</i> (via polling) para refletir o estado do modelo {@link DashboardStatistics}
 * nos componentes visuais.
 * <p>
 * <b>Aspetos Técnicos Relevantes:</b>
 * <ul>
 * <li><b>Concorrência de UI:</b> Utiliza um {@link ScheduledExecutorService} para buscar dados
 * em background e {@link Platform#runLater(Runnable)} para injetar atualizações na
 * <i>JavaFX Application Thread</i>, evitando exceções de "Not on FX application thread".</li>
 * <li><b>Data Binding:</b> Utiliza {@link TableView} com classes internas (DTOs) para
 * renderização tabular eficiente de tipos de veículos e interseções.</li>
 * <li><b>Controlo de Processos:</b> Integra com {@link SimulationProcessManager} para orquestrar
 * o ciclo de vida (spawn/kill) dos processos externos da simulação.</li>
 * </ul>
 */
public class DashboardUI extends Application {

    private DashboardServer server;
    private DashboardStatistics statistics;

    // Global Statistics Labels
    private Label lblVehiclesGenerated;
    private Label lblVehiclesCompleted;
    private Label lblVehiclesInTransit;
    private Label lblAvgSystemTime;
    private Label lblAvgWaitingTime;
    private Label lblLastUpdate;

    // Vehicle Type Table
    private TableView<VehicleTypeRow> vehicleTypeTable;

    // Intersection Table
    private TableView<IntersectionRow> intersectionTable;

    // Update scheduler (Background Thread)
    private ScheduledExecutorService updateScheduler;

    // Configuration controls
    private ComboBox<String> configFileSelector;
    private String selectedConfigFile = "simulation.properties";
    private Label configInfoLabel;

    /**
     * Ponto de entrada da aplicação JavaFX.
     * Configura o Stage primário, inicializa o servidor de backend e constrói a árvore de cena (Scene Graph).
     */
    @Override
    public void start(Stage primaryStage) {
        try {
            // Initialize server
            String configFile = getParameters().getRaw().isEmpty()
                    ? "src/main/resources/simulation.properties"
                    : getParameters().getRaw().get(0);

            SimulationConfig config = new SimulationConfig(configFile);
            server = new DashboardServer(config);
            statistics = server.getStatistics();

            // Start the dashboard server (Backend listening port)
            server.start();

            // Build UI Layout
            BorderPane root = new BorderPane();
            root.getStyleClass().add("root");
            root.setStyle("-fx-background-color: #2b2b2b;");

            // Header (Top)
            VBox header = createHeader();
            root.setTop(header);

            // Main content (Center)
            VBox mainContent = createMainContent();
            root.setCenter(mainContent);

            // Footer (Bottom)
            HBox footer = createFooter();
            root.setBottom(footer);

            // Create scene & apply CSS
            Scene scene = new Scene(root, 1200, 850);
            String cssUrl = getClass().getResource("/dashboard.css").toExternalForm();
            scene.getStylesheets().add(cssUrl);

            primaryStage.setTitle("Traffic Simulation Dashboard - Real-time Statistics");
            primaryStage.setScene(scene);
            primaryStage.show();

            // Start periodic updates loop
            startPeriodicUpdates();

            // Handle window close (Graceful shutdown)
            primaryStage.setOnCloseRequest(event -> {
                shutdown();
            });

        } catch (Exception e) {
            showErrorAlert("Failed to start Dashboard Server", e.getMessage());
            e.printStackTrace();
            Platform.exit();
        }
    }

    private VBox createHeader() {
        VBox header = new VBox(10);
        header.getStyleClass().add("header");
        header.setAlignment(Pos.CENTER);

        Label title = new Label("DISTRIBUTED TRAFFIC SIMULATION DASHBOARD");
        title.getStyleClass().add("header-title");

        Label subtitle = new Label("Real-time Statistics and Monitoring");
        subtitle.getStyleClass().add("header-subtitle");

        // Configuration Panel
        VBox configPanel = createConfigurationPanel();

        // Control Buttons
        HBox controls = new HBox(15);
        controls.setAlignment(Pos.CENTER);

        Button btnStart = new Button("START SIMULATION");
        btnStart.getStyleClass().add("button-start");

        Button btnStop = new Button("STOP SIMULATION");
        btnStop.getStyleClass().add("button-stop");
        btnStop.setDisable(true);

        SimulationProcessManager processManager = new SimulationProcessManager();

        btnStart.setOnAction(e -> {
            try {
                // Passar o ficheiro de configuração selecionado
                processManager.setConfigFile(selectedConfigFile);
                processManager.startSimulation();
                
                // Toggle UI state
                btnStart.setDisable(true);
                btnStop.setDisable(false);
                configFileSelector.setDisable(true); // Bloquear mudanças durante simulação
            } catch (IOException ex) {
                showErrorAlert("Start Failed", "Could not start simulation processes: " + ex.getMessage());
            }
        });

        btnStop.setOnAction(e -> {
            processManager.stopSimulation();
            
            // Toggle UI state
            btnStart.setDisable(false);
            btnStop.setDisable(true);
            configFileSelector.setDisable(false); // Desbloquear para nova simulação
        });

        controls.getChildren().addAll(btnStart, btnStop);

        header.getChildren().addAll(title, subtitle, configPanel, controls);

        return header;
    }

    /**
     * Cria o painel de configuração com seleção de cenário e parâmetros.
     */
    private VBox createConfigurationPanel() {
        VBox configBox = new VBox(10);
        configBox.setAlignment(Pos.CENTER);
        configBox.setPadding(new Insets(10));
        configBox.setStyle("-fx-background-color: rgba(255, 255, 255, 0.05); -fx-background-radius: 5;");

        Label configLabel = new Label("Configuração da Simulação");
        configLabel.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;");

        HBox configControls = new HBox(20);
        configControls.setAlignment(Pos.CENTER);

        // Scenario selector
        VBox scenarioBox = new VBox(5);
        scenarioBox.setAlignment(Pos.CENTER_LEFT);
        Label scenarioLabel = new Label("Cenário:");
        scenarioLabel.setStyle("-fx-font-size: 12px;");
        
        configFileSelector = new ComboBox<>();
        configFileSelector.getItems().addAll(
            "simulation.properties",
            "simulation-low.properties",
            "simulation-medium.properties",
            "simulation-high.properties"
        );
        configFileSelector.setValue("simulation.properties");
        configFileSelector.setOnAction(e -> {
            selectedConfigFile = configFileSelector.getValue();
            updateConfigInfo();
            System.out.println("Configuração selecionada: " + selectedConfigFile);
        });
        
        scenarioBox.getChildren().addAll(scenarioLabel, configFileSelector);
        configControls.getChildren().add(scenarioBox);

        // Routing policy selector
        VBox routingBox = new VBox(5);
        routingBox.setAlignment(Pos.CENTER_LEFT);
        Label routingLabel = new Label("Política de Roteamento:");
        routingLabel.setStyle("-fx-font-size: 12px;");
        
        ComboBox<String> routingPolicySelector = new ComboBox<>();
        routingPolicySelector.getItems().addAll(
            "RANDOM",
            "SHORTEST_PATH",
            "LEAST_CONGESTED"
        );
        routingPolicySelector.setValue("RANDOM");
        routingPolicySelector.setOnAction(e -> {
            String selectedPolicy = routingPolicySelector.getValue();
            System.out.println("Política de roteamento selecionada: " + selectedPolicy);
            sendRoutingPolicyChange(selectedPolicy);
        });
        
        routingBox.getChildren().addAll(routingLabel, routingPolicySelector);
        configControls.getChildren().add(routingBox);

        // Advanced configuration button
        VBox buttonBox = new VBox(5);
        buttonBox.setAlignment(Pos.CENTER_LEFT);
        Label spacerLabel = new Label(" ");
        spacerLabel.setStyle("-fx-font-size: 12px;");
        
        Button btnAdvancedConfig = new Button("Configuração Avançada...");
        btnAdvancedConfig.setStyle("-fx-font-size: 11px;");
        btnAdvancedConfig.setOnAction(e -> {
            ConfigurationDialog.showAdvancedConfig((Stage) configBox.getScene().getWindow());
        });
        
        Button btnBatchAnalysis = new Button("Análise em Lote...");
        btnBatchAnalysis.setStyle("-fx-font-size: 11px;");
        btnBatchAnalysis.setOnAction(e -> {
            BatchAnalysisDialog.show((Stage) configBox.getScene().getWindow(), statistics);
        });
        
        buttonBox.getChildren().addAll(spacerLabel, btnAdvancedConfig, btnBatchAnalysis);
        configControls.getChildren().add(buttonBox);

        // Configuration info display
        configInfoLabel = new Label();
        configInfoLabel.setStyle("-fx-font-size: 11px; -fx-text-fill: #aaaaaa;");
        configInfoLabel.setWrapText(true);
        configInfoLabel.setMaxWidth(800);
        configInfoLabel.setAlignment(Pos.CENTER);
        updateConfigInfo();

        configBox.getChildren().addAll(configLabel, configControls, configInfoLabel);
        return configBox;
    }

    private VBox createMainContent() {
        VBox mainContent = new VBox(20);
        mainContent.setPadding(new Insets(20));

        // Global Statistics Panel
        VBox globalStatsCard = createGlobalStatisticsPanel();

        // Tables Container
        HBox tablesContainer = new HBox(20);
        tablesContainer.setAlignment(Pos.TOP_CENTER);

        // Vehicle Type Statistics Panel
        VBox vehicleTypeCard = createVehicleTypePanel();
        HBox.setHgrow(vehicleTypeCard, Priority.ALWAYS);

        // Intersection Statistics Panel
        VBox intersectionCard = createIntersectionPanel();
        HBox.setHgrow(intersectionCard, Priority.ALWAYS);

        tablesContainer.getChildren().addAll(vehicleTypeCard, intersectionCard);

        mainContent.getChildren().addAll(globalStatsCard, tablesContainer);

        return mainContent;
    }

    private VBox createGlobalStatisticsPanel() {
        VBox card = new VBox();
        card.getStyleClass().add("card");

        // Card Header
        HBox cardHeader = new HBox();
        cardHeader.getStyleClass().add("card-header");
        Label cardTitle = new Label("Global Statistics");
        cardTitle.getStyleClass().add("card-title");
        cardHeader.getChildren().add(cardTitle);

        // Card Content
        GridPane grid = new GridPane();
        grid.getStyleClass().add("card-content");
        grid.setHgap(40);
        grid.setVgap(15);
        grid.setAlignment(Pos.CENTER);

        // Initialize labels
        lblVehiclesGenerated = createStatValueLabel("0");
        lblVehiclesCompleted = createStatValueLabel("0");
        lblVehiclesInTransit = createStatValueLabel("0");
        lblAvgSystemTime = createStatValueLabel("0.00 s");
        lblAvgWaitingTime = createStatValueLabel("0.00 s");

        // Add labels with descriptions
        addStatRow(grid, 0, 0, "Total Vehicles Generated", lblVehiclesGenerated);
        addStatRow(grid, 1, 0, "Total Vehicles Completed", lblVehiclesCompleted);
        addStatRow(grid, 2, 0, "Vehicles In Transit", lblVehiclesInTransit);
        addStatRow(grid, 0, 1, "Average System Time", lblAvgSystemTime);
        addStatRow(grid, 1, 1, "Average Waiting Time", lblAvgWaitingTime);

        card.getChildren().addAll(cardHeader, grid);
        return card;
    }

    private VBox createVehicleTypePanel() {
        VBox card = new VBox();
        card.getStyleClass().add("card");

        // Card Header
        HBox cardHeader = new HBox();
        cardHeader.getStyleClass().add("card-header");
        Label cardTitle = new Label("Vehicle Type Statistics");
        cardTitle.getStyleClass().add("card-title");
        cardHeader.getChildren().add(cardTitle);

        // Table
        vehicleTypeTable = new TableView<>();
        vehicleTypeTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        vehicleTypeTable.setPrefHeight(300);

        TableColumn<VehicleTypeRow, String> typeCol = new TableColumn<>("Vehicle Type");
        typeCol.setCellValueFactory(new PropertyValueFactory<>("vehicleType"));

        TableColumn<VehicleTypeRow, Integer> countCol = new TableColumn<>("Count");
        countCol.setCellValueFactory(new PropertyValueFactory<>("count"));

        TableColumn<VehicleTypeRow, String> avgWaitCol = new TableColumn<>("Avg Wait Time");
        avgWaitCol.setCellValueFactory(new PropertyValueFactory<>("avgWaitTime"));

        vehicleTypeTable.getColumns().addAll(typeCol, countCol, avgWaitCol);

        card.getChildren().addAll(cardHeader, vehicleTypeTable);
        return card;
    }

    private VBox createIntersectionPanel() {
        VBox card = new VBox();
        card.getStyleClass().add("card");

        // Card Header
        HBox cardHeader = new HBox();
        cardHeader.getStyleClass().add("card-header");
        Label cardTitle = new Label("Intersection Statistics");
        cardTitle.getStyleClass().add("card-title");
        cardHeader.getChildren().add(cardTitle);

        // Table
        intersectionTable = new TableView<>();
        intersectionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        intersectionTable.setPrefHeight(300);

        TableColumn<IntersectionRow, String> idCol = new TableColumn<>("Intersection ID");
        idCol.setCellValueFactory(new PropertyValueFactory<>("intersectionId"));

        TableColumn<IntersectionRow, Integer> arrivalsCol = new TableColumn<>("Total Arrivals");
        arrivalsCol.setCellValueFactory(new PropertyValueFactory<>("arrivals"));

        TableColumn<IntersectionRow, Integer> departuresCol = new TableColumn<>("Total Departures");
        departuresCol.setCellValueFactory(new PropertyValueFactory<>("departures"));

        TableColumn<IntersectionRow, Integer> queueCol = new TableColumn<>("Current Queue");
        queueCol.setCellValueFactory(new PropertyValueFactory<>("queueSize"));

        intersectionTable.getColumns().addAll(idCol, arrivalsCol, departuresCol, queueCol);

        card.getChildren().addAll(cardHeader, intersectionTable);
        return card;
    }

    private HBox createFooter() {
        HBox footer = new HBox(10);
        footer.getStyleClass().add("footer");
        footer.setAlignment(Pos.CENTER_LEFT);

        Label statusLabel = new Label("Status:");
        statusLabel.getStyleClass().add("footer-text");
        statusLabel.setStyle("-fx-font-weight: bold;");

        Circle statusIndicator = new Circle(6);
        statusIndicator.setFill(javafx.scene.paint.Color.LIME);

        Label statusText = new Label("Connected and Receiving Data");
        statusText.getStyleClass().add("footer-text");

        lblLastUpdate = new Label("Last Update: --:--:--");
        lblLastUpdate.getStyleClass().add("footer-text");

        Region spacer = new Region();
        HBox.setHgrow(spacer, Priority.ALWAYS);

        footer.getChildren().addAll(statusLabel, statusIndicator, statusText, spacer, lblLastUpdate);

        return footer;
    }

    private Label createStatValueLabel(String initialValue) {
        Label label = new Label(initialValue);
        label.getStyleClass().add("stat-value");
        return label;
    }

    private void addStatRow(GridPane grid, int row, int colGroup, String description, Label valueLabel) {
        VBox container = new VBox(5);
        container.setAlignment(Pos.CENTER_LEFT);

        Label descLabel = new Label(description);
        descLabel.getStyleClass().add("stat-label");

        container.getChildren().addAll(descLabel, valueLabel);

        grid.add(container, colGroup, row);
    }

    /**
     * Inicia o ciclo de polling em background.
     * Atualiza a UI a cada 100ms.
     */
    private void startPeriodicUpdates() {
        updateScheduler = Executors.newSingleThreadScheduledExecutor();
        updateScheduler.scheduleAtFixedRate(() -> {
            // Crucial: Encapsular atualização de UI em Platform.runLater
            // para garantir execução na JavaFX Application Thread
            Platform.runLater(this::updateUI);
        }, 0, 100, TimeUnit.MILLISECONDS);
    }

    /**
     * Sincroniza o estado atual do objeto Statistics com os controlos JavaFX.
     * Chamado periodicamente pela thread de UI.
     */
    private void updateUI() {
        // Update global statistics
        lblVehiclesGenerated.setText(String.valueOf(statistics.getTotalVehiclesGenerated()));
        lblVehiclesCompleted.setText(String.valueOf(statistics.getTotalVehiclesCompleted()));
        lblVehiclesInTransit.setText(String.valueOf(
                statistics.getTotalVehiclesGenerated() - statistics.getTotalVehiclesCompleted()));
        lblAvgSystemTime.setText(String.format("%.2f s", statistics.getAverageSystemTime() / 1000.0));
        lblAvgWaitingTime.setText(String.format("%.2f s", statistics.getAverageWaitingTime() / 1000.0));
        lblLastUpdate.setText(String.format("Last Update: %tT", statistics.getLastUpdateTime()));

        // Update vehicle type table
        vehicleTypeTable.getItems().clear();
        for (VehicleType type : VehicleType.values()) {
            int count = statistics.getVehicleTypeCount(type);
            double avgWait = statistics.getAverageWaitingTimeByType(type);
            vehicleTypeTable.getItems().add(new VehicleTypeRow(
                    type.toString(), count, String.format("%.2f s", avgWait / 1000.0)));
        }

        // Update intersection table
        intersectionTable.getItems().clear();
        Map<String, DashboardStatistics.IntersectionStats> intersectionStats = statistics.getAllIntersectionStats();
        for (DashboardStatistics.IntersectionStats stats : intersectionStats.values()) {
            intersectionTable.getItems().add(new IntersectionRow(
                    stats.getIntersectionId(),
                    stats.getTotalArrivals(),
                    stats.getTotalDepartures(),
                    stats.getCurrentQueueSize()));
        }
    }

    /**
     * Atualiza a informação exibida sobre a configuração selecionada.
     */
    private void updateConfigInfo() {
        String info = "";
        switch (selectedConfigFile) {
            case "simulation-low.properties":
                info = "CARGA BAIXA: 0.2 veículos/s (~720/hora) | Sem congestionamento esperado";
                break;
            case "simulation-medium.properties":
                info = "CARGA MÉDIA: 0.5 veículos/s (~1800/hora) | Algum congestionamento esperado";
                break;
            case "simulation-high.properties":
                info = "CARGA ALTA: 1.0 veículo/s (~3600/hora) | Congestionamento significativo esperado";
                break;
            default:
                info = "⚙️ CONFIGURAÇÃO PADRÃO: Verificar ficheiro para parâmetros";
                break;
        }
        configInfoLabel.setText(info);
    }

    private void shutdown() {
        System.out.println("Shutting down Dashboard UI...");

        if (updateScheduler != null && !updateScheduler.isShutdown()) {
            updateScheduler.shutdownNow();
        }

        if (server != null) {
            server.stop();
        }

        Platform.exit();
    }

    private void showErrorAlert(String title, String message) {
        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle(title);
        alert.setHeaderText(null);
        alert.setContentText(message);
        alert.showAndWait();
    }

    /**
     * Envia mensagem para o servidor do dashboard que notificará o coordenador.
     * Usa uma abordagem indireta: salva a política desejada e o coordenador lerá na próxima geração.
     */
    private void sendRoutingPolicyChange(String newPolicy) {
        // Store the policy change request in statistics
        // The coordinator will check this periodically
        if (server != null && statistics != null) {
            statistics.setRequestedRoutingPolicy(newPolicy);
            System.out.println("Política de roteamento solicitada: " + newPolicy);
            System.out.println("  - A mudança será aplicada pelo coordenador na próxima atualização");
            
            // Mostrar confirmação visual
            Platform.runLater(() -> {
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setTitle("Política Solicitada");
                alert.setHeaderText(null);
                alert.setContentText("Política de roteamento solicitada: " + newPolicy + "\nSerá aplicada em breve.");
                alert.show();
            });
        } else {
            Platform.runLater(() -> {
                showErrorAlert("Erro", "Dashboard não está conectado. Inicie a simulação primeiro.");
            });
        }
    }

    public static void main(String[] args) {
        launch(args);
    }

    // --- DTOs para Data Binding nas Tabelas ---

    /** DTO para linhas da tabela de Tipos de Veículo. */
    public static class VehicleTypeRow {
        private final String vehicleType;
        private final int count;
        private final String avgWaitTime;

        public VehicleTypeRow(String vehicleType, int count, String avgWaitTime) {
            this.vehicleType = vehicleType;
            this.count = count;
            this.avgWaitTime = avgWaitTime;
        }

        public String getVehicleType() { return vehicleType; }
        public int getCount() { return count; }
        public String getAvgWaitTime() { return avgWaitTime; }
    }

    /** DTO para linhas da tabela de Interseções. */
    public static class IntersectionRow {
        private final String intersectionId;
        private final int arrivals;
        private final int departures;
        private final int queueSize;

        public IntersectionRow(String intersectionId, int arrivals, int departures, int queueSize) {
            this.intersectionId = intersectionId;
            this.arrivals = arrivals;
            this.departures = departures;
            this.queueSize = queueSize;
        }

        public String getIntersectionId() { return intersectionId; }
        public int getArrivals() { return arrivals; }
        public int getDepartures() { return departures; }
        public int getQueueSize() { return queueSize; }
    }
}