using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Windows;

namespace AxCopilot.Services;

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

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

	private readonly List<MemoryEntry> _entries = new List<MemoryEntry>();

	private readonly object _lock = new object();

	private string? _currentWorkFolder;

	public int Count
	{
		get
		{
			lock (_lock)
			{
				return _entries.Count;
			}
		}
	}

	public IReadOnlyList<MemoryEntry> All
	{
		get
		{
			lock (_lock)
			{
				return _entries.ToList();
			}
		}
	}

	public void Load(string? workFolder)
	{
		lock (_lock)
		{
			_entries.Clear();
			_currentWorkFolder = workFolder;
			string filePath = GetFilePath(null);
			LoadFromFile(filePath);
			if (!string.IsNullOrEmpty(workFolder))
			{
				string filePath2 = GetFilePath(workFolder);
				LoadFromFile(filePath2);
			}
		}
	}

	public MemoryEntry Add(string type, string content, string? source = null, string? workFolder = null)
	{
		lock (_lock)
		{
			MemoryEntry memoryEntry = _entries.FirstOrDefault((MemoryEntry e) => e.Type == type && CalculateSimilarity(e.Content, content) > 0.8);
			if (memoryEntry != null)
			{
				memoryEntry.LastUsedAt = DateTime.Now;
				memoryEntry.UseCount++;
				Save();
				return memoryEntry;
			}
			MemoryEntry memoryEntry2 = new MemoryEntry
			{
				Id = Guid.NewGuid().ToString("N").Substring(0, 12),
				Type = type,
				Content = content,
				Source = (source ?? ""),
				CreatedAt = DateTime.Now,
				LastUsedAt = DateTime.Now,
				UseCount = 1,
				Relevance = 1.0,
				WorkFolder = workFolder
			};
			_entries.Add(memoryEntry2);
			Prune();
			Save();
			return memoryEntry2;
		}
	}

	public List<MemoryEntry> GetRelevant(string query, int maxCount = 10)
	{
		if (string.IsNullOrWhiteSpace(query))
		{
			return new List<MemoryEntry>();
		}
		lock (_lock)
		{
			HashSet<string> queryTokens = Tokenize(query);
			return (from x in _entries.Select(delegate(MemoryEntry e)
				{
					HashSet<string> second = Tokenize(e.Content);
					int num = queryTokens.Intersect<string>(second, StringComparer.OrdinalIgnoreCase).Count();
					double num2 = ((queryTokens.Count > 0) ? ((double)num / (double)queryTokens.Count) : 0.0);
					num2 += Math.Min((double)e.UseCount * 0.05, 0.3);
					if ((DateTime.Now - e.LastUsedAt).TotalDays < 7.0)
					{
						num2 += 0.1;
					}
					return (Entry: e, Score: num2);
				})
				where x.Score > 0.1
				orderby x.Score descending
				select x).Take(maxCount).Select(delegate((MemoryEntry Entry, double Score) x)
			{
				x.Entry.Relevance = x.Score;
				return x.Entry;
			}).ToList();
		}
	}

	public void Touch(string id)
	{
		lock (_lock)
		{
			MemoryEntry memoryEntry = _entries.FirstOrDefault((MemoryEntry e) => e.Id == id);
			if (memoryEntry != null)
			{
				memoryEntry.UseCount++;
				memoryEntry.LastUsedAt = DateTime.Now;
				Save();
			}
		}
	}

	public bool Remove(string id)
	{
		lock (_lock)
		{
			bool flag = _entries.RemoveAll((MemoryEntry e) => e.Id == id) > 0;
			if (flag)
			{
				Save();
			}
			return flag;
		}
	}

	public void Clear()
	{
		lock (_lock)
		{
			_entries.Clear();
			Save();
		}
	}

	private void Prune()
	{
		int num = 100;
		try
		{
			num = ((!(Application.Current is App app)) ? ((int?)null) : app.SettingsService?.Settings.Llm.MaxMemoryEntries) ?? 100;
		}
		catch
		{
		}
		if (_entries.Count <= num)
		{
			return;
		}
		List<MemoryEntry> list = (from e in _entries
			orderby e.UseCount, e.LastUsedAt
			select e).Take(_entries.Count - num).ToList();
		foreach (MemoryEntry item in list)
		{
			_entries.Remove(item);
		}
	}

	private void Save()
	{
		Directory.CreateDirectory(MemoryDir);
		List<MemoryEntry> entries = _entries.Where((MemoryEntry e) => e.WorkFolder == null).ToList();
		SaveToFile(GetFilePath(null), entries);
		if (!string.IsNullOrEmpty(_currentWorkFolder))
		{
			List<MemoryEntry> entries2 = _entries.Where((MemoryEntry e) => e.WorkFolder != null).ToList();
			SaveToFile(GetFilePath(_currentWorkFolder), entries2);
		}
	}

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

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

	private static string GetFilePath(string? workFolder)
	{
		if (string.IsNullOrEmpty(workFolder))
		{
			return Path.Combine(MemoryDir, "_global.dat");
		}
		string text = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(workFolder.ToLowerInvariant()))).Substring(0, 16);
		return Path.Combine(MemoryDir, text + ".dat");
	}

	private static double CalculateSimilarity(string a, string b)
	{
		HashSet<string> hashSet = Tokenize(a);
		HashSet<string> hashSet2 = Tokenize(b);
		if (hashSet.Count == 0 || hashSet2.Count == 0)
		{
			return 0.0;
		}
		int num = hashSet.Intersect<string>(hashSet2, StringComparer.OrdinalIgnoreCase).Count();
		int num2 = hashSet.Union<string>(hashSet2, StringComparer.OrdinalIgnoreCase).Count();
		return (num2 > 0) ? ((double)num / (double)num2) : 0.0;
	}

	private static HashSet<string> Tokenize(string text)
	{
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		StringBuilder stringBuilder = new StringBuilder();
		foreach (char c in text)
		{
			if (char.IsLetterOrDigit(c) || c == '_')
			{
				stringBuilder.Append(c);
			}
			else if (stringBuilder.Length > 1)
			{
				hashSet.Add(stringBuilder.ToString());
				stringBuilder.Clear();
			}
			else
			{
				stringBuilder.Clear();
			}
		}
		if (stringBuilder.Length > 1)
		{
			hashSet.Add(stringBuilder.ToString());
		}
		return hashSet;
	}
}
