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

namespace AxCopilot.Core;

/// <summary>
/// Windows 창의 HWND, Rect, 프로세스 경로를 수집하고 복원합니다 (The Shifter).
/// </summary>
public class ContextManager
{
    private readonly SettingsService _settings;

    public ContextManager(SettingsService settings)
    {
        _settings = settings;

        // 모니터 구성 변경 감지는 MainWindow의 WndProc에서 WM_DISPLAYCHANGE를 통해 처리
    }

    // ─── Snapshot ────────────────────────────────────────────────────────────

    /// <summary>
    /// 현재 화면에 열린 모든 업무용 창을 캡처하여 프로필로 저장합니다.
    /// </summary>
    public WorkspaceProfile CaptureProfile(string name)
    {
        var snapshots = new List<WindowSnapshot>();
        var monitorMap = BuildMonitorMap();

        EnumWindows((hWnd, _) =>
        {
            if (!IsWindowVisible(hWnd)) return true;
            if (IsIconic(hWnd)) return true; // 최소화된 창 제외 여부는 설정으로 조정 가능

            var title = GetWindowTitle(hWnd);
            if (string.IsNullOrWhiteSpace(title)) return true;

            // 작업표시줄, 바탕화면 등 시스템 창 제외
            if (IsSystemWindow(hWnd)) return true;

            var exePath = GetProcessPath(hWnd);
            if (string.IsNullOrEmpty(exePath)) return true;

            GetWindowRect(hWnd, out RECT rect);
            GetWindowPlacement(hWnd, out WINDOWPLACEMENT placement);

            var showCmd = placement.showCmd switch
            {
                1 => "Normal",
                2 => "Minimized",
                3 => "Maximized",
                _ => "Normal"
            };

            int monitorIndex = GetMonitorIndex(hWnd, monitorMap);

            snapshots.Add(new WindowSnapshot
            {
                Exe = exePath,
                Title = title,
                Rect = new WindowRect
                {
                    X = rect.Left,
                    Y = rect.Top,
                    Width = rect.Right - rect.Left,
                    Height = rect.Bottom - rect.Top
                },
                ShowCmd = showCmd,
                Monitor = monitorIndex
            });

            return true;
        }, IntPtr.Zero);

        var profile = new WorkspaceProfile
        {
            Name = name,
            Windows = snapshots,
            CreatedAt = DateTime.Now
        };

        // settings.json에 저장
        var existing = _settings.Settings.Profiles.FirstOrDefault(p =>
            p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
        if (existing != null)
            _settings.Settings.Profiles.Remove(existing);
        _settings.Settings.Profiles.Add(profile);
        _settings.Save();

        LogService.Info($"프로필 '{name}' 저장 완료: {snapshots.Count}개 창");
        return profile;
    }

    // ─── Restore ─────────────────────────────────────────────────────────────

    /// <summary>
    /// 저장된 프로필을 복원합니다.
    /// </summary>
    public async Task<RestoreResult> RestoreProfileAsync(string name, CancellationToken ct = default)
    {
        var profile = _settings.Settings.Profiles
            .FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));

        if (profile == null)
            return new RestoreResult(false, $"프로필 '{name}'을 찾을 수 없습니다.");

        var results = new List<string>();
        var monitorCount = GetMonitorCount();

        foreach (var snapshot in profile.Windows)
        {
            ct.ThrowIfCancellationRequested();

            // 1. 실행 중인 창 찾기
            var hWnd = FindMatchingWindow(snapshot);

            // 2. 창이 없으면 EXE 실행 후 대기
            if (hWnd == IntPtr.Zero && File.Exists(snapshot.Exe))
            {
                try
                {
                    Process.Start(new ProcessStartInfo(snapshot.Exe) { UseShellExecute = true });
                    hWnd = await WaitForWindowAsync(snapshot.Exe, TimeSpan.FromSeconds(3), ct);
                }
                catch (Exception ex)
                {
                    results.Add($"⚠ {snapshot.Title}: 실행 실패 ({ex.Message})");
                    LogService.Warn($"앱 실행 실패: {snapshot.Exe} - {ex.Message}");
                    continue;
                }
            }

            if (hWnd == IntPtr.Zero)
            {
                results.Add($"⏭ {snapshot.Title}: 창 없음, 건너뜀");
                continue;
            }

            // 3. 모니터 불일치 처리
            if (snapshot.Monitor >= monitorCount)
            {
                var policy = _settings.Settings.MonitorMismatch;
                if (policy == "skip")
                {
                    results.Add($"⏭ {snapshot.Title}: 모니터 불일치, 건너뜀");
                    continue;
                }
                // "fit" 또는 "warn" → 첫 번째 모니터에 배치
            }

            // 4. 창 위치/크기 복원
            try
            {
                ShowWindow(hWnd, snapshot.ShowCmd switch
                {
                    "Maximized" => 3,
                    "Minimized" => 2,
                    _ => 9 // SW_RESTORE
                });

                if (snapshot.ShowCmd == "Normal")
                {
                    SetWindowPos(hWnd, IntPtr.Zero,
                        snapshot.Rect.X, snapshot.Rect.Y,
                        snapshot.Rect.Width, snapshot.Rect.Height,
                        SWP_NOZORDER | SWP_NOACTIVATE);
                }

                results.Add($"✓ {snapshot.Title}: 복원 완료");
            }
            catch (Exception ex)
            {
                results.Add($"⚠ {snapshot.Title}: 복원 실패 ({ex.Message})");
                LogService.Warn($"창 복원 실패 (권한 문제 가능): {snapshot.Exe}");
            }
        }

        LogService.Info($"프로필 '{name}' 복원: {string.Join(", ", results)}");
        return new RestoreResult(true, string.Join("\n", results));
    }

    // ─── Profile Management ──────────────────────────────────────────────────

    public bool DeleteProfile(string name)
    {
        var profile = _settings.Settings.Profiles
            .FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
        if (profile == null) return false;
        _settings.Settings.Profiles.Remove(profile);
        _settings.Save();
        return true;
    }

    public bool RenameProfile(string oldName, string newName)
    {
        var profile = _settings.Settings.Profiles
            .FirstOrDefault(p => p.Name.Equals(oldName, StringComparison.OrdinalIgnoreCase));
        if (profile == null) return false;
        profile.Name = newName;
        _settings.Save();
        return true;
    }

    // ─── Helpers ─────────────────────────────────────────────────────────────

    private static IntPtr FindMatchingWindow(WindowSnapshot snapshot)
    {
        IntPtr found = IntPtr.Zero;
        EnumWindows((hWnd, _) =>
        {
            var path = GetProcessPath(hWnd);
            if (string.Equals(path, snapshot.Exe, StringComparison.OrdinalIgnoreCase))
            {
                found = hWnd;
                return false; // 첫 번째 매칭 창에서 중단
            }
            return true;
        }, IntPtr.Zero);
        return found;
    }

    private static async Task<IntPtr> WaitForWindowAsync(string exePath, TimeSpan timeout, CancellationToken ct)
    {
        var deadline = DateTime.UtcNow + timeout;
        while (DateTime.UtcNow < deadline)
        {
            ct.ThrowIfCancellationRequested();
            var hWnd = FindMatchingWindow(new WindowSnapshot { Exe = exePath });
            if (hWnd != IntPtr.Zero) return hWnd;
            await Task.Delay(200, ct);
        }
        return IntPtr.Zero;
    }

    private static string GetWindowTitle(IntPtr hWnd)
    {
        var sb = new StringBuilder(256);
        GetWindowText(hWnd, sb, sb.Capacity);
        return sb.ToString();
    }

    private static string GetProcessPath(IntPtr hWnd)
    {
        try
        {
            GetWindowThreadProcessId(hWnd, out uint pid);
            if (pid == 0) return "";
            var proc = Process.GetProcessById((int)pid);
            return proc.MainModule?.FileName ?? "";
        }
        catch (Exception) { return ""; }
    }

    private static bool IsSystemWindow(IntPtr hWnd)
    {
        var cls = new StringBuilder(256);
        GetClassName(hWnd, cls, cls.Capacity);
        var name = cls.ToString();
        return name is "Shell_TrayWnd" or "Progman" or "WorkerW" or "DV2ControlHost";
    }

    private static Dictionary<IntPtr, int> BuildMonitorMap()
    {
        var monitors = new List<IntPtr>();
        EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
            (IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprc, IntPtr dwData) =>
            {
                monitors.Add(hMonitor);
                return true;
            }, IntPtr.Zero);

        return monitors
            .Select((hm, idx) => (hm, idx))
            .ToDictionary(t => t.hm, t => t.idx);
    }

    private static int GetMonitorIndex(IntPtr hWnd, Dictionary<IntPtr, int> map)
    {
        var monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
        return map.TryGetValue(monitor, out int idx) ? idx : 0;
    }

    private static int GetMonitorCount()
    {
        int count = 0;
        EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprc, IntPtr dwData) => { count++; return true; }, IntPtr.Zero);
        return count;
    }

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

    private const uint SWP_NOZORDER = 0x0004;
    private const uint SWP_NOACTIVATE = 0x0010;
    private const uint MONITOR_DEFAULTTONEAREST = 0x00000002;

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT { public int Left, Top, Right, Bottom; }

    [StructLayout(LayoutKind.Sequential)]
    private struct WINDOWPLACEMENT
    {
        public uint length, flags;
        public uint showCmd;
        public POINT ptMinPosition, ptMaxPosition;
        public RECT rcNormalPosition;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT { public int x, y; }

    private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
    private delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);

    [DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
    [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd);
    [DllImport("user32.dll")] private static extern bool IsIconic(IntPtr hWnd);
    [DllImport("user32.dll")] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
    [DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
    [DllImport("user32.dll")] private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
    [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
    [DllImport("user32.dll")] private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
    [DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
    [DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
    [DllImport("user32.dll")] private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
    [DllImport("user32.dll")] private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumProc lpfnEnum, IntPtr dwData);
}

public record RestoreResult(bool Success, string Message);
