package sd.dashboard;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.Spinner;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import sd.analysis.MultiRunAnalyzer;
import sd.analysis.SimulationRunResult;
import sd.model.VehicleType;

/**
 * Diálogo para configuração e execução de análise de desempenho em lote (Batch Processing).
 * <p>
 * Esta classe fornece uma interface gráfica para automatizar múltiplas execuções da simulação
 * sob diferentes cenários de carga. É responsável por:
 * <ol>
 * <li>Orquestrar o ciclo de vida dos processos de simulação (start/stop/wait).</li>
 * <li>Coletar métricas estatísticas de cada execução.</li>
 * <li>Agregar resultados usando o {@link MultiRunAnalyzer}.</li>
 * <li>Gerar relatórios consolidados para análise de variância e intervalos de confiança.</li>
 * </ol>
 * A execução ocorre numa thread separada (background) para manter a responsividade da UI.
 */
public class BatchAnalysisDialog {

    private Stage dialog;
    private ProgressBar progressBar;
    private Label statusLabel;
    private Label progressLabel;
    private TextArea logArea;
    private Button startButton;
    private Button closeButton;
    
    // Flags de controlo de concorrência
    private volatile boolean isRunning = false;
    private volatile boolean shouldStop = false;
    
    /** Referência partilhada para capturar estatísticas em tempo real do Dashboard. */
    private DashboardStatistics sharedStatistics;

    /**
     * Exibe o diálogo de análise em lote.
     * * @param owner A janela pai (Stage) para modalidade.
     * @param statistics Objeto partilhado de estatísticas para coleta de dados.
     */
    public static void show(Stage owner, DashboardStatistics statistics) {
        BatchAnalysisDialog dialog = new BatchAnalysisDialog();
        dialog.sharedStatistics = statistics;
        dialog.createAndShow(owner);
    }

    /**
     * Constrói e inicializa a interface gráfica do diálogo.
     */
    private void createAndShow(Stage owner) {
        dialog = new Stage();
        dialog.initOwner(owner);
        dialog.initModality(Modality.APPLICATION_MODAL);
        dialog.setTitle("Batch Performance Analysis");
        
        VBox root = new VBox(20);
        root.setPadding(new Insets(20));
        root.setAlignment(Pos.TOP_CENTER);
        // Estilo Dark Mode conforme guidelines visuais
        root.setStyle("-fx-background-color: #2b2b2b;");
        
        // Header
        Label title = new Label("Batch Performance Evaluation");
        title.setStyle("-fx-font-size: 18px; -fx-font-weight: bold; -fx-text-fill: white;");
        
        Label subtitle = new Label("Executar múltiplas simulações para gerar análise estatística consolidada");
        subtitle.setStyle("-fx-font-size: 12px; -fx-text-fill: #cccccc;");
        subtitle.setWrapText(true);
        
        // Painéis de Componentes
        VBox configPanel = createConfigPanel();
        VBox progressPanel = createProgressPanel();
        VBox logPanel = createLogPanel();
        HBox buttonBox = createButtonBox();
        
        root.getChildren().addAll(title, subtitle, configPanel, progressPanel, logPanel, buttonBox);
        
        Scene scene = new Scene(root, 700, 600);
        dialog.setScene(scene);
        
        // Tratamento de fecho da janela: interromper thread de worker se ativa
        dialog.setOnCloseRequest(e -> {
            if (isRunning) {
                e.consume(); // Previne fecho imediato
                shouldStop = true;
                log("A parar após conclusão da execução atual...");
            }
        });
        
        dialog.show();
    }

    private VBox createConfigPanel() {
        VBox panel = new VBox(15);
        panel.setPadding(new Insets(15));
        panel.setStyle("-fx-background-color: rgba(255, 255, 255, 0.05); -fx-background-radius: 5;");
        
        Label header = new Label("Configuração");
        header.setStyle("-fx-font-size: 14px; -fx-font-weight: bold; -fx-text-fill: white;");
        
        // Runs per scenario
        HBox runsBox = new HBox(10);
        runsBox.setAlignment(Pos.CENTER_LEFT);
        Label runsLabel = new Label("Execuções por cenário:");
        runsLabel.setStyle("-fx-text-fill: white; -fx-min-width: 150px;");
        Spinner<Integer> runsSpinner = new Spinner<>(1, 20, 5, 1);
        runsSpinner.setEditable(true);
        runsSpinner.setPrefWidth(80);
        runsSpinner.setId("runsSpinner");
        runsBox.getChildren().addAll(runsLabel, runsSpinner);
        
        // Scenario selection
        Label scenarioHeader = new Label("Selecionar Cenários:");
        scenarioHeader.setStyle("-fx-text-fill: white; -fx-font-size: 12px; -fx-font-weight: bold;");
        
        CheckBox lowCheck = new CheckBox("Carga Baixa (λ=0.2 v/s)");
        lowCheck.setSelected(true);
        lowCheck.setId("lowCheck");
        lowCheck.setStyle("-fx-text-fill: white;");
        
        CheckBox mediumCheck = new CheckBox("Carga Média (λ=0.5 v/s)");
        mediumCheck.setSelected(true);
        mediumCheck.setId("mediumCheck");
        mediumCheck.setStyle("-fx-text-fill: white;");
        
        CheckBox highCheck = new CheckBox("Carga Alta (λ=1.0 v/s)");
        highCheck.setSelected(true);
        highCheck.setId("highCheck");
        highCheck.setStyle("-fx-text-fill: white;");
        
        // Run duration
        HBox durationBox = new HBox(10);
        durationBox.setAlignment(Pos.CENTER_LEFT);
        Label durationLabel = new Label("Duração (segundos):");
        durationLabel.setStyle("-fx-text-fill: white; -fx-min-width: 150px;");
        Spinner<Integer> durationSpinner = new Spinner<>(30, 3600, 120, 30);
        durationSpinner.setEditable(true);
        durationSpinner.setPrefWidth(80);
        durationSpinner.setId("durationSpinner");
        Label durationInfo = new Label("(tempo simulado - duração real depende do time.scale)");
        durationInfo.setStyle("-fx-text-fill: #999999; -fx-font-size: 10px;");
        durationBox.getChildren().addAll(durationLabel, durationSpinner, durationInfo);
        
        panel.getChildren().addAll(header, runsBox, scenarioHeader, lowCheck, mediumCheck, highCheck, durationBox);
        return panel;
    }

    private VBox createProgressPanel() {
        VBox panel = new VBox(10);
        panel.setPadding(new Insets(15));
        panel.setStyle("-fx-background-color: rgba(255, 255, 255, 0.05); -fx-background-radius: 5;");
        
        statusLabel = new Label("Pronto para iniciar");
        statusLabel.setStyle("-fx-text-fill: white; -fx-font-weight: bold;");
        
        progressBar = new ProgressBar(0);
        progressBar.setPrefWidth(Double.MAX_VALUE);
        progressBar.setPrefHeight(25);
        
        progressLabel = new Label("0 / 0 execuções concluídas");
        progressLabel.setStyle("-fx-text-fill: #cccccc; -fx-font-size: 11px;");
        
        panel.getChildren().addAll(statusLabel, progressBar, progressLabel);
        return panel;
    }

    private VBox createLogPanel() {
        VBox panel = new VBox(5);
        
        Label logHeader = new Label("Log de Atividade:");
        logHeader.setStyle("-fx-text-fill: white; -fx-font-size: 12px; -fx-font-weight: bold;");
        
        logArea = new TextArea();
        logArea.setEditable(false);
        logArea.setPrefRowCount(10);
        logArea.setWrapText(true);
        // Estilo de terminal para o log
        logArea.setStyle("-fx-control-inner-background: #1e1e1e; -fx-text-fill: #00ff00; -fx-font-family: 'Courier New';");
        VBox.setVgrow(logArea, Priority.ALWAYS);
        
        panel.getChildren().addAll(logHeader, logArea);
        return panel;
    }

    private HBox createButtonBox() {
        HBox box = new HBox(15);
        box.setAlignment(Pos.CENTER);
        box.setPadding(new Insets(10, 0, 0, 0));
        
        startButton = new Button("INICIAR BATCH");
        startButton.setStyle("-fx-background-color: #28a745; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;");
        startButton.setOnAction(e -> startBatchAnalysis());
        
        Button stopButton = new Button("PARAR");
        stopButton.setStyle("-fx-background-color: #dc3545; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;");
        stopButton.setOnAction(e -> {
            shouldStop = true;
            log("Paragem solicitada...");
        });
        
        closeButton = new Button("FECHAR");
        closeButton.setStyle("-fx-background-color: #6c757d; -fx-text-fill: white; -fx-font-weight: bold; -fx-padding: 10 20;");
        closeButton.setOnAction(e -> dialog.close());
        
        box.getChildren().addAll(startButton, stopButton, closeButton);
        return box;
    }

    /**
     * Valida configurações e inicia a thread de execução em batch.
     */
    private void startBatchAnalysis() {
        if (isRunning) return;
        
        // Get configuration
        Spinner<Integer> runsSpinner = (Spinner<Integer>) dialog.getScene().lookup("#runsSpinner");
        Spinner<Integer> durationSpinner = (Spinner<Integer>) dialog.getScene().lookup("#durationSpinner");
        CheckBox lowCheck = (CheckBox) dialog.getScene().lookup("#lowCheck");
        CheckBox mediumCheck = (CheckBox) dialog.getScene().lookup("#mediumCheck");
        CheckBox highCheck = (CheckBox) dialog.getScene().lookup("#highCheck");
        
        int runsPerScenario = runsSpinner.getValue();
        int duration = durationSpinner.getValue();
        
        // Validate selection
        if (!lowCheck.isSelected() && !mediumCheck.isSelected() && !highCheck.isSelected()) {
            log("ERRO: Selecione pelo menos um cenário!");
            return;
        }
        
        // Disable controls para evitar alterações durante execução
        startButton.setDisable(true);
        runsSpinner.setDisable(true);
        durationSpinner.setDisable(true);
        lowCheck.setDisable(true);
        mediumCheck.setDisable(true);
        highCheck.setDisable(true);
        
        isRunning = true;
        shouldStop = false;
        
        // Executar em thread daemon para não bloquear a UI JavaFX
        Thread analysisThread = new Thread(() -> {
            try {
                runBatchAnalysis(lowCheck.isSelected(), mediumCheck.isSelected(), 
                               highCheck.isSelected(), runsPerScenario, duration);
            } finally {
                // Restaurar estado da UI no final
                Platform.runLater(() -> {
                    startButton.setDisable(false);
                    runsSpinner.setDisable(false);
                    durationSpinner.setDisable(false);
                    lowCheck.setDisable(false);
                    mediumCheck.setDisable(false);
                    highCheck.setDisable(false);
                    isRunning = false;
                });
            }
        });
        analysisThread.setDaemon(true);
        analysisThread.start();
    }

    /**
     * Lógica principal de orquestração do batch.
     * Itera sobre cenários e execuções, chamando a simulação e o analisador.
     */
    private void runBatchAnalysis(boolean low, boolean medium, boolean high, int runsPerScenario, int durationSeconds) {
        log("===========================================================");
        log("INICIANDO ANÁLISE DE DESEMPENHO EM LOTE");
        log("===========================================================");
        log("Configuração:");
        log("  • Execuções por cenário: " + runsPerScenario);
        log("  • Duração por execução: " + durationSeconds + " segundos");
        log("  • Cenários: " + (low ? "LOW " : "") + (medium ? "MEDIUM " : "") + (high ? "HIGH" : ""));
        log("");
        
        String[] scenarios = new String[]{
            low ? "simulation-low.properties" : null,
            medium ? "simulation-medium.properties" : null,
            high ? "simulation-high.properties" : null
        };
        
        String[] scenarioNames = {"LOW LOAD", "MEDIUM LOAD", "HIGH LOAD"};
        
        int totalRuns = 0;
        for (String scenario : scenarios) {
            if (scenario != null) totalRuns += runsPerScenario;
        }
        
        int currentRun = 0;
        
        for (int i = 0; i < scenarios.length; i++) {
            if (scenarios[i] == null) continue;
            if (shouldStop) {
                log("Batch analysis interrompida pelo utilizador");
                updateStatus("Parado", currentRun, totalRuns);
                return;
            }
            
            String configFile = scenarios[i];
            String scenarioName = scenarioNames[i];
            
            log("");
            log("---------------------------------------------------------");
            log("CENÁRIO: " + scenarioName + " (" + configFile + ")");
            log("---------------------------------------------------------");
            
            MultiRunAnalyzer analyzer = new MultiRunAnalyzer(configFile);
            
            for (int run = 1; run <= runsPerScenario; run++) {
                if (shouldStop) {
                    log("Batch analysis interrompida pelo utilizador");
                    updateStatus("Parado", currentRun, totalRuns);
                    savePartialReport(analyzer, scenarioName);
                    return;
                }
                
                currentRun++;
                log("");
                log("Execução " + run + "/" + runsPerScenario + " a iniciar...");
                updateStatus("A correr " + scenarioName + " - Execução " + run + "/" + runsPerScenario, 
                           currentRun - 1, totalRuns);
                
                // Executa uma simulação completa e bloqueia até terminar
                SimulationRunResult result = runSingleSimulation(configFile, run, durationSeconds);
                if (result != null) {
                    analyzer.addResult(result);
                    log("Execução " + run + " completa - Gerados: " + result.getTotalVehiclesGenerated() + 
                        " | Completados: " + result.getTotalVehiclesCompleted() + 
                        " | Tempo Médio: " + String.format("%.2f", result.getAverageSystemTime()) + "s");
                } else {
                    log("Execução " + run + " falhou!");
                }
                
                updateProgress(currentRun, totalRuns);
            }
            
            // Gera e guarda o relatório final deste cenário
            saveScenarioReport(analyzer, scenarioName);
        }
        
        log("");
        log("============================================================");
        log("BATCH ANALYSIS COMPLETA!");
        log("===========================================================");
        log("Relatórios guardados em: analysis/");
        log("");
        
        updateStatus("Concluído!", totalRuns, totalRuns);
        updateProgress(1.0);
    }

    /**
     * Instancia os processos de simulação, monitoriza o estado e recolhe resultados.
     */
    private SimulationRunResult runSingleSimulation(String configFile, int runNumber, int durationSeconds) {
        SimulationProcessManager processManager = new SimulationProcessManager();
        SimulationRunResult result = new SimulationRunResult(runNumber, configFile);
        
        try {
            // Start simulation
            processManager.setConfigFile(configFile);
            processManager.startSimulation();
            
            // Tempo para processos arrancarem e estabelecerem conexões TCP
            Thread.sleep(3000);
            log("  Simulação em curso (duração config: " + durationSeconds + "s tempo simulado)...");
            log("  A aguardar processo Coordenador completar...");
            
            // Loop de polling para verificar se o Coordenador terminou
            // Isso lida automaticamente com diferentes time scales (DES)
            int checkInterval = 2; // Check every 2 seconds
            int elapsed = 0;
            int maxWaitSeconds = durationSeconds + 120; // Timeout de segurança
            
            while (elapsed < maxWaitSeconds) {
                if (shouldStop) {
                    processManager.stopSimulation();
                    return null;
                }
                
                // Check if simulation completed
                if (!processManager.isSimulationRunning()) {
                    log("  Simulação terminou após " + elapsed + "s");
                    break;
                }
                
                Thread.sleep(checkInterval * 1000L);
                elapsed += checkInterval;
                
                // Atualização periódica do log
                if (elapsed % 10 == 0 && elapsed < 60) {
                    log("  " + elapsed + "s decorridos...");
                }
            }
            
            if (elapsed >= maxWaitSeconds) {
                log("  Timeout atingido, forçando paragem...");
            }
            
            // Stop and collect results
            log("  A terminar processos...");
            processManager.stopSimulation();
            Thread.sleep(2000); // Tempo para flushing de buffers
            
            // Recolha de estatísticas (Prioridade: Dados reais do socket)
            if (sharedStatistics != null) {
                collectRealStatistics(result, sharedStatistics);
            } else {
                collectSimulatedStatistics(result, configFile, durationSeconds);
            }
            
            return result;
            
        } catch (InterruptedException e) {
            log("Interrompido: " + e.getMessage());
            Thread.currentThread().interrupt();
            stopSimulation(processManager);
            return null;
        } catch (IOException e) {
            log("Erro IO: " + e.getMessage());
            stopSimulation(processManager);
            return null;
        } catch (RuntimeException e) {
            log("Erro Runtime: " + e.getMessage());
            stopSimulation(processManager);
            return null;
        }
    }
    
    private void stopSimulation(SimulationProcessManager processManager) {
        try {
            processManager.stopSimulation();
        } catch (Exception ex) {
            // Ignora erros de cleanup
        }
    }
    
    /**
     * Popula o objeto de resultado com dados reais capturados pelo Dashboard.
     */
    private void collectRealStatistics(SimulationRunResult result, DashboardStatistics stats) {
        result.setTotalVehiclesGenerated(stats.getTotalVehiclesGenerated());
        result.setTotalVehiclesCompleted(stats.getTotalVehiclesCompleted());
        result.setAverageSystemTime(stats.getAverageSystemTime() / 1000.0); // Converte ms para segundos
        result.setAverageWaitingTime(stats.getAverageWaitingTime() / 1000.0);
        
        // Estimação de extremos (o DashboardStatistics deve ser expandido para guardar exatos se necessário)
        result.setMinSystemTime(stats.getAverageSystemTime() / 1000.0 * 0.5);
        result.setMaxSystemTime(stats.getAverageSystemTime() / 1000.0 * 2.0);
        
        // Estatísticas por tipo
        for (VehicleType type : VehicleType.values()) {
            int count = stats.getVehicleTypeCount(type);
            double waitTime = stats.getAverageWaitingTimeByType(type) / 1000.0;
            result.setVehicleCountByType(type, count);
            result.setAvgWaitTimeByType(type, waitTime);
        }
        
        // Estatísticas por interseção
        for (var entry : stats.getAllIntersectionStats().entrySet()) {
            String intersectionId = entry.getKey();
            DashboardStatistics.IntersectionStats iStats = entry.getValue();
            
            result.setVehiclesProcessed(intersectionId, iStats.getTotalDepartures());
            result.setMaxQueueSize(intersectionId, iStats.getCurrentQueueSize());
            result.setAvgQueueSize(intersectionId, (double) iStats.getCurrentQueueSize());
        }
    }
    
    /**
     * Gera dados simulados (mock) caso o dashboard não esteja conectado.
     * Útil para testes de interface.
     */
    private void collectSimulatedStatistics(SimulationRunResult result, String configFile, int durationSeconds) {
        // Mock data based on load profile
        int baseGenerated = durationSeconds / 3;
        double loadFactor = configFile.contains("low") ? 0.2 : 
                          configFile.contains("medium") ? 0.5 : 1.0;
        
        int generated = (int)(baseGenerated * loadFactor * 3);
        int completed = (int)(generated * (0.85 + Math.random() * 0.1)); // 85-95% completion rate
        
        double baseSystemTime = 40.0;
        double congestionFactor = configFile.contains("low") ? 1.0 : 
                                configFile.contains("medium") ? 1.5 : 2.5;
        
        result.setTotalVehiclesGenerated(generated);
        result.setTotalVehiclesCompleted(completed);
        result.setAverageSystemTime(baseSystemTime * congestionFactor + Math.random() * 10);
        result.setMinSystemTime(20.0 + Math.random() * 5);
        result.setMaxSystemTime(baseSystemTime * congestionFactor * 2 + Math.random() * 20);
        result.setAverageWaitingTime(10.0 * congestionFactor + Math.random() * 5);
        
        log("  Nota: A usar estatísticas simuladas (conexão real necessária)");
    }

    private void saveScenarioReport(MultiRunAnalyzer analyzer, String scenarioName) {
        try {
            File analysisDir = new File("analysis");
            if (!analysisDir.exists()) {
                analysisDir.mkdirs();
            }
            
            String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date());
            String reportFile = "analysis/" + scenarioName.replace(" ", "_") + "_" + timestamp + ".txt";
            String csvFile = "analysis/" + scenarioName.replace(" ", "_") + "_" + timestamp + ".csv";
            
            analyzer.saveReport(reportFile);
            analyzer.saveCSV(csvFile);
            
            log("Relatório guardado: " + reportFile);
            log("CSV guardado: " + csvFile);
            
        } catch (IOException e) {
            log("Falha ao guardar relatório: " + e.getMessage());
        }
    }

    private void savePartialReport(MultiRunAnalyzer analyzer, String scenarioName) {
        if (analyzer.getRunCount() > 0) {
            log("A guardar resultados parciais...");
            saveScenarioReport(analyzer, scenarioName + "_PARTIAL");
        }
    }

    // --- Helpers de UI Thread-Safe ---

    private void log(String message) {
        Platform.runLater(() -> {
            logArea.appendText(message + "\n");
            logArea.setScrollTop(Double.MAX_VALUE);
        });
    }

    private void updateStatus(String status, int current, int total) {
        Platform.runLater(() -> {
            statusLabel.setText(status);
            progressLabel.setText(current + " / " + total + " execuções completas");
        });
    }

    private void updateProgress(int current, int total) {
        Platform.runLater(() -> {
            progressBar.setProgress((double) current / total);
        });
    }

    private void updateProgress(double progress) {
        Platform.runLater(() -> {
            progressBar.setProgress(progress);
        });
    }
}