using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using AxCopilot.Models;

namespace AxCopilot.Services;

/// <summary>
/// 클립보드 변경을 감지하여 히스토리를 관리합니다.
/// WM_CLIPBOARDUPDATE 메시지를 수신하기 위해 숨겨진 메시지 창을 생성합니다.
/// </summary>
public partial class ClipboardHistoryService : IDisposable
{
    private const int WM_CLIPBOARDUPDATE = 0x031D;

    private static readonly string HistoryPath = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        "AxCopilot", "clipboard_history.dat");

    // 구버전 평문 파일 경로 (마이그레이션용)
    private static readonly string LegacyPath = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        "AxCopilot", "clipboard_history.json");

    /// <summary>원본 이미지 캐시 폴더 (%APPDATA%\AxCopilot\clipboard_images\)</summary>
    private static readonly string ImageCachePath = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        "AxCopilot", "clipboard_images");

    private const long MaxCacheSizeBytes = 500 * 1024 * 1024; // 500MB
    private const int  MaxCacheAgeDays   = 30;

    private static readonly JsonSerializerOptions JsonOpts = new()
    {
        WriteIndented = false,
        PropertyNameCaseInsensitive = true
    };

    private readonly SettingsService _settings;
    private HwndSource?              _msgSource;
    private readonly object          _lock = new();
    private volatile bool            _ignoreNext; // 자체 클립보드 조작 시 히스토리 추가 방지
    private bool                     _disposed;

    private readonly List<ClipboardEntry> _history = new();

    public IReadOnlyList<ClipboardEntry> History
    {
        get { lock (_lock) return _history.ToList(); }
    }

    public event EventHandler? HistoryChanged;

    public ClipboardHistoryService(SettingsService settings)
    {
        _settings = settings;
    }

    /// <summary>메시지 창을 생성하고 클립보드 알림을 등록합니다.</summary>
    public void Initialize()
    {
        if (_disposed) return;
        if (_msgSource != null) return;  // 이미 초기화됨
        if (!_settings.Settings.ClipboardHistory.Enabled) return;

        // 저장된 히스토리 복원 (텍스트 항목만)
        LoadHistory();

        Application.Current.Dispatcher.Invoke(() =>
        {
            var sourceParams = new HwndSourceParameters("axClipboardMonitor")
            {
                Width           = 0,
                Height          = 0,
                ParentWindow    = new IntPtr(-3),  // HWND_MESSAGE — 화면에 표시 안 됨
                WindowStyle     = 0,
                ExtendedWindowStyle = 0
            };
            _msgSource = new HwndSource(sourceParams);
            _msgSource.AddHook(WndProc);
            AddClipboardFormatListener(_msgSource.Handle);
            LogService.Info("클립보드 히스토리 서비스 시작");
        });
    }

    /// <summary>자체 클립보드 조작(히스토리 붙여넣기) 시 히스토리가 중복 추가되지 않도록 플래그 설정.</summary>
    public void SuppressNextCapture() => _ignoreNext = true;

    /// <summary>항목을 사용했을 때 CopiedAt을 현재 시각으로 갱신하고 목록 맨 위로 이동합니다.</summary>
    public void PromoteEntry(ClipboardEntry entry)
    {
        lock (_lock)
        {
            _history.Remove(entry);
            var updated = new ClipboardEntry(entry.Text, DateTime.Now)
            {
                Image = entry.Image,
                OriginalImagePath = entry.OriginalImagePath,
                IsPinned = entry.IsPinned,
                Category = entry.Category,
                OcrText = entry.OcrText,
            };
            _history.Insert(0, updated);
        }
        HistoryChanged?.Invoke(this, EventArgs.Empty);
        _ = SaveHistoryAsync();
    }

    public void ClearHistory()
    {
        lock (_lock) _history.Clear();
        HistoryChanged?.Invoke(this, EventArgs.Empty);
        _ = SaveHistoryAsync();
    }

    /// <summary>항목의 핀 고정을 토글합니다.</summary>
    public void TogglePin(ClipboardEntry entry)
    {
        lock (_lock)
        {
            if (!entry.IsPinned)
            {
                // 최대 핀 개수 체크
                var maxPins = _settings.Settings.Launcher.MaxPinnedClipboardItems;
                var currentPins = _history.Count(e => e.IsPinned);
                if (currentPins >= maxPins) return; // 최대 도달 시 무시
            }
            entry.IsPinned = !entry.IsPinned;
        }
        HistoryChanged?.Invoke(this, EventArgs.Empty);
        _ = SaveHistoryAsync();
    }

    /// <summary>텍스트 내용에서 카테고리를 자동 감지합니다.</summary>
    private static string DetectCategory(string text)
    {
        var trimmed = text.Trim();
        if (Uri.TryCreate(trimmed, UriKind.Absolute, out var uri) &&
            (uri.Scheme == "http" || uri.Scheme == "https"))
            return "URL";
        if (trimmed.StartsWith("\\\\") || (trimmed.Length >= 3 && trimmed[1] == ':' && (trimmed[2] == '\\' || trimmed[2] == '/')))
            return "경로";
        if (trimmed.Contains('{') && trimmed.Contains('}') || trimmed.Contains("function ") ||
            trimmed.Contains("class ") || trimmed.Contains("public ") || trimmed.Contains("private ") ||
            trimmed.Contains("def ") || trimmed.Contains("import ") || trimmed.Contains("using "))
            return "코드";
        return "일반";
    }

    /// <summary>
    /// 클립보드 감지가 동작하지 않을 때 강제 재시작합니다.
    /// Dispose 후 호출하면 내부 상태를 초기화하고 클립보드 모니터링을 재개합니다.
    /// </summary>
    public void Reinitialize()
    {
        _disposed = false;
        _msgSource = null;
        Initialize();
        LogService.Info("클립보드 히스토리 서비스 재초기화 완료");
    }

    public void Dispose()
    {
        if (_disposed) return;
        _disposed = true;
        if (_msgSource != null)
        {
            RemoveClipboardFormatListener(_msgSource.Handle);
            _msgSource.Dispose();
            _msgSource = null;
        }
        // 종료 시 히스토리 저장 (동기)
        SaveHistorySync();
    }

    // ─── 히스토리 영속성 (DPAPI 암호화) ─────────────────────────────────────
    // Windows DPAPI(DataProtectionScope.CurrentUser)를 사용하여
    // 현재 Windows 사용자 계정에서만 복호화 가능하도록 합니다.
    // 이 앱 외부에서는 파일 내용을 읽을 수 없습니다.

    private void LoadHistory()
    {
        try
        {
            // 구버전 평문 파일 → 암호화 파일 마이그레이션
            if (!File.Exists(HistoryPath) && File.Exists(LegacyPath))
            {
                MigrateLegacyFile();
            }

            if (!File.Exists(HistoryPath)) return;

            var encrypted = File.ReadAllBytes(HistoryPath);
            var plain = ProtectedData.Unprotect(encrypted, null, DataProtectionScope.CurrentUser);
            var json = Encoding.UTF8.GetString(plain);
            var saved = JsonSerializer.Deserialize<List<SavedClipEntry>>(json, JsonOpts);
            if (saved == null) return;

            int max = _settings.Settings.ClipboardHistory.MaxItems;
            lock (_lock)
            {
                foreach (var s in saved.Take(max))
                {
                    if (!string.IsNullOrEmpty(s.ImageBase64))
                    {
                        var img = Base64ToImage(s.ImageBase64);
                        if (img != null)
                        {
                            _history.Add(new ClipboardEntry("", s.CopiedAt)
                            {
                                Image = img,
                                OriginalImagePath = s.OriginalImagePath,
                                IsPinned = s.IsPinned,
                                Category = s.Category,
                                OcrText = s.OcrText,
                            });
                            continue;
                        }
                    }
                    _history.Add(new ClipboardEntry(s.Text, s.CopiedAt) { IsPinned = s.IsPinned, Category = s.Category });
                }
            }
            // 시작 시 이미지 캐시 정리
            Task.Run(CleanupImageCache);
            LogService.Info($"클립보드 히스토리 {_history.Count}개 복원 (암호화)");
        }
        catch (Exception ex)
        {
            LogService.Warn($"클립보드 히스토리 로드 실패: {ex.Message}");
        }
    }

    private void MigrateLegacyFile()
    {
        try
        {
            var json = File.ReadAllText(LegacyPath);
            var plain = Encoding.UTF8.GetBytes(json);
            var encrypted = ProtectedData.Protect(plain, null, DataProtectionScope.CurrentUser);
            Directory.CreateDirectory(Path.GetDirectoryName(HistoryPath)!);
            File.WriteAllBytes(HistoryPath, encrypted);
            File.Delete(LegacyPath); // 평문 파일 삭제
            LogService.Info("클립보드 히스토리 평문→암호화 마이그레이션 완료");
        }
        catch (Exception ex)
        {
            LogService.Warn($"클립보드 히스토리 마이그레이션 실패: {ex.Message}");
        }
    }

    private async Task SaveHistoryAsync()
    {
        try
        {
            var snapshot = BuildSnapshot();
            var json = JsonSerializer.Serialize(snapshot, JsonOpts);
            var plain = Encoding.UTF8.GetBytes(json);
            var encrypted = ProtectedData.Protect(plain, null, DataProtectionScope.CurrentUser);
            Directory.CreateDirectory(Path.GetDirectoryName(HistoryPath)!);
            await File.WriteAllBytesAsync(HistoryPath, encrypted).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            LogService.Warn($"클립보드 히스토리 저장 실패: {ex.Message}");
        }
    }

    private void SaveHistorySync()
    {
        try
        {
            var snapshot = BuildSnapshot();
            var json = JsonSerializer.Serialize(snapshot, JsonOpts);
            var plain = Encoding.UTF8.GetBytes(json);
            var encrypted = ProtectedData.Protect(plain, null, DataProtectionScope.CurrentUser);
            Directory.CreateDirectory(Path.GetDirectoryName(HistoryPath)!);
            File.WriteAllBytes(HistoryPath, encrypted);
        }
        catch (Exception) { /* 종료 시 실패 무시 */ }
    }

    private List<SavedClipEntry> BuildSnapshot()
    {
        lock (_lock)
        {
            return _history.Select(e =>
            {
                if (e.IsText)
                    return new SavedClipEntry { Text = e.Text, CopiedAt = e.CopiedAt, IsPinned = e.IsPinned, Category = e.Category };
                var b64 = ImageToBase64(e.Image);
                return new SavedClipEntry
                {
                    CopiedAt = e.CopiedAt,
                    ImageBase64 = b64,
                    OriginalImagePath = e.OriginalImagePath,
                    IsPinned = e.IsPinned,
                    Category = e.Category,
                    OcrText = e.OcrText,
                };
            }).ToList();
        }
    }

    // System.Text.Json 역직렬화를 위해 기본 생성자 + 프로퍼티 형태로 선언
    private class SavedClipEntry
    {
        public string   Text     { get; set; } = "";
        public DateTime CopiedAt { get; set; }
        /// <summary>이미지 썸네일 PNG 바이트 (Base64 인코딩). null이면 텍스트 항목.</summary>
        public string?  ImageBase64 { get; set; }
        /// <summary>원본 이미지 파일 경로 (clipboard_images 폴더).</summary>
        public string?  OriginalImagePath { get; set; }
        public bool     IsPinned { get; set; }
        public string   Category { get; set; } = "일반";
        /// <summary>이미지에서 추출된 OCR 텍스트 (텍스트 항목은 null).</summary>
        public string?  OcrText  { get; set; }
    }

    // ─── 내부 ──────────────────────────────────────────────────────────────

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == WM_CLIPBOARDUPDATE)
        {
            OnClipboardUpdate();
            handled = false;
        }
        return IntPtr.Zero;
    }

    private void OnClipboardUpdate()
    {
        if (_ignoreNext) { _ignoreNext = false; return; }
        if (!_settings.Settings.ClipboardHistory.Enabled) return;

        Application.Current.Dispatcher.Invoke(() =>
        {
            try
            {
                ClipboardEntry? entry = null;

                // ─── 텍스트 ────────────────────────────────────────────────────
                if (Clipboard.ContainsText())
                {
                    var text = Clipboard.GetText();
                    if (string.IsNullOrWhiteSpace(text)) return;
                    if (text.Length > 10_000) return;

                    // 제외 패턴 검사
                    foreach (var pattern in _settings.Settings.ClipboardHistory.ExcludePatterns)
                    {
                        try
                        {
                            if (Regex.IsMatch(text.Trim(), pattern,
                                    RegexOptions.None, TimeSpan.FromMilliseconds(200)))
                                return;
                        }
                        catch (Exception) { /* 잘못된 패턴 무시 */ }
                    }

                    var category = _settings.Settings.Launcher.EnableClipboardAutoCategory
                        ? DetectCategory(text) : "일반";
                    entry = new ClipboardEntry(text, DateTime.Now) { Category = category };
                }
                // ─── 이미지 ────────────────────────────────────────────────────
                else if (Clipboard.ContainsImage())
                {
                    var src = Clipboard.GetImage();
                    if (src == null) return;

                    // 원본 이미지를 캐시 폴더에 PNG로 저장
                    var originalPath = SaveOriginalImage(src);

                    // 표시용 썸네일 (최대 80px 폭)
                    var thumb = CreateThumbnail(src, 80);
                    entry = new ClipboardEntry("", DateTime.Now)
                    {
                        Image = thumb,
                        OriginalImagePath = originalPath,
                    };

                    // Phase L2-4: OCR 텍스트 추출 (백그라운드, 로컬 처리)
                    if (_settings.Settings.ClipboardHistory.EnableOcrSearch && originalPath != null)
                    {
                        var capturedEntry = entry;
                        _ = Task.Run(async () =>
                        {
                            var ocrText = await ExtractOcrTextAsync(originalPath).ConfigureAwait(false);
                            if (!string.IsNullOrEmpty(ocrText))
                            {
                                capturedEntry.OcrText = ocrText;
                                _ = SaveHistoryAsync();
                            }
                        });
                    }
                }

                if (entry == null) return;

                lock (_lock)
                {
                    // 텍스트 중복 제거
                    if (entry.IsText)
                    {
                        if (_history.Count > 0 && _history[0].Text == entry.Text) return;
                        _history.RemoveAll(e => e.IsText && e.Text == entry.Text);
                    }

                    _history.Insert(0, entry);

                    int max = _settings.Settings.ClipboardHistory.MaxItems;
                    while (_history.Count > max)
                    {
                        // 핀 고정 항목은 삭제 보호 — 뒤에서부터 핀 아닌 항목 제거
                        var removeIdx = _history.FindLastIndex(e => !e.IsPinned);
                        if (removeIdx >= 0) _history.RemoveAt(removeIdx);
                        else break; // 전부 핀이면 중단
                    }
                }

                HistoryChanged?.Invoke(this, EventArgs.Empty);
                _ = SaveHistoryAsync();
            }
            catch (Exception ex)
            {
                LogService.Warn($"클립보드 캡처 실패: {ex.Message}");
            }
        });
    }

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

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool AddClipboardFormatListener(IntPtr hwnd);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
}

/// <summary>클립보드 히스토리 단일 항목. 텍스트 또는 이미지 중 하나를 담습니다.</summary>
public record ClipboardEntry(string Text, DateTime CopiedAt)
{
    /// <summary>이미지 항목의 표시용 썸네일 (텍스트 항목은 null)</summary>
    public BitmapSource? Image { get; init; }

    /// <summary>원본 해상도 이미지 파일 경로 (clipboard_images 폴더). null이면 썸네일만 존재.</summary>
    public string? OriginalImagePath { get; init; }

    /// <summary>핀 고정 여부 (핀 항목은 삭제되지 않고 상단에 표시)</summary>
    public bool IsPinned { get; set; }

    /// <summary>카테고리 (URL, 코드, 경로, 일반)</summary>
    public string Category { get; set; } = "일반";

    /// <summary>
    /// Windows OCR API로 추출한 이미지 텍스트 (백그라운드 처리, null이면 미처리 또는 추출 실패).
    /// 이미지 항목의 텍스트 기반 검색에 활용됩니다.
    /// </summary>
    public string? OcrText { get; set; }

    /// <summary>텍스트 항목 여부</summary>
    public bool IsText => Image == null;

    /// <summary>UI 표시용 첫 줄 미리보기 (최대 80자). 이미지의 경우 OCR 텍스트 우선 표시.</summary>
    public string Preview
    {
        get
        {
            if (!IsText)
            {
                // OCR 텍스트가 있으면 첫 줄 미리보기로 표시
                if (!string.IsNullOrWhiteSpace(OcrText))
                {
                    var ocrLine = OcrText.Replace("\r\n", " ").Replace("\n", " ").Replace("\r", " ").Trim();
                    return ocrLine.Length > 72 ? ocrLine[..69] + "…" : ocrLine;
                }
                return "[이미지]";
            }
            var line = Text.Replace("\r\n", "↵ ").Replace("\n", "↵ ").Replace("\r", "↵ ");
            return line.Length > 80 ? line[..77] + "…" : line;
        }
    }

    /// <summary>복사 시각 상대 표시</summary>
    public string RelativeTime
    {
        get
        {
            var diff = DateTime.Now - CopiedAt;
            if (diff.TotalSeconds < 60)  return "방금 전";
            if (diff.TotalMinutes < 60)  return $"{(int)diff.TotalMinutes}분 전";
            if (diff.TotalHours   < 24)  return $"{(int)diff.TotalHours}시간 전";
            return CopiedAt.ToString("MM/dd HH:mm");
        }
    }
}
