using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace AxCopilot.Services;

/// <summary>
/// 에이전트 메모리 서비스.
/// 작업 폴더별 + 전역 메모리를 관리하며, 대화 간 지속적 컨텍스트를 유지합니다.
/// 저장소: %APPDATA%\AxCopilot\memory\{hash}.dat (암호화)
/// </summary>
public class AgentMemoryService
{
    private static App? CurrentApp => System.Windows.Application.Current as App;

    private static readonly string MemoryDir = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        "AxCopilot", "memory");

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

    private readonly List<MemoryEntry> _entries = new();
    private readonly object _lock = new();
    private string? _currentWorkFolder;

    /// <summary>현재 로드된 메모리 항목 수.</summary>
    public int Count { get { lock (_lock) return _entries.Count; } }

    /// <summary>모든 메모리 항목 (읽기 전용).</summary>
    public IReadOnlyList<MemoryEntry> All { get { lock (_lock) return _entries.ToList(); } }

    /// <summary>작업 폴더별 메모리 + 전역 메모리를 로드합니다.</summary>
    public void Load(string? workFolder)
    {
        lock (_lock)
        {
            _entries.Clear();
            _currentWorkFolder = workFolder;

            // 전역 메모리
            var globalPath = GetFilePath(null);
            LoadFromFile(globalPath);

            // 폴더별 메모리
            if (!string.IsNullOrEmpty(workFolder))
            {
                var folderPath = GetFilePath(workFolder);
                LoadFromFile(folderPath);
            }
        }
    }

    /// <summary>새 메모리를 추가합니다. 유사한 내용이 있으면 병합합니다.</summary>
    public MemoryEntry Add(string type, string content, string? source = null, string? workFolder = null)
    {
        lock (_lock)
        {
            // 중복 검사: 동일 타입 + 유사 내용 (80% 이상 키워드 겹침)
            var existing = _entries.FirstOrDefault(e =>
                e.Type == type && CalculateSimilarity(e.Content, content) > 0.8);
            if (existing != null)
            {
                existing.LastUsedAt = DateTime.Now;
                existing.UseCount++;
                Save();
                return existing;
            }

            var entry = new MemoryEntry
            {
                Id = Guid.NewGuid().ToString("N")[..12],
                Type = type,
                Content = content,
                Source = source ?? "",
                CreatedAt = DateTime.Now,
                LastUsedAt = DateTime.Now,
                UseCount = 1,
                Relevance = 1.0,
                WorkFolder = workFolder,
            };
            _entries.Add(entry);
            Prune();
            Save();
            return entry;
        }
    }

    /// <summary>쿼리와 관련된 메모리를 검색합니다.</summary>
    public List<MemoryEntry> GetRelevant(string query, int maxCount = 10)
    {
        if (string.IsNullOrWhiteSpace(query)) return new();

        lock (_lock)
        {
            var queryTokens = Tokenize(query);
            var scored = _entries
                .Select(e =>
                {
                    var entryTokens = Tokenize(e.Content);
                    var overlap = queryTokens.Intersect(entryTokens, StringComparer.OrdinalIgnoreCase).Count();
                    var score = queryTokens.Count > 0 ? (double)overlap / queryTokens.Count : 0;
                    // 사용 빈도와 최근 사용 가중치 적용
                    score += Math.Min(e.UseCount * 0.05, 0.3);
                    if ((DateTime.Now - e.LastUsedAt).TotalDays < 7) score += 0.1;
                    return (Entry: e, Score: score);
                })
                .Where(x => x.Score > 0.1)
                .OrderByDescending(x => x.Score)
                .Take(maxCount)
                .Select(x =>
                {
                    x.Entry.Relevance = x.Score;
                    return x.Entry;
                })
                .ToList();

            return scored;
        }
    }

    /// <summary>메모리 사용 기록을 갱신합니다.</summary>
    public void Touch(string id)
    {
        lock (_lock)
        {
            var entry = _entries.FirstOrDefault(e => e.Id == id);
            if (entry != null)
            {
                entry.UseCount++;
                entry.LastUsedAt = DateTime.Now;
                Save();
            }
        }
    }

    /// <summary>메모리를 삭제합니다.</summary>
    public bool Remove(string id)
    {
        lock (_lock)
        {
            var removed = _entries.RemoveAll(e => e.Id == id) > 0;
            if (removed) Save();
            return removed;
        }
    }

    /// <summary>모든 메모리를 삭제합니다.</summary>
    public void Clear()
    {
        lock (_lock)
        {
            _entries.Clear();
            Save();
        }
    }

    /// <summary>오래되고 사용되지 않는 메모리를 정리합니다.</summary>
    private void Prune()
    {
        var maxEntries = 100;
        try
        {
            maxEntries = CurrentApp?.SettingsService?.Settings.Llm.MaxMemoryEntries ?? 100;
        }
        catch (Exception) { }

        if (_entries.Count <= maxEntries) return;

        // 점수 기반 정리: 오래되고 사용 안 되는 것부터
        var toRemove = _entries
            .OrderBy(e => e.UseCount)
            .ThenBy(e => e.LastUsedAt)
            .Take(_entries.Count - maxEntries)
            .ToList();

        foreach (var entry in toRemove)
            _entries.Remove(entry);
    }

    /// <summary>메모리를 암호화하여 파일에 저장합니다.</summary>
    private void Save()
    {
        Directory.CreateDirectory(MemoryDir);

        // 전역 메모리
        var globalEntries = _entries.Where(e => e.WorkFolder == null).ToList();
        SaveToFile(GetFilePath(null), globalEntries);

        // 폴더별 메모리
        if (!string.IsNullOrEmpty(_currentWorkFolder))
        {
            var folderEntries = _entries.Where(e => e.WorkFolder != null).ToList();
            SaveToFile(GetFilePath(_currentWorkFolder), folderEntries);
        }
    }

    private void SaveToFile(string path, List<MemoryEntry> entries)
    {
        try
        {
            var json = JsonSerializer.Serialize(entries, JsonOptions);
            var encrypted = CryptoService.PortableEncrypt(json);
            var tempPath = path + ".tmp";
            File.WriteAllText(tempPath, encrypted);
            File.Move(tempPath, path, overwrite: true);
        }
        catch (Exception ex)
        {
            LogService.Warn($"메모리 저장 실패: {ex.Message}");
        }
    }

    private void LoadFromFile(string path)
    {
        if (!File.Exists(path)) return;
        try
        {
            var encrypted = File.ReadAllText(path);
            var json = CryptoService.PortableDecrypt(encrypted);
            if (string.IsNullOrEmpty(json)) return;
            var entries = JsonSerializer.Deserialize<List<MemoryEntry>>(json, JsonOptions);
            if (entries != null)
                _entries.AddRange(entries);
        }
        catch (Exception ex)
        {
            LogService.Warn($"메모리 로드 실패 ({path}): {ex.Message}");
        }
    }

    private static string GetFilePath(string? workFolder)
    {
        if (string.IsNullOrEmpty(workFolder))
            return Path.Combine(MemoryDir, "_global.dat");

        // 폴더 경로를 해시하여 파일명 생성
        var hash = Convert.ToHexString(
            SHA256.HashData(Encoding.UTF8.GetBytes(workFolder.ToLowerInvariant())))[..16];
        return Path.Combine(MemoryDir, $"{hash}.dat");
    }

    /// <summary>두 문자열의 키워드 유사도를 계산합니다 (0~1).</summary>
    private static double CalculateSimilarity(string a, string b)
    {
        var tokensA = Tokenize(a);
        var tokensB = Tokenize(b);
        if (tokensA.Count == 0 || tokensB.Count == 0) return 0;
        var intersection = tokensA.Intersect(tokensB, StringComparer.OrdinalIgnoreCase).Count();
        var union = tokensA.Union(tokensB, StringComparer.OrdinalIgnoreCase).Count();
        return union > 0 ? (double)intersection / union : 0;
    }

    /// <summary>텍스트를 토큰으로 분리합니다.</summary>
    private static HashSet<string> Tokenize(string text)
    {
        var tokens = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        var sb = new StringBuilder();
        foreach (var c in text)
        {
            if (char.IsLetterOrDigit(c) || c == '_')
                sb.Append(c);
            else if (sb.Length > 1) // 1글자 토큰 제외
            {
                tokens.Add(sb.ToString());
                sb.Clear();
            }
            else
                sb.Clear();
        }
        if (sb.Length > 1) tokens.Add(sb.ToString());
        return tokens;
    }
}

/// <summary>에이전트 메모리 항목.</summary>
public class MemoryEntry
{
    [JsonPropertyName("id")]
    public string Id { get; set; } = "";

    [JsonPropertyName("type")]
    public string Type { get; set; } = "fact"; // rule | preference | fact | correction

    [JsonPropertyName("content")]
    public string Content { get; set; } = "";

    [JsonPropertyName("source")]
    public string Source { get; set; } = "";

    [JsonPropertyName("createdAt")]
    public DateTime CreatedAt { get; set; }

    [JsonPropertyName("lastUsedAt")]
    public DateTime LastUsedAt { get; set; }

    [JsonPropertyName("useCount")]
    public int UseCount { get; set; }

    [JsonPropertyName("relevance")]
    public double Relevance { get; set; }

    [JsonPropertyName("workFolder")]
    public string? WorkFolder { get; set; }
}
