package sd;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import sd.config.SimulationConfig;
import sd.coordinator.SocketClient;
import sd.dashboard.StatsUpdatePayload;
import sd.des.DESEventType;
import sd.des.EventQueue;
import sd.des.SimulationClock;
import sd.des.SimulationEvent;
import sd.des.TrafficLightEvent;
import sd.logging.EventLogger;
import sd.model.Intersection;
import sd.model.Message;
import sd.model.MessageType;
import sd.model.TrafficLight;
import sd.model.TrafficLightState;
import sd.model.Vehicle;
import sd.protocol.MessageProtocol;
import sd.protocol.SocketConnection;
import sd.serialization.SerializationException;

/**
 * Representa um nó de processamento autónomo na malha de simulação distribuída
 * (Worker Node).
 * <p>
 * Esta classe implementa a lógica de uma interseção rodoviária utilizando uma
 * arquitetura híbrida:
 * <ol>
 * <li><b>Reativa (Network I/O):</b> Threads dedicadas aceitam conexões TCP e
 * injetam veículos nas filas de entrada assim que chegam.</li>
 * <li><b>Proativa (DES Engine):</b> Uma thread de processamento de eventos gere
 * a lógica temporal (mudança de semáforos, tempos de travessia) baseada num
 * relógio virtual monotónico.</li>
 * </ol>
 * <p>
 * A sincronização entre a chegada assíncrona de veículos (Rede) e o
 * processamento determinístico (DES) é gerida através de estruturas de dados
 * concorrentes e bloqueios justos (Fair Locks).
 */
public class IntersectionProcess {

    private final String intersectionId;

    private final SimulationConfig config;

    private final Intersection intersection;

    private ServerSocket serverSocket;

    /**
     * Tabela de encaminhamento dinâmico para conexões de saída (Next-Hop Cache).
     */
    private final Map<String, SocketConnection> outgoingConnections;

    /** Pool de threads para tratamento de I/O de rede (entrada de veículos). */
    private final ExecutorService connectionHandlerPool;

    private ScheduledExecutorService statsExecutor;
    private ScheduledExecutorService departureExecutor;

    private volatile boolean running;
    /** Fator de dilatação temporal (0.0 = Velocidade Máxima, 1.0 = Tempo Real). */
    private double timeScale;

    // --- Componentes DES (Simulação de Eventos Discretos) ---
    /** Relógio central virtual da interseção. */
    private final SimulationClock clock;
    /** Fila de prioridade (Min-Heap) para agendamento temporal de eventos. */
    private final EventQueue eventQueue;
    private final EventLogger eventLogger;
    /** Thread "Single-Writer" responsável pela mutação de estado da simulação. */
    private Thread eventProcessorThread;

    /**
     * Mecanismo de exclusão mútua para controlo de fases semafóricas.
     * Configurado com política de justiça (fairness=true) para evitar inanição
     * (starvation) de direções com menos tráfego.
     */
    private final Lock trafficCoordinationLock;

    /**
     * Estado volátil que indica a direção ativa. Apenas uma direção pode deter o
     * token 'Green' por vez.
     */
    private volatile String currentGreenDirection;

    private SocketClient dashboardClient;

    // Métricas voláteis para acesso atómico sem bloqueio
    private volatile int totalArrivals = 0;
    private volatile int totalDepartures = 0;

    /**
     * Inicializa o processo da interseção, carregando a topologia e preparando o
     * motor DES.
     *
     * @param intersectionId O identificador único na malha (ex: "Cr1").
     * @param configFilePath Caminho para o ficheiro de propriedades.
     * @throws IOException Se falhar o bind da porta ou leitura de config.
     */
    public IntersectionProcess(String intersectionId, String configFilePath) throws IOException {
        this.intersectionId = intersectionId;
        this.config = new SimulationConfig(configFilePath);
        this.intersection = new Intersection(intersectionId);
        this.outgoingConnections = new HashMap<>();
        this.connectionHandlerPool = Executors.newCachedThreadPool();
        this.statsExecutor = Executors.newSingleThreadScheduledExecutor();
        this.departureExecutor = Executors.newScheduledThreadPool(4);
        this.running = false;
        this.trafficCoordinationLock = new ReentrantLock(true); // Fair lock to prevent starvation
        this.currentGreenDirection = null;
        this.timeScale = config.getTimeScale();

        // Initialize DES components
        this.clock = new SimulationClock();
        this.eventQueue = new EventQueue(true); // Track history for debugging
        this.eventLogger = EventLogger.getInstance();

        eventLogger.log(sd.logging.EventType.PROCESS_STARTED, intersectionId,
                "Intersection process initialized with DES architecture");

        System.out.println("=".repeat(60));
        System.out.println("INTERSECTION PROCESS: " + intersectionId + " (DES Mode)");
        System.out.println("=".repeat(60));
    }

    /**
     * Inicia o ciclo principal do motor de simulação (DES Engine Loop).
     * <p>
     * Executa o ciclo "Fetch-Decode-Execute":
     * <ol>
     * <li>Remove o evento com menor timestamp da fila (Fetch).</li>
     * <li>Avança o relógio virtual para o tempo do evento.</li>
     * <li>Aplica atraso artificial se {@code timeScale > 0} (para visualização
     * humana).</li>
     * <li>Despacha o evento para o manipulador apropriado (Execute).</li>
     * </ol>
     */
    private void startEventProcessor() {
        eventProcessorThread = new Thread(() -> {
            eventLogger.log(sd.logging.EventType.SIMULATION_STARTED, intersectionId,
                    "Event processor thread started");

            // Keep running while the process is active
            double lastTime = 0.0;
            while (running) {
                SimulationEvent event = eventQueue.poll();
                if (event == null) {
                    // Backoff exponencial ou sleep curto para evitar busy-waiting em idle
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                    continue;
                }

                // Aplicação de escala temporal (Throttle)
                if (timeScale > 0) {
                    double simTimeDelta = event.getTimestamp() - lastTime;
                    long realDelayMs = (long) (simTimeDelta * timeScale * 1000);
                    if (realDelayMs > 0) {
                        try {
                            Thread.sleep(realDelayMs);
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                    lastTime = event.getTimestamp();
                }

                // Atualização atómica do tempo de simulação
                clock.advanceTo(event.getTimestamp());

                // Processamento polimórfico
                processEvent(event);
            }

            eventLogger.log(sd.logging.EventType.SIMULATION_STOPPED, intersectionId,
                    String.format("Event processor thread terminated at time %.2f", clock.getCurrentTime()));
        }, "EventProcessor-" + intersectionId);

        eventProcessorThread.start();
    }

    /**
     * Despachante central de eventos.
     * <p>
     * Encaminha o evento para a lógica de negócio específica baseada no tipo
     * {@link DESEventType}.
     *
     * @param event O evento de simulação a ser processado.
     */
    private void processEvent(SimulationEvent event) {
        try {
            switch (event.getType()) {
                case TRAFFIC_LIGHT_CHANGE:
                    handleTrafficLightChangeEvent(event);
                    break;

                case VEHICLE_ARRIVAL:
                    // Chegadas são tratadas reativamente via Socket, mas eventos podem ser usados
                    // para métricas
                    break;

                case VEHICLE_CROSSING_START:
                    handleVehicleCrossingStartEvent(event);
                    break;

                case VEHICLE_CROSSING_END:
                    handleVehicleCrossingEndEvent(event);
                    break;

                case SIMULATION_END:
                    handleSimulationEndEvent(event);
                    break;

                default:
                    System.err.println("[" + intersectionId + "] Unknown event type: " + event.getType());
            }
        } catch (Exception e) {
            System.err.println("[" + intersectionId + "] Error processing event " + event.getType() +
                    " at time " + event.getTimestamp() + ": " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * Gere a máquina de estados dos semáforos.
     * <p>
     * O fluxo de execução é o seguinte:
     * <ol>
     * <li>Atualiza o estado do semáforo (Verde <-> Vermelho).</li>
     * <li>Se o novo estado for Verde: Calcula a capacidade de vazão e agenda
     * travessias (Service Events).</li>
     * <li>Agenda recursivamente a próxima mudança de estado para manter o ciclo
     * infinito.</li>
     * </ol>
     *
     * @param event O evento que desencadeou a mudança de estado.
     */
    private void handleTrafficLightChangeEvent(SimulationEvent event) {
        TrafficLightEvent tlEvent = (TrafficLightEvent) event.getPayload();
        TrafficLight light = tlEvent.getLight();
        String direction = tlEvent.getDirection();

        // Toggle state
        TrafficLightState oldState = light.getState();
        TrafficLightState newState = (oldState == TrafficLightState.GREEN) ? TrafficLightState.RED
                : TrafficLightState.GREEN;

        light.changeState(newState);

        sd.logging.EventType logEventType = (newState == TrafficLightState.GREEN)
                ? sd.logging.EventType.LIGHT_CHANGED_GREEN
                : sd.logging.EventType.LIGHT_CHANGED_RED;

        eventLogger.log(logEventType, intersectionId,
                String.format("Direction %s changed to %s at time %.2f",
                        direction, newState, event.getTimestamp()));

        // Processamento de lote (Batch Processing) para a fase Verde
        if (newState == TrafficLightState.GREEN) {
            processQueuedVehiclesForLight(light, event.getTimestamp());
        }

        // Agendamento do próximo ciclo (Feedback Loop)
        double nextChangeTime = event.getTimestamp() +
                (newState == TrafficLightState.GREEN ? light.getGreenTime() : light.getRedTime());

        SimulationEvent nextEvent = new SimulationEvent(
                nextChangeTime,
                DESEventType.TRAFFIC_LIGHT_CHANGE,
                tlEvent);
        eventQueue.schedule(nextEvent);
    }

    /**
     * Calcula a vazão da interseção durante uma fase verde.
     * <p>
     * Implementa uma lógica de previsão ("Look-ahead"):
     * <ol>
     * <li>Itera sobre a fila de espera do semáforo.</li>
     * <li>Calcula o tempo de serviço acumulado (Service Time) baseado no tipo de
     * veículo.</li>
     * <li>Agenda a partida apenas se o veículo couber na janela temporal restante
     * do sinal verde.</li>
     * </ol>
     *
     * @param light       O semáforo ativo.
     * @param currentTime O instante de início da fase verde.
     */
    private void processQueuedVehiclesForLight(TrafficLight light, double currentTime) {
        double greenDuration = light.getGreenTime();
        double timeOffset = 0.0;

        int queueSize = light.getQueueSize();
        System.out.printf("[%s] Processing queue for %s (GREEN for %.2fs, queue size: %d, currentTime=%.2f)%n",
                intersectionId, light.getId(), greenDuration, queueSize, currentTime);

        // Algoritmo de esvaziamento de fila baseado em Time Budget
        while (light.getQueueSize() > 0) {
            // Estimativa inicial (optimista)
            double crossingTime = config.getLightVehicleCrossingTime();

            // Verificação de limite de tempo (Hard Deadline do sinal vermelho)
            if (timeOffset + crossingTime > greenDuration) {
                break; // Veículo não cabe no ciclo atual
            }

            // Commit: Remove da fila
            Vehicle vehicle = light.removeVehicle(currentTime + timeOffset);
            if (vehicle == null)
                break;

            // Recálculo preciso baseado no tipo real do veículo
            crossingTime = getCrossingTimeForVehicle(vehicle);

            // Agendamento do evento futuro de término de travessia
            double crossingStartTime = currentTime + timeOffset;
            scheduleVehicleCrossing(vehicle, crossingStartTime, crossingTime);

            // Incrementa offset para serializar as travessias (Head-of-Line Blocking)
            timeOffset += crossingTime;

            System.out.printf("[%s] Scheduled vehicle %s to cross at t=%.2f (duration=%.2fs)%n",
                    intersectionId, vehicle.getId(), crossingStartTime, crossingTime);
        }
    }

    /**
     * Cria e agenda o evento de conclusão de travessia (Partida).
     *
     * @param vehicle          O veículo que está a atravessar.
     * @param startTime        Instante de início da travessia.
     * @param crossingDuration Duração estimada da travessia.
     */
    private void scheduleVehicleCrossing(Vehicle vehicle, double startTime, double crossingDuration) {
        // Schedule crossing end (when vehicle departs)
        double departureTime = startTime + crossingDuration;

        // Create event with vehicle as payload
        SimulationEvent departureEvent = new SimulationEvent(
                departureTime,
                DESEventType.VEHICLE_CROSSING_END,
                vehicle);
        eventQueue.schedule(departureEvent);

        eventLogger.log(sd.logging.EventType.VEHICLE_QUEUED, intersectionId,
                String.format("Vehicle %s crossing scheduled: %.2fs to %.2fs",
                        vehicle.getId(), startTime, departureTime));
    }

    /**
     * Determina o custo temporal da travessia baseado na física do veículo.
     *
     * @param vehicle O veículo em questão.
     * @return O tempo em segundos necessário para atravessar a interseção.
     */
    private double getCrossingTimeForVehicle(Vehicle vehicle) {
        return switch (vehicle.getType()) {
            case BIKE -> config.getBikeVehicleCrossingTime();
            case LIGHT -> config.getLightVehicleCrossingTime();
            case HEAVY -> config.getHeavyVehicleCrossingTime();
            default -> config.getLightVehicleCrossingTime();
        };
    }

    /**
     * Manipula o evento de início de travessia de um veículo.
     * <p>
     * Atualmente serve como placeholder para lógica futura de animação ou
     * ocupação de zonas críticas na interseção.
     *
     * @param event O evento de início de travessia.
     */
    private void handleVehicleCrossingStartEvent(SimulationEvent event) {
        // Placeholder para lógica futura de animação ou ocupação de zona crítica
        eventLogger.log(sd.logging.EventType.VEHICLE_DEPARTED, intersectionId,
                "Vehicle crossing started at time " + event.getTimestamp());
    }

    /**
     * Finaliza a lógica de travessia e inicia a transferência (handover) para o
     * próximo nó.
     * <p>
     * Este método é invocado quando o tempo de travessia expira no relógio virtual.
     * Executa as seguintes ações:
     * <ol>
     * <li>Atualiza as métricas de tempo de travessia do veículo.</li>
     * <li>Incrementa contadores locais de veículos processados.</li>
     * <li>Transfere a responsabilidade do veículo para a rede, enviando-o ao
     * próximo destino.</li>
     * </ol>
     *
     * @param event O evento de fim de travessia.
     */
    private void handleVehicleCrossingEndEvent(SimulationEvent event) {
        Vehicle vehicle = (Vehicle) event.getPayload();

        // Atualiza métricas do veículo
        double crossingTime = getCrossingTimeForVehicle(vehicle);
        vehicle.addCrossingTime(crossingTime);

        // Atualiza métricas locais
        intersection.incrementVehiclesSent();

        // Handover: Transfere a responsabilidade do veículo para a rede
        sendVehicleToNextDestination(vehicle);

        eventLogger.log(sd.logging.EventType.VEHICLE_DEPARTED, intersectionId,
                String.format("Vehicle %s departed at time %.2f", vehicle.getId(), event.getTimestamp()));
    }

    /**
     * Finaliza a execução do processo de simulação.
     * 
     * @param event O evento de fim de simulação.
     */
    private void handleSimulationEndEvent(SimulationEvent event) {
        eventLogger.log(sd.logging.EventType.SIMULATION_STOPPED, intersectionId,
                String.format("Simulation ended at time %.2f", event.getTimestamp()));
        running = false;
    }

    /**
     * Exporta o histórico completo de eventos para análise post-mortem.
     *
     * @param outputPath O caminho do ficheiro onde o histórico será guardado.
     */
    public void exportEventHistory(String outputPath) {
        String history = eventQueue.exportEventHistory();
        try (java.io.PrintWriter writer = new java.io.PrintWriter(outputPath)) {
            writer.println(history);
            System.out.println("[" + intersectionId + "] Event history exported to: " + outputPath);
        } catch (java.io.FileNotFoundException e) {
            System.err.println("[" + intersectionId + "] Failed to export event history: " + e.getMessage());
        }
    }

    /**
     * Ponto de entrada principal da aplicação.
     *
     * @param args Argumentos da linha de comando (ID da interseção e ficheiro de
     *             configuração opcional).
     */
    public static void main(String[] args) {
        if (args.length < 1) {
            System.err.println("Usage: java IntersectionProcess <intersectionId> [configFile]");
            System.err.println("Example: java IntersectionProcess Cr1");
            System.exit(1);
        }

        String intersectionId = args[0];
        String configFile = args.length > 1 ? args[1] : "src/main/resources/simulation.properties";

        try {
            IntersectionProcess process = new IntersectionProcess(intersectionId, configFile);
            process.initialize();
            process.start();

            // Add shutdown hook
            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                System.out.println("\nShutdown signal received...");
                process.shutdown();
            }));

        } catch (IOException e) {
            System.err.println("Failed to start intersection process: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }

    /**
     * Realiza o bootstrap dos componentes lógicos e de rede da interseção.
     * <p>
     * Inclui a criação de semáforos, configuração de encaminhamento e conexão ao
     * Dashboard.
     */
    public void initialize() {
        System.out.println("\n[" + intersectionId + "] Initializing intersection...");

        createTrafficLights();

        configureRouting();

        connectToDashboard();

        System.out.println("[" + intersectionId + "] Initialization complete.");
    }

    /**
     * Estabelece a conexão com o Dashboard para envio de telemetria em tempo real.
     */
    private void connectToDashboard() {
        try {
            String dashboardHost = config.getDashboardHost();
            int dashboardPort = config.getDashboardPort();

            System.out.println("[" + intersectionId + "] Connecting to dashboard at " +
                    dashboardHost + ":" + dashboardPort + "...");

            dashboardClient = new SocketClient(intersectionId, dashboardHost, dashboardPort);
            dashboardClient.connect();

            System.out.println("[" + intersectionId + "] Connected to dashboard.");

        } catch (IOException e) {
            System.err.println("[" + intersectionId + "] Failed to connect to dashboard: " +
                    e.getMessage());
            System.err.println("[" + intersectionId + "] Will continue without dashboard reporting.");
            dashboardClient = null;
        }
    }

    /**
     * Inicializa os semáforos da interseção com base na configuração carregada.
     */
    private void createTrafficLights() {
        System.out.println("\n[" + intersectionId + "] Creating traffic lights...");

        SimulationConfig.IntersectionConfig intersectionConfig = getIntersectionConfig();
        List<String> directions = intersectionConfig.getLights();

        if (directions == null || directions.isEmpty()) {
            System.err.println("  Warning: No traffic lights configured for " + intersectionId);
            return;
        }

        for (String direction : directions) {
            double greenTime = config.getTrafficLightGreenTime(intersectionId, direction);
            double redTime = config.getTrafficLightRedTime(intersectionId, direction);

            TrafficLight light = new TrafficLight(
                    intersectionId + "-" + direction,
                    direction,
                    greenTime,
                    redTime);

            intersection.addTrafficLight(light);
            System.out.println("  Created traffic light: " + direction +
                    " (Green: " + greenTime + "s, Red: " + redTime + "s)");
        }
    }

    /**
     * Obtém a configuração específica para esta interseção a partir da configuração
     * global.
     *
     * @return O objeto de configuração da interseção.
     * @throws RuntimeException Se a configuração estiver em falta.
     */
    private SimulationConfig.IntersectionConfig getIntersectionConfig() {
        if (config.getNetworkConfig() == null || config.getNetworkConfig().getIntersections() == null) {
            throw new RuntimeException("Network configuration not loaded or empty.");
        }
        return config.getNetworkConfig().getIntersections().stream()
                .filter(i -> i.getId().equals(intersectionId))
                .findFirst()
                .orElseThrow(() -> new RuntimeException("Intersection config not found for " + intersectionId));
    }

    /**
     * Configura a tabela de encaminhamento (routing) da interseção.
     * <p>
     * Define para cada destino qual a direção de saída (semáforo) correspondente.
     */
    private void configureRouting() {
        System.out.println("\n[" + intersectionId + "] Configuring routing...");

        SimulationConfig.IntersectionConfig intersectionConfig = getIntersectionConfig();
        Map<String, String> routes = intersectionConfig.getRoutes();

        if (routes != null) {
            for (Map.Entry<String, String> entry : routes.entrySet()) {
                String destination = entry.getKey();
                String direction = entry.getValue();
                intersection.configureRoute(destination, direction);
                System.out.println("  Route configured: To " + destination + " -> Use " + direction);
            }
        } else {
            System.out.println("  No routes configured.");
        }

        System.out.println("  Routing configured.");
    }

    /**
     * Primitiva de bloqueio: Solicita acesso exclusivo à zona crítica da
     * interseção.
     *
     * @param direction A direção que solicita passagem.
     */
    public void requestGreenLight(String direction) {
        trafficCoordinationLock.lock();
        currentGreenDirection = direction;
    }

    /**
     * Primitiva de bloqueio: Liberta o acesso exclusivo à zona crítica.
     *
     * @param direction A direção que está a libertar a passagem.
     */
    public void releaseGreenLight(String direction) {
        if (direction.equals(currentGreenDirection)) {
            currentGreenDirection = null;
            trafficCoordinationLock.unlock();
        }
    }

    /**
     * Inicializa o estado dos semáforos no arranque da simulação (t=0).
     * <p>
     * Garante que apenas um semáforo começa em Verde e os restantes em Vermelho,
     * agendando os eventos iniciais na fila do DES.
     */
    private void scheduleInitialTrafficLightEvents() {
        System.out.println("\n[" + intersectionId + "] Scheduling initial traffic light events (DES mode)...");

        double currentTime = clock.getCurrentTime();
        System.out.printf("[%s] Initial clock time: %.2f%n", intersectionId, currentTime);

        for (TrafficLight light : intersection.getTrafficLights()) {
            String direction = light.getDirection();

            // Lógica de arranque: Primeiro da lista = Verde, outros = Vermelho
            boolean isFirstLight = intersection.getTrafficLights().indexOf(light) == 0;
            TrafficLightState initialState = isFirstLight ? TrafficLightState.GREEN : TrafficLightState.RED;
            light.changeState(initialState);

            // Agenda a primeira transição
            double firstChangeTime = currentTime +
                    (initialState == TrafficLightState.GREEN ? light.getGreenTime() : light.getRedTime());

            TrafficLightEvent tlEvent = new TrafficLightEvent(light, direction, intersectionId);
            SimulationEvent event = new SimulationEvent(
                    firstChangeTime,
                    DESEventType.TRAFFIC_LIGHT_CHANGE,
                    tlEvent);
            eventQueue.schedule(event);

            System.out.println("  Scheduled first event for direction " + direction +
                    " (initial: " + initialState + ", change at t=" + firstChangeTime + ")");

            eventLogger.log(
                    initialState == TrafficLightState.GREEN ? sd.logging.EventType.LIGHT_CHANGED_GREEN
                            : sd.logging.EventType.LIGHT_CHANGED_RED,
                    intersectionId,
                    "Direction " + direction + " initialized to " + initialState);
        }
    }

    /**
     * Encaminhamento de rede: Serializa e envia o objeto veículo para o próximo nó.
     * <p>
     * Calcula também o tempo de viagem virtual entre nós (Edge Weight).
     *
     * @param vehicle O veículo a ser enviado.
     */
    public void sendVehicleToNextDestination(Vehicle vehicle) {
        String nextDestination = vehicle.getCurrentDestination();

        // Cálculo de latência de viagem (Edge Weight)
        double baseTime = config.getBaseTravelTime();
        double multiplier = 1.0;
        switch (vehicle.getType()) {
            case BIKE -> multiplier = config.getBikeTravelTimeMultiplier();
            case HEAVY -> multiplier = config.getHeavyTravelTimeMultiplier();
            default -> multiplier = 1.0;
        }
        double travelTime = baseTime * multiplier;

        System.out.printf("[%s] Vehicle %s departing to %s. Travel time: %.2fs%n",
                intersectionId, vehicle.getId(), nextDestination, travelTime);

        recordVehicleDeparture();

        // Envio imediato (o delay de viagem é implícito no tempo de chegada no próximo
        // nó ou simulado aqui)
        sendVehicleImmediately(vehicle, nextDestination);
    }

    /**
     * Envia o veículo imediatamente para o próximo nó via conexão TCP persistente.
     *
     * @param vehicle         O veículo a ser enviado.
     * @param nextDestination O identificador do próximo nó destino.
     */
    private void sendVehicleImmediately(Vehicle vehicle, String nextDestination) {
        try {
            // Lazy loading da conexão
            SocketConnection connection = getOrCreateConnection(nextDestination);

            // Encapsulamento da mensagem
            MessageProtocol message = new Message(
                    MessageType.VEHICLE_TRANSFER,
                    intersectionId,
                    nextDestination,
                    vehicle,
                    System.currentTimeMillis());

            connection.sendMessage(message);

            System.out.println("[" + intersectionId + "] Vehicle " + vehicle.getId() +
                    " arrived at " + nextDestination + " (msg sent)");

        } catch (IOException | InterruptedException e) {
            System.err.println("[" + intersectionId + "] Failed to send vehicle " +
                    vehicle.getId() + " to " + nextDestination + ": " + e.getMessage());
        }
    }

    /**
     * Obtém ou cria uma conexão para o destino especificado (Singleton por
     * destino).
     * <p>
     * Este método é thread-safe.
     *
     * @param destinationId O identificador do nó destino.
     * @return A conexão TCP estabelecida.
     * @throws IOException          Se ocorrer um erro de I/O na criação da conexão.
     * @throws InterruptedException Se a thread for interrompida durante a espera.
     */
    private synchronized SocketConnection getOrCreateConnection(String destinationId)
            throws IOException, InterruptedException {

        if (!outgoingConnections.containsKey(destinationId)) {
            String host = getHostForDestination(destinationId);
            int port = getPortForDestination(destinationId);

            System.out.println("[" + intersectionId + "] Creating connection to " +
                    destinationId + " at " + host + ":" + port);

            SocketConnection connection = new SocketConnection(host, port);
            outgoingConnections.put(destinationId, connection);
        }

        return outgoingConnections.get(destinationId);
    }

    /**
     * Resolve o hostname ou endereço IP para um determinado destino.
     *
     * @param destinationId O ID do destino.
     * @return O endereço do host.
     */
    private String getHostForDestination(String destinationId) {
        if (destinationId.equals("S")) {
            return config.getExitHost();
        } else {
            return config.getIntersectionHost(destinationId);
        }
    }

    /**
     * Resolve a porta TCP para um determinado destino.
     *
     * @param destinationId O ID do destino.
     * @return O número da porta.
     */
    private int getPortForDestination(String destinationId) {
        if (destinationId.equals("S")) {
            return config.getExitPort();
        } else {
            return config.getIntersectionPort(destinationId);
        }
    }

    /**
     * Inicia o servidor e o loop de aceitação de conexões.
     * <p>
     * Este método bloqueia a thread chamadora durante a execução do servidor.
     *
     * @throws IOException Se ocorrer um erro ao fazer bind da porta.
     */
    public void start() throws IOException {
        int port = config.getIntersectionPort(intersectionId);
        serverSocket = new ServerSocket(port);
        running = true;

        System.out.println("\n[" + intersectionId + "] Server started on port " + port);

        // DES Mode: Schedule initial events and start event processor
        scheduleInitialTrafficLightEvents();
        startEventProcessor();
        System.out.println("[" + intersectionId + "] Running in DES mode");

        // Background task para telemetria
        statsExecutor.scheduleAtFixedRate(this::sendStatsToDashboard, 1, 1, TimeUnit.SECONDS);

        System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n");

        // Loop principal de aceitação de conexões
        while (running) {
            try {
                Socket clientSocket = serverSocket.accept();

                System.out.println("[" + intersectionId + "] New connection accepted from " +
                        clientSocket.getInetAddress().getHostAddress());

                if (!running) {
                    clientSocket.close();
                    break;
                }

                // Configura timeout para evitar bloqueios infinitos em leitura
                try {
                    clientSocket.setSoTimeout(1000);
                } catch (java.net.SocketException e) {
                    System.err.println("[" + intersectionId + "] Failed to set timeout: " + e.getMessage());
                    clientSocket.close();
                    continue;
                }

                // Delega processamento para thread pool (NIO style)
                connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket));

            } catch (IOException e) {
                if (!running) {
                    break; // Shutdown normal
                }
                System.err.println("[" + intersectionId + "] Error accepting connection: " +
                        e.getMessage());
            }
        }
    }

    /**
     * Lógica de tratamento de conexões de entrada (Consumer).
     * <p>
     * Lê continuamente do socket até que a conexão seja fechada, processando
     * mensagens
     * de chegada de veículos ou comandos de simulação.
     *
     * @param clientSocket O socket do cliente conectado.
     */
    private void handleIncomingConnection(Socket clientSocket) {
        try {
            clientSocket.setSoTimeout(1000); // 1 second timeout

        } catch (java.net.SocketException e) {
            System.err.println("[" + intersectionId + "] Failed to set socket timeout: " + e.getMessage());
            return;
        }

        try (SocketConnection connection = new SocketConnection(clientSocket)) {

            System.out.println("[" + intersectionId + "] New connection accepted from " +
                    clientSocket.getInetAddress().getHostAddress());

            while (running && connection.isConnected()) {
                try {
                    MessageProtocol message = connection.receiveMessage();

                    if (message.getType() == MessageType.SIMULATION_START) {
                        System.out.println("[" + intersectionId + "] Simulation start time synchronized");
                        continue;
                    }

                    if (message.getType() == MessageType.VEHICLE_TRANSFER ||
                            message.getType() == MessageType.VEHICLE_SPAWN) {

                        // Lógica de desserialização polimórfica (Vehicle ou Map)
                        Vehicle vehicle;
                        Object payload = message.getPayload();
                        if (payload instanceof Vehicle) {
                            vehicle = (Vehicle) payload;
                        } else if (payload instanceof java.util.Map) {
                            com.google.gson.Gson gson = new com.google.gson.Gson();
                            String json = gson.toJson(payload);
                            vehicle = gson.fromJson(json, Vehicle.class);
                        } else {
                            System.err.println("[" + intersectionId + "] Unknown payload type: " + payload.getClass());
                            continue;
                        }

                        System.out.println("[" + intersectionId + "] Received vehicle: " +
                                vehicle.getId() + " from " + message.getSourceNode());

                        // Lógica de Roteamento Local
                        vehicle.advanceRoute();
                        intersection.receiveVehicle(vehicle, clock.getCurrentTime());

                        System.out.printf("[%s] Vehicle %s queued. Total queue size: %d%n",
                                intersectionId, vehicle.getId(), intersection.getTotalQueueSize());

                        recordVehicleArrival();

                    } else if (message.getType() == MessageType.SHUTDOWN) {
                        System.out.println(
                                "[" + intersectionId + "] Received SHUTDOWN command from " + message.getSourceNode());
                        running = false;
                        break;
                    }

                } catch (java.net.SocketTimeoutException e) {
                    if (!running) {
                        break;
                    }
                } catch (ClassNotFoundException e) {
                    System.err.println("[" + intersectionId + "] Unknown message type received: " +
                            e.getMessage());
                    break;
                } catch (IOException e) {
                    if (running) {
                        System.err.println("[" + intersectionId + "] Failed to deserialize message: " +
                                e.getMessage());
                        e.printStackTrace();
                    }
                    break;
                }
            }

        } catch (IOException e) {
            if (running) {
                System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage());
            }
        }
    }

    /**
     * Procedimento de Encerramento Gracioso (Graceful Shutdown).
     * <ol>
     * <li>Para a aceitação de novas conexões.</li>
     * <li>Envia últimas estatísticas.</li>
     * <li>Encerra pools de threads.</li>
     * <li>Fecha sockets ativos.</li>
     * </ol>
     */
    public void shutdown() {
        if (!running) {
            return;
        }

        System.out.println("\n[" + intersectionId + "] Shutting down...");
        running = false;

        sendStatsToDashboard();

        // 1. Close ServerSocket
        if (serverSocket != null && !serverSocket.isClosed()) {
            try {
                serverSocket.close();
            } catch (IOException e) {
                // Expected
            }
        }

        // 2. Shutdown thread pools
        if (connectionHandlerPool != null && !connectionHandlerPool.isShutdown()) {
            connectionHandlerPool.shutdownNow();
        }
        if (statsExecutor != null && !statsExecutor.isShutdown()) {
            statsExecutor.shutdownNow();
        }
        if (departureExecutor != null && !departureExecutor.isShutdown()) {
            departureExecutor.shutdownNow();
        }

        // 3. Wait briefly for termination
        try {
            if (connectionHandlerPool != null) {
                connectionHandlerPool.awaitTermination(1, TimeUnit.SECONDS);
            }
            if (statsExecutor != null) {
                statsExecutor.awaitTermination(1, TimeUnit.SECONDS);
            }
            if (departureExecutor != null) {
                departureExecutor.awaitTermination(1, TimeUnit.SECONDS);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        // 4. Close outgoing connections
        synchronized (outgoingConnections) {
            for (SocketConnection conn : outgoingConnections.values()) {
                try {
                    conn.close();
                } catch (Exception e) {
                    // Ignore
                }
            }
            outgoingConnections.clear();
        }

        // 5. Close dashboard connection
        if (dashboardClient != null) {
            dashboardClient.close();
        }

        System.out.println("[" + intersectionId + "] Shutdown complete.");
        System.out.println("============================================================\n");
    }

    /**
     * Obtém o modelo de dados da interseção.
     *
     * @return O objeto Intersection.
     */
    public Intersection getIntersection() {
        return intersection;
    }

    /**
     * Regista a chegada de um novo veículo para fins estatísticos.
     */
    public void recordVehicleArrival() {
        totalArrivals++;
    }

    /**
     * Regista a partida de um veículo para fins estatísticos.
     */
    public void recordVehicleDeparture() {
        totalDepartures++;
    }

    /**
     * Envia um "snapshot" do estado atual para o Dashboard (Telemetria Push).
     * <p>
     * Inclui o número acumulado de chegadas, partidas e o tamanho atual das filas.
     */
    private void sendStatsToDashboard() {
        if (dashboardClient == null || !dashboardClient.isConnected()) {
            return;
        }

        try {
            int currentQueueSize = intersection.getTrafficLights().stream()
                    .mapToInt(TrafficLight::getQueueSize)
                    .sum();

            StatsUpdatePayload payload = new StatsUpdatePayload()
                    .setIntersectionArrivals(totalArrivals)
                    .setIntersectionDepartures(totalDepartures)
                    .setIntersectionQueueSize(currentQueueSize);

            sd.model.Message message = new sd.model.Message(
                    MessageType.STATS_UPDATE,
                    intersectionId,
                    "Dashboard",
                    payload);

            dashboardClient.send(message);

            System.out.printf("[%s] Sent stats to dashboard (arrivals=%d, departures=%d, queue=%d)%n",
                    intersectionId, totalArrivals, totalDepartures, currentQueueSize);

        } catch (SerializationException | IOException e) {
            System.err.println("[" + intersectionId + "] Failed to send stats to dashboard: " + e.getMessage());
        }
    }
}