package sd.analysis;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import sd.model.VehicleType;

/**
 * Responsável pela agregação e análise estatística de múltiplas execuções da simulação.
 * <p>
 * Esta classe coleta resultados individuais ({@link SimulationRunResult}) e calcula
 * métricas consolidadas, incluindo média, desvio padrão, mediana e intervalos de
 * confiança de 95%. O objetivo é fornecer uma visão robusta do comportamento do
 * sistema, mitigando a variância estocástica de execuções isoladas.
 */
public class MultiRunAnalyzer {
    
    /** Lista acumulada de resultados de execuções individuais. */
    private final List<SimulationRunResult> results;
    
    /** Identificador do ficheiro de configuração utilizado nas execuções. */
    private final String configurationFile;
    
    /**
     * Inicializa o analisador para um conjunto específico de configurações.
     *
     * @param configurationFile O caminho ou nome do ficheiro de configuração base.
     */
    public MultiRunAnalyzer(String configurationFile) {
        this.configurationFile = configurationFile;
        this.results = new ArrayList<>();
    }
    
    /**
     * Adiciona o resultado de uma execução de simulação concluída ao conjunto de dados.
     *
     * @param result O objeto contendo as métricas da execução individual.
     */
    public void addResult(SimulationRunResult result) {
        results.add(result);
    }
    
    /**
     * Retorna o número total de execuções armazenadas até o momento.
     *
     * @return O tamanho da lista de resultados.
     */
    public int getRunCount() {
        return results.size();
    }
    
    /**
     * Gera um relatório estatístico abrangente formatado em texto.
     * <p>
     * O relatório inclui:
     * <ul>
     * <li>Métricas globais (throughput, tempos de espera, tempos no sistema).</li>
     * <li>Análise segmentada por tipo de veículo ({@link VehicleType}).</li>
     * <li>Análise de gargalos por interseção (tamanhos de fila).</li>
     * <li>Resumos brutos das execuções individuais.</li>
     * </ul>
     *
     * @return Uma String contendo o relatório completo formatado.
     */
    public String generateReport() {
        if (results.isEmpty()) {
            return "No simulation results to analyze.";
        }
        
        StringBuilder report = new StringBuilder();
        
        // Header
        report.append("=".repeat(80)).append("\n");
        report.append("ANÁLISE ESTATÍSTICA MULTI-EXECUÇÃO\n");
        report.append("=".repeat(80)).append("\n");
        report.append("Configuração: ").append(configurationFile).append("\n");
        report.append("Número de Execuções: ").append(results.size()).append("\n");
        report.append("Data da Análise: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())).append("\n");
        report.append("\n");
        
        // Global metrics
        report.append("-".repeat(80)).append("\n");
        report.append("MÉTRICAS GLOBAIS\n");
        report.append("-".repeat(80)).append("\n\n");
        
        report.append(analyzeMetric("Veículos Gerados", 
            extractValues(r -> (double) r.getTotalVehiclesGenerated())));
        report.append("\n");
        
        report.append(analyzeMetric("Veículos Completados", 
            extractValues(r -> (double) r.getTotalVehiclesCompleted())));
        report.append("\n");
        
        report.append(analyzeMetric("Taxa de Conclusão (%)", 
            extractValues(r -> r.getTotalVehiclesGenerated() > 0 
                ? 100.0 * r.getTotalVehiclesCompleted() / r.getTotalVehiclesGenerated() 
                : 0.0)));
        report.append("\n");
        
        report.append(analyzeMetric("Tempo Médio no Sistema (segundos)", 
            extractValues(r -> r.getAverageSystemTime())));
        report.append("\n");
        
        report.append(analyzeMetric("Tempo Médio de Espera (segundos)", 
            extractValues(r -> r.getAverageWaitingTime())));
        report.append("\n");
        
        // Per-vehicle-type analysis
        report.append("\n");
        report.append("-".repeat(80)).append("\n");
        report.append("ANÁLISE POR TIPO DE VEÍCULO\n");
        report.append("-".repeat(80)).append("\n\n");
        
        for (VehicleType type : VehicleType.values()) {
            report.append("--- ").append(type).append(" ---\n");
            
            report.append(analyzeMetric("  Contagem de Veículos", 
                extractValues(r -> (double) r.getVehicleCountByType().getOrDefault(type, 0))));
            report.append("\n");
            
            report.append(analyzeMetric("  Tempo Médio no Sistema (segundos)", 
                extractValues(r -> r.getAvgSystemTimeByType().getOrDefault(type, 0.0))));
            report.append("\n");
            
            report.append(analyzeMetric("  Tempo Médio de Espera (segundos)", 
                extractValues(r -> r.getAvgWaitTimeByType().getOrDefault(type, 0.0))));
            report.append("\n\n");
        }
        
        // Per-intersection analysis
        report.append("-".repeat(80)).append("\n");
        report.append("ANÁLISE POR INTERSEÇÃO\n");
        report.append("-".repeat(80)).append("\n\n");
        
        Set<String> allIntersections = new TreeSet<>();
        for (SimulationRunResult result : results) {
            allIntersections.addAll(result.getMaxQueueSizeByIntersection().keySet());
        }
        
        for (String intersection : allIntersections) {
            report.append("--- ").append(intersection).append(" ---\n");
            
            report.append(analyzeMetric("  Tamanho Máximo da Fila", 
                extractValues(r -> (double) r.getMaxQueueSizeByIntersection().getOrDefault(intersection, 0))));
            report.append("\n");
            
            report.append(analyzeMetric("  Tamanho Médio da Fila", 
                extractValues(r -> r.getAvgQueueSizeByIntersection().getOrDefault(intersection, 0.0))));
            report.append("\n");
            
            report.append(analyzeMetric("  Veículos Processados", 
                extractValues(r -> (double) r.getVehiclesProcessedByIntersection().getOrDefault(intersection, 0))));
            report.append("\n\n");
        }
        
        // Individual run summaries
        report.append("-".repeat(80)).append("\n");
        report.append("RESUMOS INDIVIDUAIS DAS EXECUÇÕES\n");
        report.append("-".repeat(80)).append("\n\n");
        
        for (SimulationRunResult result : results) {
            report.append(result.toString()).append("\n\n");
        }
        
        report.append("=".repeat(80)).append("\n");
        report.append("FIM DO RELATÓRIO\n");
        report.append("=".repeat(80)).append("\n");
        
        return report.toString();
    }
    
    /**
     * Analisa uma métrica específica e retorna as estatísticas formatadas.
     * <p>
     * Calcula média, desvio padrão, mediana, intervalo de confiança (95%) e extremos (min/max).
     *
     * @param metricName O nome descritivo da métrica (ex: "Tempo de Espera").
     * @param values A lista de valores numéricos brutos extraídos das execuções.
     * @return Uma string formatada com os dados estatísticos.
     */
    private String analyzeMetric(String metricName, List<Double> values) {
        if (values.isEmpty() || values.stream().allMatch(v -> v == 0.0)) {
            return metricName + ": Sem dados\n";
        }
        
        double mean = StatisticalAnalysis.mean(values);
        double stdDev = StatisticalAnalysis.standardDeviation(values);
        double[] ci = StatisticalAnalysis.confidenceInterval95(values);
        double min = StatisticalAnalysis.min(values);
        double max = StatisticalAnalysis.max(values);
        double median = StatisticalAnalysis.median(values);
        
        return String.format(
            "%s:\n" +
            "    Média:   %10.2f    Desvio Padrão: %10.2f\n" +
            "    Mediana: %10.2f    IC 95%%: [%.2f, %.2f]\n" +
            "    Mín:     %10.2f    Máx:          %10.2f\n",
            metricName, mean, stdDev, median, ci[0], ci[1], min, max
        );
    }
    
    /**
     * Extrai valores numéricos dos resultados de simulação usando uma função mapeadora.
     * <p>
     * Utilizado internamente para transformar a lista de objetos complexos {@link SimulationRunResult}
     * em listas simples de Doubles para processamento estatístico.
     *
     * @param extractor Função lambda que define qual campo extrair de cada resultado.
     * @return Lista de valores double correspondentes.
     */
    private List<Double> extractValues(java.util.function.Function<SimulationRunResult, Double> extractor) {
        List<Double> values = new ArrayList<>();
        for (SimulationRunResult result : results) {
            values.add(extractor.apply(result));
        }
        return values;
    }
    
    /**
     * Persiste o relatório gerado num ficheiro de texto.
     *
     * @param filename O caminho do ficheiro de destino.
     * @throws IOException Se ocorrer um erro de escrita no disco.
     */
    public void saveReport(String filename) throws IOException {
        try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)))) {
            writer.print(generateReport());
        }
    }
    
    /**
     * Gera um resumo em formato CSV para fácil importação em ferramentas de planilha.
     * <p>
     * Este método atua como um wrapper para {@link #saveCSVSummary(String)}.
     *
     * @param filename O caminho do ficheiro CSV de destino.
     * @throws IOException Se ocorrer um erro de escrita no disco.
     */
    public void saveCSV(String filename) throws IOException {
        saveCSVSummary(filename);
    }
    
    /**
     * Gera e grava o sumário CSV detalhado com métricas chave por execução.
     * <p>
     * Colunas incluídas: Execução, VeículosGerados, VeículosCompletados, TaxaConclusão,
     * TempoMédioSistema, TempoMédioEspera, TempoMínimoSistema, TempoMáximoSistema.
     *
     * @param filename O caminho do ficheiro CSV de destino.
     * @throws IOException Se ocorrer um erro de escrita no disco.
     */
    public void saveCSVSummary(String filename) throws IOException {
        try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)))) {
            // Header
            writer.println("Execução,VeículosGerados,VeículosCompletados,TaxaConclusão," +
                "TempoMédioSistema,TempoMédioEspera,TempoMínimoSistema,TempoMáximoSistema");
            
            // Data rows
            for (SimulationRunResult result : results) {
                double completionRate = result.getTotalVehiclesGenerated() > 0 
                    ? 100.0 * result.getTotalVehiclesCompleted() / result.getTotalVehiclesGenerated() 
                    : 0.0;
                
                writer.printf("%d,%d,%d,%.2f,%.2f,%.2f,%.2f,%.2f\n",
                    result.getRunNumber(),
                    result.getTotalVehiclesGenerated(),
                    result.getTotalVehiclesCompleted(),
                    completionRate,
                    result.getAverageSystemTime(),
                    result.getAverageWaitingTime(),
                    result.getMinSystemTime(),
                    result.getMaxSystemTime()
                );
            }
        }
    }
}