using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using AxCopilot.Services;

namespace AxCopilot.Core;

/// <summary>
/// 글로벌 키보드 훅으로 어떤 앱이 포커스를 가져도 설정된 핫키를 감지합니다.
/// WH_KEYBOARD_LL (Low-Level Keyboard Hook) 사용.
/// </summary>
public class InputListener : IDisposable
{
    private const int WH_KEYBOARD_LL  = 13;
    private const int WM_KEYDOWN      = 0x0100;
    private const int WM_SYSKEYDOWN   = 0x0104;
    private const int WM_KEYUP        = 0x0101;
    private const int WM_SYSKEYUP     = 0x0105;

    // 수정자 키 VK 코드
    private const int VK_SHIFT   = 0x10;
    private const int VK_CONTROL = 0x11;
    private const int VK_MENU    = 0x12; // Alt
    private const int VK_LWIN   = 0x5B;
    private const int VK_RWIN   = 0x5C;

    private IntPtr _hookHandle = IntPtr.Zero;
    private LowLevelKeyboardProc? _proc;
    private int _retryCount = 0;
    private const int MaxRetry = 3;

    // 핫키 발동 후 잔여 KEYUP 이벤트 억제 플래그
    // Alt+X 핫키 → Alt KEYUP이 앱 메뉴바를 활성화하는 문제 방지
    private volatile bool _suppressNextAltUp;
    private volatile bool _suppressNextKeyUp;
    private volatile int  _suppressKeyUpVk;

    // 현재 설정된 핫키 (기본: Alt+Space)
    private HotkeyDefinition _hotkey = new(0x20, false, true, false, false);

    // 글로벌 캡처 단축키 (기본: PrintScreen, 비활성)
    private HotkeyDefinition _captureHotkey;
    private bool _captureHotkeyEnabled;

    // 항목별 전용 핫키 목록
    private readonly List<(HotkeyDefinition Def, string Target, string Type)> _customHotkeys = new();

    public event EventHandler? HotkeyTriggered;
    public event EventHandler? CaptureHotkeyTriggered;

    /// <summary>전용 핫키 발동 시 발생합니다. EventArgs에 Target과 Type이 포함됩니다.</summary>
    public event EventHandler<CustomHotkeyEventArgs>? CustomHotkeyTriggered;

    public event EventHandler? HookFailed;

    /// <summary>
    /// 핫키 녹화 중일 때 true로 설정하면 핫키 이벤트를 발생시키지 않습니다.
    /// </summary>
    public bool SuspendHotkey { get; set; }

    /// <summary>
    /// 스니펫 확장기가 설정하는 키 필터.
    /// true를 반환하면 해당 키 이벤트를 소비(다른 앱으로 전달 차단)합니다.
    /// 훅 콜백 스레드에서 실행되므로 빠르고 스레드 안전하게 구현해야 합니다.
    /// </summary>
    public Func<int, bool>? KeyFilter { get; set; }

    /// <summary>
    /// 설정에서 읽은 핫키 문자열로 핫키를 업데이트합니다. ("Alt+Space", "Ctrl+K" 등)
    /// </summary>
    public void UpdateHotkey(string hotkeyStr)
    {
        if (HotkeyParser.TryParse(hotkeyStr, out var def))
        {
            _hotkey = def;
            LogService.Info($"핫키 변경: {hotkeyStr}");
        }
        else
        {
            LogService.Warn($"핫키 파싱 실패: '{hotkeyStr}' — 기존 핫키 유지");
        }
    }

    /// <summary>
    /// 항목별 전용 핫키 목록을 업데이트합니다.
    /// </summary>
    public void UpdateCustomHotkeys(IEnumerable<Models.HotkeyAssignment> assignments)
    {
        lock (_customHotkeys)
        {
            _customHotkeys.Clear();
            foreach (var a in assignments)
            {
                if (HotkeyParser.TryParse(a.Hotkey, out var def))
                    _customHotkeys.Add((def, a.Target, a.Type));
                else
                    LogService.Warn($"전용 핫키 파싱 실패: '{a.Hotkey}' (대상: {a.Target})");
            }
            LogService.Info($"전용 핫키 {_customHotkeys.Count}개 등록");
        }
    }

    /// <summary>
    /// 글로벌 캡처 단축키를 설정합니다.
    /// </summary>
    public void UpdateCaptureHotkey(string hotkeyStr, bool enabled)
    {
        _captureHotkeyEnabled = enabled;
        if (enabled && HotkeyParser.TryParse(hotkeyStr, out var def))
        {
            _captureHotkey = def;
            LogService.Info($"캡처 단축키 활성화: {hotkeyStr}");
        }
        else if (!enabled)
        {
            LogService.Info("캡처 단축키 비활성화");
        }
    }

    public void Start()
    {
        _proc = HookCallback;
        Register();
    }

    private void Register()
    {
        using var curProcess = Process.GetCurrentProcess();
        using var curModule = curProcess.MainModule!;
        _hookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, _proc!,
            GetModuleHandle(curModule.ModuleName!), 0);

        if (_hookHandle == IntPtr.Zero)
        {
            var err = Marshal.GetLastWin32Error();
            LogService.Error($"Global Hook 등록 실패 (에러 코드: {err})");
            TryRetryRegister();
        }
        else
        {
            _retryCount = 0;
            LogService.Info($"Global Keyboard Hook 등록 완료 ({HotkeyParser.Format(_hotkey)})");
        }
    }

    private void TryRetryRegister()
    {
        if (_retryCount < MaxRetry)
        {
            _retryCount++;
            LogService.Warn($"Hook 재등록 시도 {_retryCount}/{MaxRetry}");
            Task.Delay(1000).ContinueWith(_ => Register());
        }
        else
        {
            LogService.Error("Hook 재등록 최대 횟수 초과");
            HookFailed?.Invoke(this, EventArgs.Empty);
        }
    }

    /// <summary>
    /// 현재 포그라운드 창이 핫키·스니펫 확장을 억제해야 하는 시스템 대화상자인지 확인합니다.
    /// Windows 공통 대화상자(#32770): 파일 열기/저장, 브라우저 파일 업로드 등.
    /// </summary>
    private static bool IsSuppressedForegroundWindow()
    {
        var hwnd = GetForegroundWindow();
        if (hwnd == IntPtr.Zero) return false;
        var sb = new StringBuilder(64);
        GetClassName(hwnd, sb, 64);
        var cls = sb.ToString();
        // #32770 = Windows 공통 대화상자 (파일 열기/저장/선택)
        // SunAwtDialog = Java Swing 파일 대화상자 (일부 앱)
        return cls == "#32770" || cls == "SunAwtDialog";
    }

    private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode < 0)
            return CallNextHookEx(_hookHandle, nCode, wParam, lParam);

        var vkCode = Marshal.ReadInt32(lParam);

        // ─── KEYUP: 핫키 발동 후 잔여 이벤트 억제 ─────────────────────────────
        // Alt+X 핫키를 누른 직후 Alt KEYUP이 일부 앱의 메뉴바를 활성화하는 문제를 방지.
        // 메인 키 KEYUP도 억제하여 앱이 "키를 완전히 처리"한 것으로 착각하지 않도록 함.
        if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)
        {
            if (_suppressNextAltUp && vkCode == VK_MENU)
            {
                _suppressNextAltUp = false;
                return (IntPtr)1; // Alt KEYUP 억제 → 메뉴바 활성화 차단
            }
            if (_suppressNextKeyUp && vkCode == _suppressKeyUpVk)
            {
                _suppressNextKeyUp = false;
                return (IntPtr)1; // 메인 키 KEYUP 억제
            }
            return CallNextHookEx(_hookHandle, nCode, wParam, lParam);
        }

        // ─── 이하 KEYDOWN / SYSKEYDOWN 처리 ──────────────────────────────────
        if (wParam != WM_KEYDOWN && wParam != WM_SYSKEYDOWN)
            return CallNextHookEx(_hookHandle, nCode, wParam, lParam);

        // ─── 시스템 파일 대화상자에서는 핫키·스니펫 전부 비활성 ──────────────
        if (IsSuppressedForegroundWindow())
            return CallNextHookEx(_hookHandle, nCode, wParam, lParam);

        // ─── 핫키 감지 ──────────────────────────────────────────────────────
        if (!SuspendHotkey && vkCode == _hotkey.VkCode)
        {
            bool ctrlOk  = !_hotkey.Ctrl  || (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
            bool altOk   = !_hotkey.Alt   || (GetAsyncKeyState(VK_MENU)    & 0x8000) != 0;
            bool shiftOk = !_hotkey.Shift || (GetAsyncKeyState(VK_SHIFT)   & 0x8000) != 0;
            bool winOk   = !_hotkey.Win   || (GetAsyncKeyState(VK_LWIN)    & 0x8000) != 0
                                          || (GetAsyncKeyState(VK_RWIN)    & 0x8000) != 0;

            if (ctrlOk && altOk && shiftOk && winOk)
            {
                HotkeyTriggered?.Invoke(this, EventArgs.Empty);
                // 이후 KEYUP 억제 설정
                _suppressNextKeyUp = true;
                _suppressKeyUpVk   = vkCode;
                if (_hotkey.Alt) _suppressNextAltUp = true;
                return (IntPtr)1;
            }
        }

        // ─── 글로벌 캡처 단축키 감지 ─────────────────────────────────────────
        if (!SuspendHotkey && _captureHotkeyEnabled && vkCode == _captureHotkey.VkCode)
        {
            bool ctrlOk  = !_captureHotkey.Ctrl  || (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
            bool altOk   = !_captureHotkey.Alt   || (GetAsyncKeyState(VK_MENU)    & 0x8000) != 0;
            bool shiftOk = !_captureHotkey.Shift || (GetAsyncKeyState(VK_SHIFT)   & 0x8000) != 0;
            bool winOk   = !_captureHotkey.Win   || (GetAsyncKeyState(VK_LWIN)    & 0x8000) != 0
                                                  || (GetAsyncKeyState(VK_RWIN)    & 0x8000) != 0;

            if (ctrlOk && altOk && shiftOk && winOk)
            {
                CaptureHotkeyTriggered?.Invoke(this, EventArgs.Empty);
                _suppressNextKeyUp = true;
                _suppressKeyUpVk   = vkCode;
                if (_captureHotkey.Alt) _suppressNextAltUp = true;
                return (IntPtr)1;
            }
        }

        // ─── 항목별 전용 핫키 감지 ───────────────────────────────────────────
        if (!SuspendHotkey && _customHotkeys.Count > 0)
        {
            List<(HotkeyDefinition Def, string Target, string Type)> snapshot;
            lock (_customHotkeys) { snapshot = new List<(HotkeyDefinition, string, string)>(_customHotkeys); }

            foreach (var (def, target, type) in snapshot)
            {
                if (vkCode != def.VkCode) continue;

                bool ctrlOk  = !def.Ctrl  || (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0;
                bool altOk   = !def.Alt   || (GetAsyncKeyState(VK_MENU)    & 0x8000) != 0;
                bool shiftOk = !def.Shift || (GetAsyncKeyState(VK_SHIFT)   & 0x8000) != 0;
                bool winOk   = !def.Win   || (GetAsyncKeyState(VK_LWIN)    & 0x8000) != 0
                                          || (GetAsyncKeyState(VK_RWIN)    & 0x8000) != 0;

                if (ctrlOk && altOk && shiftOk && winOk)
                {
                    CustomHotkeyTriggered?.Invoke(this, new CustomHotkeyEventArgs(target, type));
                    _suppressNextKeyUp = true;
                    _suppressKeyUpVk   = vkCode;
                    if (def.Alt) _suppressNextAltUp = true;
                    return (IntPtr)1;
                }
            }
        }

        // ─── 스니펫 키 필터 ─────────────────────────────────────────────────
        if (KeyFilter?.Invoke(vkCode) == true)
            return (IntPtr)1;

        return CallNextHookEx(_hookHandle, nCode, wParam, lParam);
    }

    public void Dispose()
    {
        if (_hookHandle != IntPtr.Zero)
        {
            UnhookWindowsHookEx(_hookHandle);
            _hookHandle = IntPtr.Zero;
        }
    }

    // ─── P/Invoke ────────────────────────────────────────────────────────────

    private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn,
        IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport("user32.dll")]
    private static extern short GetAsyncKeyState(int vKey);

    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
}

/// <summary>항목별 전용 핫키 발동 이벤트 인수.</summary>
public class CustomHotkeyEventArgs : EventArgs
{
    public string Target { get; }
    public string Type   { get; }

    public CustomHotkeyEventArgs(string target, string type)
    {
        Target = target;
        Type   = type;
    }
}
