package sd.logging;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import sd.model.Vehicle;

/**
 * Subsistema de auditoria granular responsável pelo rastreio detalhado (Tracing) de veículos individuais.
 * <p>
 * Diferente do {@link EventLogger} (que regista eventos globais do sistema), esta classe foca-se
 * na perspetiva do <b>agente</b>. Cria um ficheiro de rastro dedicado (`.trace`) para cada veículo
 * monitorizado, registando cronologicamente cada interação com a infraestrutura (interseções,
 * filas, semáforos).
 * <p>
 * <b>Funcionalidades:</b>
 * <ul>
 * <li>Análise forense de percursos individuais.</li>
 * <li>Validação de tempos de espera e travessia por nó.</li>
 * <li>Cálculo de eficiência de rota (tempo em movimento vs. tempo parado).</li>
 * </ul>
 */
public class VehicleTracer {

    private static VehicleTracer instance;
    private static final Object instanceLock = new Object();

    /** Mapa thread-safe de sessões de trace ativas (VehicleID -> TraceHandler). */
    private final Map<String, VehicleTrace> trackedVehicles;
    
    private final SimpleDateFormat timestampFormat;
    private final long simulationStartMillis;
    private final String traceDirectory;

    /**
     * Inicializa o tracer e prepara o diretório de saída.
     *
     * @param traceDirectory Caminho para armazenamento dos ficheiros .trace.
     */
    private VehicleTracer(String traceDirectory) {
        this.trackedVehicles = new ConcurrentHashMap<>();
        this.timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        this.simulationStartMillis = System.currentTimeMillis();
        this.traceDirectory = traceDirectory;

        try {
            java.nio.file.Files.createDirectories(java.nio.file.Paths.get(traceDirectory));
        } catch (IOException e) {
            System.err.println("Failed to create trace directory: " + e.getMessage());
        }
    }

    /**
     * Obtém a instância única do tracer (Singleton).
     * @return A instância global.
     */
    public static VehicleTracer getInstance() {
        if (instance == null) {
            synchronized (instanceLock) {
                if (instance == null) {
                    instance = new VehicleTracer("logs/traces");
                }
            }
        }
        return instance;
    }

    /**
     * Reinicializa o tracer com um diretório personalizado.
     * Útil para isolar logs de diferentes execuções em lote.
     */
    public static void initialize(String traceDirectory) {
        synchronized (instanceLock) {
            if (instance != null) {
                instance.shutdown();
            }
            instance = new VehicleTracer(traceDirectory);
        }
    }

    /**
     * Inicia a sessão de rastreio para um veículo específico.
     * Cria o ficheiro {@code logs/traces/vehicle-{id}.trace} e escreve o cabeçalho.
     *
     * @param vehicleId O identificador único do veículo.
     */
    public void startTracking(String vehicleId) {
        if (trackedVehicles.containsKey(vehicleId)) {
            return; // Já está a ser rastreado
        }

        VehicleTrace trace = new VehicleTrace(vehicleId, traceDirectory);
        trackedVehicles.put(vehicleId, trace);

        trace.logEvent("TRACKING_STARTED", "", "Started tracking vehicle " + vehicleId);
    }

    /**
     * Encerra a sessão de rastreio, fecha o descritor de ficheiro e remove da memória.
     */
    public void stopTracking(String vehicleId) {
        VehicleTrace trace = trackedVehicles.remove(vehicleId);
        if (trace != null) {
            trace.logEvent("TRACKING_STOPPED", "", "Stopped tracking vehicle " + vehicleId);
            trace.close();
        }
    }

    /**
     * Verifica se um veículo está atualmente sob auditoria.
     */
    public boolean isTracking(String vehicleId) {
        return trackedVehicles.containsKey(vehicleId);
    }

    /**
     * Regista o evento de instanciação do veículo pelo Coordenador.
     */
    public void logGenerated(Vehicle vehicle) {
        if (!isTracking(vehicle.getId()))
            return;

        VehicleTrace trace = trackedVehicles.get(vehicle.getId());
        if (trace != null) {
            trace.logEvent("GENERATED", "Coordinator",
                    String.format("Type: %s, Entry Time: %.2fs, Route: %s",
                            vehicle.getType(), vehicle.getEntryTime(), vehicle.getRoute()));
        }
    }

    /**
     * Regista a chegada física do veículo à zona de deteção de uma interseção.
     */
    public void logArrival(String vehicleId, String intersection, double simulationTime) {
        if (!isTracking(vehicleId))
            return;

        VehicleTrace trace = trackedVehicles.get(vehicleId);
        if (trace != null) {
            trace.logEvent("ARRIVED", intersection,
                    String.format("Arrived at %s (sim time: %.2fs)", intersection, simulationTime));
        }
    }

    /**
     * Regista a entrada do veículo na estrutura de fila de um semáforo.
     */
    public void logQueued(String vehicleId, String intersection, String direction, int queuePosition) {
        if (!isTracking(vehicleId))
            return;

        VehicleTrace trace = trackedVehicles.get(vehicleId);
        if (trace != null) {
            trace.logEvent("QUEUED", intersection,
                    String.format("Queued at %s-%s (position: %d)", intersection, direction, queuePosition));
        }
    }

    /**
     * Regista o início da espera ativa (veículo parado no Vermelho).
     */
    public void logWaitingStart(String vehicleId, String intersection, String direction) {
        if (!isTracking(vehicleId))
            return;

        VehicleTrace trace = trackedVehicles.get(vehicleId);
        if (trace != null) {
            trace.logEvent("WAITING_START", intersection,
                    String.format("Started waiting at %s-%s (light is RED)", intersection, direction));
        }
    }

    /**
     * Regista o fim da espera (Sinal Verde).
     * @param waitTime Duração total da paragem nesta instância.
     */
    public void logWaitingEnd(String vehicleId, String intersection, String direction, double waitTime) {
        if (!isTracking(vehicleId))
            return;

        VehicleTrace trace = trackedVehicles.get(vehicleId);
        if (trace != null) {
            trace.logEvent("WAITING_END", intersection,
                    String.format("Finished waiting at %s-%s (waited %.2fs)", intersection, direction, waitTime));
        }
    }

    /**
     * Regista o início da travessia da interseção (ocupação da zona crítica).
     */
    public void logCrossingStart(String vehicleId, String intersection, String direction) {
        if (!isTracking(vehicleId))
            return;

        VehicleTrace trace = trackedVehicles.get(vehicleId);
        if (trace != null) {
            trace.logEvent("CROSSING_START", intersection,
                    String.format("Started crossing %s-%s (light is GREEN)", intersection, direction));
        }
    }

    /**
     * Regista a libertação da zona crítica da interseção.
     */
    public void logCrossingEnd(String vehicleId, String intersection, double crossingTime) {
        if (!isTracking(vehicleId))
            return;

        VehicleTrace trace = trackedVehicles.get(vehicleId);
        if (trace != null) {
            trace.logEvent("CROSSING_END", intersection,
                    String.format("Finished crossing %s (took %.2fs)", intersection, crossingTime));
        }
    }

    /**
     * Regista a partida da interseção em direção ao próximo nó.
     */
    public void logDeparture(String vehicleId, String intersection, String nextDestination) {
        if (!isTracking(vehicleId))
            return;

        VehicleTrace trace = trackedVehicles.get(vehicleId);
        if (trace != null) {
            trace.logEvent("DEPARTED", intersection,
                    String.format("Departed from %s toward %s", intersection, nextDestination));
        }
    }

    /**
     * Regista a saída do sistema (no Exit Node).
     * <p>
     * Este método também desencadeia a escrita do <b>Sumário de Viagem</b> no final do log
     * e fecha o ficheiro automaticamente.
     */
    public void logExit(Vehicle vehicle, double systemTime) {
        if (!isTracking(vehicle.getId()))
            return;

        VehicleTrace trace = trackedVehicles.get(vehicle.getId());
        if (trace != null) {
            trace.logEvent("EXITED", "Exit Node",
                    String.format("Exited system - Total time: %.2fs, Waiting: %.2fs, Crossing: %.2fs",
                            systemTime, vehicle.getTotalWaitingTime(), vehicle.getTotalCrossingTime()));

            // Escreve estatísticas sumarizadas
            trace.writeSummary(vehicle, systemTime);

            // Stop tracking and close file
            stopTracking(vehicle.getId());
        }
    }

    /**
     * Fecha forçosamente todos os traces abertos.
     * Deve ser chamado no shutdown da simulação para evitar corrupção de logs.
     */
    public void shutdown() {
        for (VehicleTrace trace : trackedVehicles.values()) {
            trace.close();
        }
        trackedVehicles.clear();
    }

    /**
     * Classe interna auxiliar que gere o descritor de ficheiro e a formatação para um único veículo.
     */
    private class VehicleTrace {
        private final String vehicleId;
        private final PrintWriter writer;
        private final long traceStartMillis;

        VehicleTrace(String vehicleId, String directory) {
            this.vehicleId = vehicleId;
            this.traceStartMillis = System.currentTimeMillis();

            PrintWriter w = null;
            try {
                String filename = String.format("%s/vehicle-%s.trace", directory, vehicleId);
                w = new PrintWriter(new BufferedWriter(new FileWriter(filename, false)), true);

                // Write header
                w.println("=".repeat(80));
                w.println("VEHICLE TRACE: " + vehicleId);
                w.println("Trace Started: " + timestampFormat.format(new Date()));
                w.println("=".repeat(80));
                w.println();
                w.printf("%-23s | %-8s | %-15s | %-15s | %s\n",
                        "TIMESTAMP", "REL_TIME", "EVENT", "LOCATION", "DESCRIPTION");
                w.println("-".repeat(80));

            } catch (IOException e) {
                System.err.println("Failed to create trace file for " + vehicleId + ": " + e.getMessage());
            }

            this.writer = w;
        }

        void logEvent(String eventType, String location, String description) {
            if (writer == null)
                return;

            long now = System.currentTimeMillis();
            String timestamp = timestampFormat.format(new Date(now));
            double relativeTime = (now - traceStartMillis) / 1000.0;

            writer.printf("%-23s | %8.3fs | %-15s | %-15s | %s\n",
                    timestamp,
                    relativeTime,
                    truncate(eventType, 15),
                    truncate(location, 15),
                    description);
            writer.flush();
        }

        void writeSummary(Vehicle vehicle, double systemTime) {
            if (writer == null)
                return;

            writer.println();
            writer.println("=".repeat(80));
            writer.println("JOURNEY SUMMARY");
            writer.println("=".repeat(80));
            writer.println("Vehicle ID: " + vehicle.getId());
            writer.println("Vehicle Type: " + vehicle.getType());
            writer.println("Route: " + vehicle.getRoute());
            writer.println();
            writer.printf("Entry Time: %.2f seconds\n", vehicle.getEntryTime());
            writer.printf("Total System Time: %.2f seconds\n", systemTime);
            writer.printf("Total Waiting Time: %.2f seconds (%.1f%%)\n",
                    vehicle.getTotalWaitingTime(),
                    100.0 * vehicle.getTotalWaitingTime() / systemTime);
            writer.printf("Total Crossing Time: %.2f seconds (%.1f%%)\n",
                    vehicle.getTotalCrossingTime(),
                    100.0 * vehicle.getTotalCrossingTime() / systemTime);
            writer.printf("Travel Time: %.2f seconds (%.1f%%)\n",
                    systemTime - vehicle.getTotalWaitingTime() - vehicle.getTotalCrossingTime(),
                    100.0 * (systemTime - vehicle.getTotalWaitingTime() - vehicle.getTotalCrossingTime()) / systemTime);
            writer.println("=".repeat(80));
        }

        void close() {
            if (writer != null) {
                writer.println();
                writer.println("-".repeat(80));
                writer.println("END OF TRACE");
                writer.println("=".repeat(80));
                writer.close();
            }
        }

        private String truncate(String str, int maxLength) {
            if (str == null)
                return "";
            return str.length() <= maxLength ? str : str.substring(0, maxLength);
        }
    }
}