using System.IO;
using System.Text.RegularExpressions;
using AxCopilot.Services;

namespace AxCopilot.Core;

/// <summary>
/// L4-5: 고급 검색 필터 파서.
/// "report ext:.pdf size:&gt;1mb modified:week in:Documents" 형식의 쿼리에서
/// 필터 토큰을 분리하고 IndexEntry 매칭 여부를 판별합니다.
///
/// 지원 필터:
///   ext:.pdf          — 확장자 (쉼표 구분 다중 가능: ext:.docx,.xlsx)
///   type:file|folder|app|alias
///   in:경로조각       — 경로에 문자열 포함
///   size:&gt;1mb / size:&lt;500kb / size:=10mb
///   modified:today / week / month / year / &gt;2024-01-01
/// </summary>
public static class SearchFilterParser
{
    // 필터 토큰 감지 정규식: key:value (value는 공백 전까지)
    private static readonly Regex _tokenRx = new(
        @"\b(ext|type|in|size|modified):([\S]+)",
        RegexOptions.IgnoreCase | RegexOptions.Compiled);

    // size 파싱: >, <, = + 숫자 + 단위(b|kb|mb|gb)
    private static readonly Regex _sizeRx = new(
        @"^([><!=]?)(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)$",
        RegexOptions.IgnoreCase | RegexOptions.Compiled);

    // modified 날짜 파싱: >YYYY-MM-DD 또는 <YYYY-MM-DD
    private static readonly Regex _dateRx = new(
        @"^([><]?)(\d{4}-\d{2}-\d{2})$",
        RegexOptions.Compiled);

    /// <summary>
    /// 입력 쿼리에서 필터 토큰을 파싱합니다.
    /// 반환: (필터가 제거된 순수 텍스트 쿼리, 파싱된 필터)
    /// </summary>
    public static (string Query, ParsedFilters Filters) Parse(string input)
    {
        var filters = new ParsedFilters();
        var cleaned = _tokenRx.Replace(input.Trim(), m =>
        {
            ParseToken(m.Groups[1].Value.ToLowerInvariant(),
                       m.Groups[2].Value, filters);
            return " "; // 필터 토큰을 공백으로 교체
        });

        // 연속 공백 제거
        cleaned = Regex.Replace(cleaned, @"\s+", " ").Trim();
        return (cleaned, filters);
    }

    // ─── 토큰별 파싱 ──────────────────────────────────────────────────────

    private static void ParseToken(string key, string value, ParsedFilters f)
    {
        switch (key)
        {
            case "ext":
                f.Extensions = value.Split(',')
                    .Select(e => e.Trim())
                    .Where(e => !string.IsNullOrEmpty(e))
                    .Select(e => e.StartsWith('.') ? e.ToLowerInvariant() : "." + e.ToLowerInvariant())
                    .ToArray();
                break;

            case "type":
                f.Types = value.ToLowerInvariant() switch
                {
                    "file" or "f"          => [IndexEntryType.File],
                    "folder" or "dir" or "d" => [IndexEntryType.Folder],
                    "app" or "a"           => [IndexEntryType.App],
                    "alias"                => [IndexEntryType.Alias],
                    _ => null
                };
                break;

            case "in":
                f.PathFragment = value;
                break;

            case "size":
                ParseSize(value, f);
                break;

            case "modified":
                ParseModified(value, f);
                break;
        }
    }

    private static void ParseSize(string value, ParsedFilters f)
    {
        var m = _sizeRx.Match(value.Trim());
        if (!m.Success) return;

        var op    = m.Groups[1].Value is "" ? ">" : m.Groups[1].Value; // 기본: >
        var num   = double.Parse(m.Groups[2].Value, System.Globalization.CultureInfo.InvariantCulture);
        var unit  = m.Groups[3].Value.ToLowerInvariant();
        var bytes = (long)(num * unit switch
        {
            "kb" => 1024.0,
            "mb" => 1024.0 * 1024,
            "gb" => 1024.0 * 1024 * 1024,
            _    => 1.0  // b
        });

        f.SizeOp    = op[0];
        f.SizeBytes = bytes;
    }

    private static void ParseModified(string value, ParsedFilters f)
    {
        var now = DateTime.Now;
        switch (value.ToLowerInvariant())
        {
            case "today":
                f.ModifiedFrom = now.Date;
                f.ModifiedTo   = now.Date.AddDays(1).AddTicks(-1);
                break;
            case "yesterday":
                f.ModifiedFrom = now.Date.AddDays(-1);
                f.ModifiedTo   = now.Date.AddTicks(-1);
                break;
            case "week":
                f.ModifiedFrom = now.Date.AddDays(-7);
                f.ModifiedTo   = now;
                break;
            case "month":
                f.ModifiedFrom = now.Date.AddDays(-30);
                f.ModifiedTo   = now;
                break;
            case "year":
                f.ModifiedFrom = now.Date.AddDays(-365);
                f.ModifiedTo   = now;
                break;
            default:
                // modified:>2024-01-15 또는 modified:<2024-06-01
                var dm = _dateRx.Match(value.Trim());
                if (dm.Success && DateTime.TryParse(dm.Groups[2].Value, out var dt))
                {
                    if (dm.Groups[1].Value == "<")
                    {
                        f.ModifiedFrom = DateTime.MinValue;
                        f.ModifiedTo   = dt;
                    }
                    else // > 또는 없음
                    {
                        f.ModifiedFrom = dt;
                        f.ModifiedTo   = DateTime.MaxValue;
                    }
                }
                break;
        }
    }

    // ─── 필터 매칭 ────────────────────────────────────────────────────────

    /// <summary>
    /// IndexEntry가 ParsedFilters 조건을 모두 만족하는지 확인합니다.
    /// size/modified 조건은 파일시스템 접근이 필요하므로 마지막에 검사합니다.
    /// </summary>
    public static bool Matches(IndexEntry entry, ParsedFilters filters)
    {
        // 확장자 필터 (빠른 문자열 비교)
        if (filters.Extensions is { Length: > 0 })
        {
            var ext = Path.GetExtension(entry.Path).ToLowerInvariant();
            if (!filters.Extensions.Contains(ext)) return false;
        }

        // 타입 필터
        if (filters.Types != null)
        {
            if (!filters.Types.Contains(entry.Type)) return false;
        }

        // 경로 포함 필터
        if (filters.PathFragment != null)
        {
            if (!entry.Path.Contains(filters.PathFragment, StringComparison.OrdinalIgnoreCase))
                return false;
        }

        // 크기/수정일 필터 — 파일 항목에만 적용, 파일시스템 접근
        if (filters.SizeBytes.HasValue || filters.ModifiedFrom.HasValue)
        {
            // 앱/폴더/Alias는 size 필터 대상 아님
            if (entry.Type != IndexEntryType.File)
                return !filters.SizeBytes.HasValue; // 파일이 아니면 size 필터 통과 불가

            try
            {
                var fi = new FileInfo(Environment.ExpandEnvironmentVariables(entry.Path));
                if (!fi.Exists) return false;

                if (filters.SizeBytes.HasValue)
                {
                    bool ok = filters.SizeOp switch
                    {
                        '>' => fi.Length > filters.SizeBytes.Value,
                        '<' => fi.Length < filters.SizeBytes.Value,
                        '=' => fi.Length == filters.SizeBytes.Value,
                        '!' => fi.Length != filters.SizeBytes.Value,
                        _   => true
                    };
                    if (!ok) return false;
                }

                if (filters.ModifiedFrom.HasValue)
                {
                    var mtime = fi.LastWriteTime;
                    if (mtime < filters.ModifiedFrom.Value || mtime > filters.ModifiedTo!.Value)
                        return false;
                }
            }
            catch
            {
                return false; // 접근 불가 파일 제외
            }
        }

        return true;
    }

    /// <summary>
    /// 활성 필터 목록을 사람이 읽기 쉬운 텍스트로 반환합니다. (UI 힌트용)
    /// </summary>
    public static string Describe(ParsedFilters f)
    {
        var parts = new List<string>();
        if (f.Extensions is { Length: > 0 })
            parts.Add($"ext:{string.Join(",", f.Extensions)}");
        if (f.Types != null)
            parts.Add($"type:{string.Join(",", f.Types.Select(t => t.ToString().ToLower()))}");
        if (f.PathFragment != null)
            parts.Add($"in:{f.PathFragment}");
        if (f.SizeBytes.HasValue)
            parts.Add($"size:{f.SizeOp}{FormatBytes(f.SizeBytes.Value)}");
        if (f.ModifiedFrom.HasValue && f.ModifiedTo.HasValue)
        {
            var span = (DateTime.Now - f.ModifiedFrom.Value).TotalDays;
            parts.Add(span switch
            {
                <= 1   => "modified:today",
                <= 2   => "modified:yesterday",
                <= 8   => "modified:week",
                <= 31  => "modified:month",
                <= 366 => "modified:year",
                _      => $"modified:>{f.ModifiedFrom:yyyy-MM-dd}"
            });
        }
        return string.Join("  ", parts);
    }

    private static string FormatBytes(long b) => b switch
    {
        >= 1024L * 1024 * 1024 => $"{b / (1024.0 * 1024 * 1024):F1}GB",
        >= 1024L * 1024        => $"{b / (1024.0 * 1024):F1}MB",
        >= 1024L               => $"{b / 1024.0:F1}KB",
        _                      => $"{b}B"
    };
}

/// <summary>ParsedFilters: SearchFilterParser.Parse()가 반환하는 파싱된 필터 집합.</summary>
public class ParsedFilters
{
    public string[]?          Extensions   { get; set; }
    public IndexEntryType[]?  Types        { get; set; }
    public string?            PathFragment { get; set; }
    public char               SizeOp       { get; set; } = '>';
    public long?              SizeBytes    { get; set; }
    public DateTime?          ModifiedFrom { get; set; }
    public DateTime?          ModifiedTo   { get; set; }

    /// <summary>하나라도 필터가 설정되어 있으면 true.</summary>
    public bool HasFilters =>
        (Extensions is { Length: > 0 }) ||
        Types        != null             ||
        PathFragment != null             ||
        SizeBytes    != null             ||
        ModifiedFrom != null;
}
