using UnityEngine;
using System.Collections;
using System.IO;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;

public class SimMonitor : MonoBehaviour
{
    private const float _updateRate = 0.1f; // 100 Hz
    private string _telemetryBinPath;
    private string _eventLogPath;
    private Coroutine _monitorRoutine;

    private string _sessionDirectory;


    private FileStream _telemetryFileStream;
    private BinaryWriter _telemetryBinaryWriter;

    [SerializeField]
    private List<EventRecord> _eventLogCache;

    [System.Serializable]
    private class EventRecord
    {
        public float Time;
        public float PositionX;
        public float PositionY;
        public float PositionZ;
        public string EventType;
        public string Details;
    }

    private void Awake() {
        InitializeSessionDirectory();
    }

    private void Start()
    {
        SimManager.Instance.OnSimulationStarted += RegisterSimulationStarted;
        SimManager.Instance.OnSimulationEnded += RegisterSimulationEnded;
        SimManager.Instance.OnNewThreat += RegisterNewThreat;
        SimManager.Instance.OnNewInterceptor += RegisterNewInterceptor;
    }

    private void InitializeSessionDirectory() {
        string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
        _sessionDirectory = Application.persistentDataPath + $"\\Telemetry\\Logs\\{timestamp}";
        Directory.CreateDirectory(_sessionDirectory);
        Debug.Log($"Monitoring simulation logs to {_sessionDirectory}");
    }

    private void InitializeLogFiles()
    {
        string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
        
        _eventLogPath = Path.Combine(_sessionDirectory, $"sim_events_{timestamp}.csv");
        
        // Initialize the event log cache
        _eventLogCache = new List<EventRecord>();

        _telemetryBinPath = Path.Combine(_sessionDirectory, $"sim_telemetry_{timestamp}.bin");

        // Open the file stream and binary writer for telemetry data
        _telemetryFileStream = new FileStream(_telemetryBinPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
        _telemetryBinaryWriter = new BinaryWriter(_telemetryFileStream);

        Debug.Log("Log files initialized successfully.");
    }

    private void CloseLogFiles() {
        if (_telemetryBinaryWriter != null)
        {
            _telemetryBinaryWriter.Flush();
            _telemetryBinaryWriter.Close();
            _telemetryBinaryWriter = null;
        }

        if (_telemetryFileStream != null)
        {
            _telemetryFileStream.Close();
            _telemetryFileStream = null;
        }

    }

    private IEnumerator MonitorRoutine()
    {
        while (true)
        {
            RecordTelemetry();
            yield return new WaitForSeconds(_updateRate);
        }
    }

    private void RecordTelemetry()
    {
        float time = (float)SimManager.Instance.GetElapsedSimulationTime();
        var agents = SimManager.Instance.GetActiveAgents();
        if(_telemetryBinaryWriter == null) {
            Debug.LogWarning("Telemetry binary writer is null");
            return;
        }
        for (int i = 0; i < agents.Count; i++)
        {
            var agent = agents[i];

            if (!agent.gameObject.activeInHierarchy)
                continue;

            Vector3 pos = agent.transform.position;

            if (pos == Vector3.zero)
                continue;

            Vector3 vel = agent.GetVelocity(); // Ensure GetVelocity() doesn't allocate

            int agentID = agent.GetInstanceID();
            int flightPhase = (int)agent.GetFlightPhase();
            byte agentType = (byte)(agent is Threat ? 0 : 1);

            // Write telemetry data directly to the binary file
            _telemetryBinaryWriter.Write(time);
            _telemetryBinaryWriter.Write(agentID);
            _telemetryBinaryWriter.Write(pos.x);
            _telemetryBinaryWriter.Write(pos.y);
            _telemetryBinaryWriter.Write(pos.z);
            _telemetryBinaryWriter.Write(vel.x);
            _telemetryBinaryWriter.Write(vel.y);
            _telemetryBinaryWriter.Write(vel.z);
            _telemetryBinaryWriter.Write(flightPhase);
            _telemetryBinaryWriter.Write(agentType);
        }
    }

    public void ConvertBinaryTelemetryToCsv(string binaryFilePath, string csvFilePath)
    {
        try
        {
            using FileStream fs = new FileStream(binaryFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            using BinaryReader reader = new BinaryReader(fs);
            using StreamWriter writer = new StreamWriter(csvFilePath, false);
            {
                // Write CSV header
                writer.WriteLine("Time,AgentID,AgentX,AgentY,AgentZ,AgentVX,AgentVY,AgentVZ,AgentState,AgentType");

                while (reader.BaseStream.Position != reader.BaseStream.Length)
                {
                    float time = reader.ReadSingle();
                    int agentID = reader.ReadInt32();
                    float posX = reader.ReadSingle();
                    float posY = reader.ReadSingle();
                    float posZ = reader.ReadSingle();
                    float velX = reader.ReadSingle();
                    float velY = reader.ReadSingle();
                    float velZ = reader.ReadSingle();
                    int flightPhase = reader.ReadInt32();
                    byte agentTypeByte = reader.ReadByte();
                    string agentType = agentTypeByte == 0 ? "T" : "M";

                    // Write the data to CSV
                    writer.WriteLine($"{time:F2},{agentID},{posX:F2},{posY:F2},{posZ:F2},{velX:F2},{velY:F2},{velZ:F2},{flightPhase},{agentType}");
                }
            }
        }
        catch (IOException e)
        {
            Debug.LogWarning($"An IO error occurred while converting binary telemetry to CSV: {e.Message}");
        }
    }

    private void WriteEventsToFile()
    {
        using (StreamWriter writer = new StreamWriter(_eventLogPath, false))
        {
            // Write CSV header
            writer.WriteLine("Time,PositionX,PositionY,PositionZ,Event,Details");

            foreach (var record in _eventLogCache)
            {
                writer.WriteLine($"{record.Time:F2},{record.PositionX:F2},{record.PositionY:F2},{record.PositionZ:F2},{record.EventType},{record.Details}");
            }
        }
    }

    private void RegisterSimulationStarted()
    {
        InitializeLogFiles();
        _monitorRoutine = StartCoroutine(MonitorRoutine());
    }

    private void RegisterSimulationEnded()
    {
        StopCoroutine(_monitorRoutine);
        CloseLogFiles();
        WriteEventsToFile();
        StartCoroutine(ConvertBinaryTelemetryToCsvCoroutine(_telemetryBinPath, Path.ChangeExtension(_telemetryBinPath, ".csv")));
    }

    private IEnumerator ConvertBinaryTelemetryToCsvCoroutine(string binaryFilePath, string csvFilePath)
    {
        yield return null; // Wait for the next frame to ensure RecordTelemetry() has finished
        ConvertBinaryTelemetryToCsv(binaryFilePath, csvFilePath);
    }

    public void RegisterNewThreat(Threat threat) {
        RegisterNewAgent(threat, "NEW_THREAT");
    }

    public void RegisterNewInterceptor(Interceptor interceptor) {
        RegisterNewAgent(interceptor, "NEW_INTERCEPTOR");
        interceptor.OnInterceptMiss += RegisterInterceptorMiss;
        interceptor.OnInterceptHit += RegisterInterceptorHit;
    }

    private void RegisterNewAgent(Agent agent, string eventType)
    {
        float time = (float)SimManager.Instance.GetElapsedSimulationTime();
        Vector3 pos = agent.transform.position;
        var record = new EventRecord
        {
            Time = time,
            PositionX = pos.x,
            PositionY = pos.y,
            PositionZ = pos.z,
            EventType = eventType,
            Details = agent.name
        };
        _eventLogCache.Add(record);
    }

    public void RegisterInterceptorHit(Interceptor interceptor, Threat threat) {
        RegisterInterceptEvent(interceptor, threat, true);
    }

    public void RegisterInterceptorMiss(Interceptor interceptor, Threat threat) {
        RegisterInterceptEvent(interceptor, threat, false);
    }

    public void RegisterInterceptEvent(Interceptor interceptor, Threat threat, bool hit)
    {
        float time = (float)SimManager.Instance.GetElapsedSimulationTime();
        Vector3 pos = interceptor.transform.position;
        string eventType = hit ? "HIT" : "MISS";
        var record = new EventRecord
        {
            Time = time,
            PositionX = pos.x,
            PositionY = pos.y,
            PositionZ = pos.z,
            EventType = eventType,
            Details = $"{interceptor.name} and {threat.name}"
        };
        _eventLogCache.Add(record);
    }

    private void OnDestroy()
    {
        CloseLogFiles();
    }
}