using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using AutoTrackR2.LogEventHandlers;

namespace AutoTrackR2;


// Represents a single log entry
// This is the object that will be passed to each handler, mostly for convenience
public class LogEntry
{
    public DateTime Timestamp { get; set; }
    public required string? Message { get; set; }

}

enum GameProcessState
{
    NotRunning,
    Running,
    Unknown
}

public class LogHandler
{
    private string _logPath;
    private FileStream? _fileStream;
    private StreamReader? _reader;
    private Thread? _monitorThread;
    private CancellationTokenSource? _cancellationTokenSource;
    private GameProcessState _gameProcessState = GameProcessState.NotRunning;
    private bool _isMonitoring = false;

    public bool IsMonitoring => _isMonitoring;

    // Handlers that should be run on every log entry
    // Overlap with _startupEventHandlers is fine
    private readonly List<ILogEventHandler> _eventHandlers = [
        new LoginEvent(),
        new InstancedInteriorEvent(),
        new InArenaCommanderEvent(),
        new InPersistentUniverseEvent(),
        new GameVersionEvent(),
        new JumpDriveStateChangedEvent(),
        new RequestJumpFailedEvent()
    ];

    public LogHandler(string? logPath)
    {
        if (string.IsNullOrEmpty(logPath))
        {
            throw new ArgumentNullException(nameof(logPath), "Log path cannot be null or empty");
        }
        _logPath = logPath;
    }

    public void Initialize()
    {
        if (!File.Exists(_logPath))
        {
            throw new FileNotFoundException("Log file not found", _logPath);
        }

        _fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        _reader = new StreamReader(_fileStream);

        while (_reader.ReadLine() is { } line)
        {
            HandleLogEntry(line);
        }

        // Ensures that any deaths already in log aren't sent to the APIs until the monitor thread is running
        _eventHandlers.Add(new ActorDeathEvent());

        StartMonitoring();
    }

    public void StartMonitoring()
    {
        if (_isMonitoring) return;

        _cancellationTokenSource = new CancellationTokenSource();
        _monitorThread = new Thread(() => MonitorLog(_cancellationTokenSource.Token));
        _monitorThread.Start();
        _isMonitoring = true;
    }

    public void StopMonitoring()
    {
        if (!_isMonitoring) return;

        _cancellationTokenSource?.Cancel();
        _monitorThread?.Join();
        _reader?.Close();
        _fileStream?.Close();
        _isMonitoring = false;
    }

    // Parse a single line of the log file and run matching handlers
    private void HandleLogEntry(string line)
    {
        // Console.WriteLine(line);
        foreach (var handler in _eventHandlers)
        {
            var match = handler.Pattern.Match(line);
            if (!match.Success) continue;

            var entry = new LogEntry
            {
                Timestamp = DateTime.Now,
                Message = line
            };
            handler.Handle(entry);
            break;
        }
    }

    private void MonitorLog(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            if (_reader == null || _fileStream == null)
            {
                break;
            }

            CheckGameProcessState();

            List<string> lines = new List<string>();
            while (_reader.ReadLine() is { } line)
            {
                lines.Add(line);
            }

            foreach (var line in lines)
            {
                // start new thread to handle log entry
                var thread = new Thread(() => HandleLogEntry(line));
                thread.Start();
                // Console.WriteLine(line);
            }

            {
                // Wait for new lines to be written to the log file
                Thread.Sleep(1000);
            }
        }
        Console.WriteLine("Monitor thread stopped");
    }

    private void CheckGameProcessState()
    {
        // Check if the game process is running by window name
        var process = Process.GetProcesses().FirstOrDefault(p => p.MainWindowTitle == "Star Citizen");

        GameProcessState newGameProcessState = process != null ? GameProcessState.Running : GameProcessState.NotRunning;

        if (newGameProcessState == GameProcessState.Running && _gameProcessState == GameProcessState.NotRunning)
        {
            // Game process went from NotRunning to Running, so reload the Game.log file
            Console.WriteLine("Game process started, reloading log file");

            _reader?.Close();
            _fileStream?.Close();

            _fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            _reader = new StreamReader(_fileStream);
        }

        _gameProcessState = newGameProcessState;
    }
}