package sd.dashboard;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import sd.model.VehicleType;

/**
 * Repositório central de estado da simulação, desenhado para acesso concorrente de alta frequência.
 * <p>
 * Esta classe atua como a "Single Source of Truth" para o Dashboard. Utiliza primitivas
 * de concorrência do pacote {@code java.util.concurrent} (como {@link AtomicInteger} e 
 * {@link ConcurrentHashMap}) para permitir leituras e escritas simultâneas sem a necessidade
 * de bloqueios explícitos (Lock-Free), minimizando a latência de processamento das mensagens
 * recebidas dos múltiplos nós da rede.
 */
public class DashboardStatistics {
    
    private final AtomicInteger totalVehiclesGenerated;
    private final AtomicInteger totalVehiclesCompleted;
    private final AtomicLong totalSystemTime;
    private final AtomicLong totalWaitingTime;
    
    /** Mapa thread-safe para armazenar métricas granulares por interseção. */
    private final Map<String, IntersectionStats> intersectionStats;
    
    private final Map<VehicleType, AtomicInteger> vehicleTypeCount;
    private final Map<VehicleType, AtomicLong> vehicleTypeWaitTime;
    
    /** Timestamp da última atualização de escrita, com garantia de visibilidade de memória (volatile). */
    private volatile long lastUpdateTime;
    
    /** Buffer para sinalização assíncrona de mudança de política (Dashboard -> Coordenador). */
    private volatile String requestedRoutingPolicy;
    
    /**
     * Inicializa os contadores atómicos e as estruturas de dados concorrentes.
     */
    public DashboardStatistics() {
        this.totalVehiclesGenerated = new AtomicInteger(0);
        this.totalVehiclesCompleted = new AtomicInteger(0);
        this.totalSystemTime = new AtomicLong(0);
        this.totalWaitingTime = new AtomicLong(0);
        
        this.intersectionStats = new ConcurrentHashMap<>();
        this.vehicleTypeCount = new ConcurrentHashMap<>();
        this.vehicleTypeWaitTime = new ConcurrentHashMap<>();
        
        for (VehicleType type : VehicleType.values()) {
            vehicleTypeCount.put(type, new AtomicInteger(0));
            vehicleTypeWaitTime.put(type, new AtomicLong(0));
        }
        
        this.lastUpdateTime = System.currentTimeMillis();
    }
    
    public void updateVehiclesGenerated(int count) {
        totalVehiclesGenerated.set(count);
        updateTimestamp();
    }
    
    public void incrementVehiclesGenerated() {
        totalVehiclesGenerated.incrementAndGet();
        updateTimestamp();
    }
    
    public void updateVehiclesCompleted(int count) {
        totalVehiclesCompleted.set(count);
        updateTimestamp();
    }
    
    public void incrementVehiclesCompleted() {
        totalVehiclesCompleted.incrementAndGet();
        updateTimestamp();
    }
    
    public void addSystemTime(long timeMs) {
        totalSystemTime.addAndGet(timeMs);
        updateTimestamp();
    }
    
    public void setTotalSystemTime(long timeMs) {
        totalSystemTime.set(timeMs);
        updateTimestamp();
    }
    
    public void addWaitingTime(long timeMs) {
        totalWaitingTime.addAndGet(timeMs);
        updateTimestamp();
    }
    
    public void setTotalWaitingTime(long timeMs) {
        totalWaitingTime.set(timeMs);
        updateTimestamp();
    }
    
    public void updateVehicleTypeStats(VehicleType type, int count, long waitTimeMs) {
        vehicleTypeCount.get(type).set(count);
        vehicleTypeWaitTime.get(type).set(waitTimeMs);
        updateTimestamp();
    }
    
    public void incrementVehicleType(VehicleType type) {
        vehicleTypeCount.get(type).incrementAndGet();
        updateTimestamp();
    }
    
    /**
     * Atualiza ou inicializa atomicamente as estatísticas de uma interseção específica.
     * <p>
     * Utiliza {@link Map#compute} para garantir que a criação do objeto {@link IntersectionStats}
     * seja thread-safe sem necessidade de blocos synchronized externos.
     *
     * @param intersectionId ID da interseção.
     * @param arrivals Total acumulado de chegadas.
     * @param departures Total acumulado de partidas.
     * @param currentQueueSize Tamanho instantâneo da fila.
     */
    public void updateIntersectionStats(String intersectionId, int arrivals, 
                                       int departures, int currentQueueSize) {
        intersectionStats.compute(intersectionId, (id, stats) -> {
            if (stats == null) {
                stats = new IntersectionStats(intersectionId);
            }
            stats.updateStats(arrivals, departures, currentQueueSize);
            return stats;
        });
        updateTimestamp();
    }
    
    private void updateTimestamp() {
        lastUpdateTime = System.currentTimeMillis();
    }
    
    // --- Getters e Métricas Calculadas ---

    public int getTotalVehiclesGenerated() {
        return totalVehiclesGenerated.get();
    }
    
    public int getTotalVehiclesCompleted() {
        return totalVehiclesCompleted.get();
    }
    
    /**
     * Calcula o tempo médio no sistema em tempo real.
     * @return Média em milissegundos (0.0 se nenhum veículo completou).
     */
    public double getAverageSystemTime() {
        int completed = totalVehiclesCompleted.get();
        if (completed == 0) return 0.0;
        return (double) totalSystemTime.get() / completed;
    }
    
    /**
     * Calcula o tempo médio de espera em tempo real.
     * @return Média em milissegundos (0.0 se nenhum veículo completou).
     */
    public double getAverageWaitingTime() {
        int completed = totalVehiclesCompleted.get();
        if (completed == 0) return 0.0;
        return (double) totalWaitingTime.get() / completed;
    }
    
    public int getVehicleTypeCount(VehicleType type) {
        return vehicleTypeCount.get(type).get();
    }
    
    public double getAverageWaitingTimeByType(VehicleType type) {
        int count = vehicleTypeCount.get(type).get();
        if (count == 0) return 0.0;
        return (double) vehicleTypeWaitTime.get(type).get() / count;
    }
    
    public IntersectionStats getIntersectionStats(String intersectionId) {
        return intersectionStats.get(intersectionId);
    }
    
    public Map<String, IntersectionStats> getAllIntersectionStats() {
        return new HashMap<>(intersectionStats);
    }
    
    public long getLastUpdateTime() {
        return lastUpdateTime;
    }
    
    /**
     * Obtém um snapshot dos tamanhos atuais das filas de todas as interseções.
     * <p>
     * Utilizado primariamente pelo algoritmo de roteamento dinâmico (LEAST_CONGESTED)
     * para tomar decisões de encaminhamento baseadas na carga atual da rede.
     * * @return Mapa contendo {@code intersectionId -> queueSize}.
     */
    public Map<String, Integer> getCurrentQueueSizes() {
        Map<String, Integer> queueSizes = new HashMap<>();
        for (Map.Entry<String, IntersectionStats> entry : intersectionStats.entrySet()) {
            queueSizes.put(entry.getKey(), entry.getValue().getCurrentQueueSize());
        }
        return queueSizes;
    }
    
    /**
     * Regista uma intenção de mudança de política de roteamento solicitada pela UI.
     * O Coordenador fará polling deste valor periodicamente.
     */
    public void setRequestedRoutingPolicy(String policy) {
        this.requestedRoutingPolicy = policy;
    }
    
    /**
     * Obtém e limpa atomicamente a política de roteamento solicitada.
     * Implementa a semântica de consumo único (one-time consumption).
     * * @return A política solicitada ou null se não houver mudança pendente.
     */
    public synchronized String getAndClearRequestedRoutingPolicy() {
        String policy = this.requestedRoutingPolicy;
        this.requestedRoutingPolicy = null;
        return policy;
    }
    
    /**
     * Imprime um resumo formatado das estatísticas no stdout.
     * Útil para o modo CLI (Headless).
     */
    public void display() {
        System.out.println("\n--- GLOBAL STATISTICS ---");
        System.out.printf("Total Vehicles Generated: %d%n", getTotalVehiclesGenerated());
        System.out.printf("Total Vehicles Completed: %d%n", getTotalVehiclesCompleted());
        System.out.printf("Vehicles In Transit: %d%n", 
            getTotalVehiclesGenerated() - getTotalVehiclesCompleted());
        System.out.printf("Average System Time: %.2f ms%n", getAverageSystemTime());
        System.out.printf("Average Waiting Time: %.2f ms%n", getAverageWaitingTime());
        
        System.out.println("\n--- VEHICLE TYPE STATISTICS ---");
        for (VehicleType type : VehicleType.values()) {
            int count = getVehicleTypeCount(type);
            double avgWait = getAverageWaitingTimeByType(type);
            System.out.printf("%s: %d vehicles, avg wait: %.2f ms%n", 
                type, count, avgWait);
        }
        
        System.out.println("\n--- INTERSECTION STATISTICS ---");
        if (intersectionStats.isEmpty()) {
            System.out.println("(No data received yet)");
        } else {
            for (IntersectionStats stats : intersectionStats.values()) {
                stats.display();
            }
        }
        
        System.out.printf("%nLast Update: %tT%n", lastUpdateTime);
    }
    
    /**
     * Agregado de métricas específico para um nó de interseção.
     * Mantém contadores atómicos para garantir consistência em atualizações concorrentes.
     */
    public static class IntersectionStats {
        private final String intersectionId;
        private final AtomicInteger totalArrivals;
        private final AtomicInteger totalDepartures;
        private final AtomicInteger currentQueueSize;
        
        public IntersectionStats(String intersectionId) {
            this.intersectionId = intersectionId;
            this.totalArrivals = new AtomicInteger(0);
            this.totalDepartures = new AtomicInteger(0);
            this.currentQueueSize = new AtomicInteger(0);
        }
        
        public void updateStats(int arrivals, int departures, int queueSize) {
            this.totalArrivals.set(arrivals);
            this.totalDepartures.set(departures);
            this.currentQueueSize.set(queueSize);
        }
        
        public String getIntersectionId() {
            return intersectionId;
        }
        
        public int getTotalArrivals() {
            return totalArrivals.get();
        }
        
        public int getTotalDepartures() {
            return totalDepartures.get();
        }
        
        public int getCurrentQueueSize() {
            return currentQueueSize.get();
        }
        
        public void display() {
            System.out.printf("%s: Arrivals=%d, Departures=%d, Queue=%d%n",
                intersectionId, getTotalArrivals(), getTotalDepartures(), getCurrentQueueSize());
        }
    }
}