using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace AxCopilot.Services.Agent;

public static class SkillService
{
	private static List<SkillDefinition> _skills = new List<SkillDefinition>();

	private static string _lastFolder = "";

	private static readonly Dictionary<string, string> ToolNameMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
	{
		["Bash"] = "process",
		["bash"] = "process",
		["Read"] = "file_read",
		["Write"] = "file_write",
		["Edit"] = "file_edit",
		["Glob"] = "glob",
		["Grep"] = "grep_tool",
		["WebSearch"] = "http_tool",
		["WebFetch"] = "http_tool",
		["execute_command"] = "process",
		["read_file"] = "file_read",
		["write_file"] = "file_write",
		["edit_file"] = "file_edit",
		["search_files"] = "glob",
		["search_content"] = "grep_tool",
		["list_files"] = "folder_map",
		["shell"] = "process",
		["terminal"] = "process",
		["cat"] = "file_read",
		["find"] = "glob",
		["rg"] = "grep_tool",
		["git"] = "git_tool"
	};

	public static IReadOnlyList<SkillDefinition> Skills => _skills;

	public static void LoadSkills(string? customFolder = null)
	{
		List<string> list = new List<string>();
		string text = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "skills");
		if (Directory.Exists(text))
		{
			list.Add(text);
		}
		string text2 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "skills");
		if (Directory.Exists(text2))
		{
			list.Add(text2);
		}
		if (!string.IsNullOrEmpty(customFolder) && Directory.Exists(customFolder))
		{
			list.Add(customFolder);
		}
		List<SkillDefinition> list2 = new List<SkillDefinition>();
		HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		foreach (string item in list)
		{
			string[] files = Directory.GetFiles(item, "*.skill.md");
			foreach (string text3 in files)
			{
				try
				{
					SkillDefinition skillDefinition = ParseSkillFile(text3);
					if (skillDefinition != null && hashSet.Add(skillDefinition.Name))
					{
						list2.Add(skillDefinition);
					}
				}
				catch (Exception ex)
				{
					LogService.Warn("스킬 로드 실패 [" + text3 + "]: " + ex.Message);
				}
			}
			try
			{
				string[] directories = Directory.GetDirectories(item);
				foreach (string path in directories)
				{
					string text4 = Path.Combine(path, "SKILL.md");
					if (!File.Exists(text4))
					{
						continue;
					}
					try
					{
						SkillDefinition skillDefinition2 = ParseSkillFile(text4);
						if (skillDefinition2 != null && hashSet.Add(skillDefinition2.Name))
						{
							list2.Add(skillDefinition2);
						}
					}
					catch (Exception ex2)
					{
						LogService.Warn("스킬 로드 실패 [" + text4 + "]: " + ex2.Message);
					}
				}
			}
			catch
			{
			}
		}
		foreach (SkillDefinition item2 in list2)
		{
			if (!string.IsNullOrEmpty(item2.Requires))
			{
				IEnumerable<string> source = from r in item2.Requires.Split(',')
					select r.Trim();
				item2.IsAvailable = source.All((string r) => RuntimeDetector.IsAvailable(r));
			}
		}
		_skills = list2;
		_lastFolder = customFolder ?? "";
		int num = list2.Count((SkillDefinition s) => !s.IsAvailable);
		LogService.Info($"스킬 {list2.Count}개 로드 완료" + ((num > 0) ? $" (런타임 미충족 {num}개)" : ""));
	}

	public static SkillDefinition? Find(string name)
	{
		return _skills.FirstOrDefault((SkillDefinition s) => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
	}

	public static List<SkillDefinition> MatchSlashCommand(string input)
	{
		if (!input.StartsWith('/'))
		{
			return new List<SkillDefinition>();
		}
		return _skills.Where((SkillDefinition s) => ("/" + s.Name).StartsWith(input, StringComparison.OrdinalIgnoreCase)).ToList();
	}

	public static void EnsureSkillFolder()
	{
		string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "skills");
		if (!Directory.Exists(text))
		{
			Directory.CreateDirectory(text);
		}
		CreateExampleSkill(text, "daily-standup.skill.md", "daily-standup", "데일리 스탠드업", "작업 폴더의 최근 변경사항을 요약하여 데일리 스탠드업 보고서를 생성합니다.", "작업 폴더의 Git 상태와 최근 커밋을 분석하여 데일리 스탠드업 보고서를 작성하세요.\n\n다음 도구를 사용하세요:\n1. git_tool (action: log, args: \"--oneline -10\") — 최근 커밋 확인\n2. git_tool (action: status) — 현재 변경사항 확인\n3. git_tool (action: diff, args: \"--stat\") — 변경 파일 통계\n\n보고서 형식:\n## \ud83d\udccb 데일리 스탠드업 보고서\n\n### ✅ 완료한 작업\n- 최근 커밋 기반으로 정리\n\n### \ud83d\udd04 진행 중인 작업\n- 현재 수정 중인 파일 기반\n\n### ⚠\ufe0f 블로커/이슈\n- TODO/FIXME가 있으면 표시\n\n한국어로 작성하세요.");
		CreateExampleSkill(text, "bug-hunt.skill.md", "bug-hunt", "버그 탐색", "작업 폴더에서 잠재적 버그 패턴을 검색합니다.", "작업 폴더의 코드에서 잠재적 버그 패턴을 찾아 보고하세요.\n\n다음 도구를 사용하세요:\n1. grep_tool — 위험 패턴 검색:\n   - 빈 catch 블록: catch\\s*\\{\\s*\\}\n   - TODO/FIXME: (TODO|FIXME|HACK|XXX)\n   - .Result/.Wait(): \\.(Result|Wait\\(\\))\n   - 하드코딩된 자격증명: (password|secret|apikey)\\s*=\\s*\"\n2. code_review (action: diff_review, focus: bugs) — 최근 변경사항 버그 검사\n\n결과를 심각도별로 분류하여 보고하세요:\n- \ud83d\udd34 CRITICAL: 즉시 수정 필요\n- \ud83d\udfe1 WARNING: 검토 필요\n- \ud83d\udd35 INFO: 개선 권장\n\n한국어로 작성하세요.");
		CreateExampleSkill(text, "code-explain.skill.md", "code-explain", "코드 설명", "지정한 파일의 코드를 상세히 설명합니다.", "사용자가 지정한 파일 또는 작업 폴더의 주요 파일을 읽고 상세히 설명하세요.\n\n다음 도구를 사용하세요:\n1. file_read — 파일 내용 읽기\n2. folder_map — 프로젝트 구조 파악 (필요시)\n\n설명 포함 사항:\n- 파일의 역할과 책임\n- 주요 클래스/함수의 목적\n- 데이터 흐름\n- 외부 의존성\n- 개선 포인트 (있다면)\n\n한국어로 쉽게 설명하세요. 코드 블록을 활용하여 핵심 부분을 인용하세요.");
	}

	public static string? ExportSkill(SkillDefinition skill, string outputDir)
	{
		try
		{
			if (!File.Exists(skill.FilePath))
			{
				LogService.Warn("스킬 내보내기 실패: 파일 없음 — " + skill.FilePath);
				return null;
			}
			string path = skill.Name + ".skill.zip";
			string text = Path.Combine(outputDir, path);
			if (File.Exists(text))
			{
				File.Delete(text);
			}
			using ZipArchive destination = ZipFile.Open(text, ZipArchiveMode.Create);
			if (skill.IsStandardFormat)
			{
				string directoryName = Path.GetDirectoryName(skill.FilePath);
				if (directoryName != null && Directory.Exists(directoryName))
				{
					string fileName = Path.GetFileName(directoryName);
					foreach (string item in Directory.EnumerateFiles(directoryName, "*", SearchOption.AllDirectories))
					{
						bool flag;
						switch (Path.GetExtension(item).ToLowerInvariant())
						{
						case ".exe":
						case ".dll":
						case ".bat":
						case ".cmd":
						case ".ps1":
						case ".sh":
							flag = true;
							break;
						default:
							flag = false;
							break;
						}
						if (!flag)
						{
							string entryName = fileName + "/" + Path.GetRelativePath(directoryName, item).Replace('\\', '/');
							destination.CreateEntryFromFile(item, entryName, CompressionLevel.Optimal);
						}
					}
				}
			}
			else
			{
				string entryName2 = skill.Name + "/" + Path.GetFileName(skill.FilePath);
				destination.CreateEntryFromFile(skill.FilePath, entryName2, CompressionLevel.Optimal);
			}
			LogService.Info("스킬 내보내기 완료: " + text);
			return text;
		}
		catch (Exception ex)
		{
			LogService.Warn("스킬 내보내기 실패: " + ex.Message);
			return null;
		}
	}

	public static int ImportSkills(string zipPath)
	{
		try
		{
			if (!File.Exists(zipPath))
			{
				LogService.Warn("스킬 가져오기 실패: 파일 없음 — " + zipPath);
				return 0;
			}
			string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "skills");
			if (!Directory.Exists(text))
			{
				Directory.CreateDirectory(text);
			}
			using ZipArchive zipArchive = ZipFile.OpenRead(zipPath);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".exe", ".dll", ".bat", ".cmd", ".ps1", ".sh", ".com", ".scr", ".msi" };
			foreach (ZipArchiveEntry entry in zipArchive.Entries)
			{
				if (hashSet.Contains(Path.GetExtension(entry.Name)))
				{
					LogService.Warn("스킬 가져오기 차단: 실행 가능 파일 포함 — " + entry.FullName);
					return 0;
				}
			}
			List<ZipArchiveEntry> list = zipArchive.Entries.Where((ZipArchiveEntry e) => e.Name.EndsWith(".skill.md", StringComparison.OrdinalIgnoreCase) || e.Name.Equals("SKILL.md", StringComparison.OrdinalIgnoreCase)).ToList();
			if (list.Count == 0)
			{
				LogService.Warn("스킬 가져오기 실패: zip에 .skill.md 또는 SKILL.md 파일 없음");
				return 0;
			}
			int num = 0;
			foreach (ZipArchiveEntry entry2 in zipArchive.Entries)
			{
				if (string.IsNullOrEmpty(entry2.Name))
				{
					continue;
				}
				string text2 = entry2.FullName.Replace('/', Path.DirectorySeparatorChar);
				if (!text2.Contains(".."))
				{
					string text3 = Path.Combine(text, text2);
					string directoryName = Path.GetDirectoryName(text3);
					if (directoryName != null && !Directory.Exists(directoryName))
					{
						Directory.CreateDirectory(directoryName);
					}
					entry2.ExtractToFile(text3, overwrite: true);
					if (entry2.Name.EndsWith(".skill.md", StringComparison.OrdinalIgnoreCase) || entry2.Name.Equals("SKILL.md", StringComparison.OrdinalIgnoreCase))
					{
						num++;
					}
				}
			}
			if (num > 0)
			{
				LogService.Info($"스킬 가져오기 완료: {num}개 스킬 ({zipPath})");
				LoadSkills();
			}
			return num;
		}
		catch (Exception ex)
		{
			LogService.Warn("스킬 가져오기 실패: " + ex.Message);
			return 0;
		}
	}

	public static string MapToolNames(string skillBody)
	{
		if (string.IsNullOrEmpty(skillBody))
		{
			return skillBody;
		}
		foreach (KeyValuePair<string, string> item in ToolNameMap)
		{
			skillBody = skillBody.Replace("`" + item.Key + "`", "`" + item.Value + "`");
			skillBody = skillBody.Replace("(" + item.Key + ")", "(" + item.Value + ")");
		}
		return skillBody;
	}

	private static void CreateExampleSkill(string folder, string fileName, string name, string label, string description, string body)
	{
		string path = Path.Combine(folder, fileName);
		if (!File.Exists(path))
		{
			string text = $"---\nname: {name}\nlabel: {label}\ndescription: {description}\nicon: \\uE768\n---\n\n{body.Trim()}";
			string[] value = (from l in text.Split('\n')
				select l.TrimStart()).ToArray();
			File.WriteAllText(path, string.Join('\n', value), Encoding.UTF8);
		}
	}

	private static SkillDefinition? ParseSkillFile(string filePath)
	{
		string text = File.ReadAllText(filePath, Encoding.UTF8);
		if (!text.TrimStart().StartsWith("---"))
		{
			return null;
		}
		int num = text.IndexOf("---", StringComparison.Ordinal);
		int num2 = text.IndexOf("---", num + 3, StringComparison.Ordinal);
		if (num2 < 0)
		{
			return null;
		}
		int num3 = num + 3;
		string text2 = text.Substring(num3, num2 - num3).Trim();
		string text3 = text;
		num3 = num2 + 3;
		string skillBody = text3.Substring(num3, text3.Length - num3).Trim();
		Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		string text4 = null;
		string[] array = text2.Split('\n');
		foreach (string text5 in array)
		{
			if (text4 != null && (text5.StartsWith("  ") || text5.StartsWith("\t")))
			{
				string text6 = text5.TrimStart();
				int num4 = text6.IndexOf(':');
				if (num4 > 0)
				{
					string text7 = text6.Substring(0, num4).Trim();
					text3 = text6;
					num3 = num4 + 1;
					string value = text3.Substring(num3, text3.Length - num3).Trim().Trim('"', '\'');
					dictionary[text4 + "." + text7] = value;
				}
				continue;
			}
			text4 = null;
			int num5 = text5.IndexOf(':');
			if (num5 > 0)
			{
				string text8 = text5.Substring(0, num5).Trim();
				text3 = text5;
				num3 = num5 + 1;
				string value2 = text3.Substring(num3, text3.Length - num3).Trim().Trim('"', '\'');
				if (string.IsNullOrEmpty(value2))
				{
					text4 = text8;
				}
				else
				{
					dictionary[text8] = value2;
				}
			}
		}
		string fileName = Path.GetFileName(Path.GetDirectoryName(filePath) ?? "");
		string text9 = Path.GetFileNameWithoutExtension(filePath).Replace(".skill", "");
		string defaultValue = (filePath.EndsWith("SKILL.md", StringComparison.OrdinalIgnoreCase) ? fileName : text9);
		string valueOrDefault = dictionary.GetValueOrDefault("name", defaultValue);
		if (string.IsNullOrEmpty(valueOrDefault))
		{
			return null;
		}
		string text10 = dictionary.GetValueOrDefault("label", "") ?? "";
		string value3 = dictionary.GetValueOrDefault("icon", "") ?? "";
		if (string.IsNullOrEmpty(text10) && dictionary.TryGetValue("metadata.label", out var value4))
		{
			text10 = value4 ?? "";
		}
		if (string.IsNullOrEmpty(value3) && dictionary.TryGetValue("metadata.icon", out var value5))
		{
			value3 = value5 ?? "";
		}
		return new SkillDefinition
		{
			Id = valueOrDefault,
			Name = valueOrDefault,
			Label = (string.IsNullOrEmpty(text10) ? valueOrDefault : text10),
			Description = (dictionary.GetValueOrDefault("description", "") ?? ""),
			Icon = (string.IsNullOrEmpty(value3) ? "\ue768" : ConvertUnicodeEscape(value3)),
			SystemPrompt = MapToolNames(skillBody),
			FilePath = filePath,
			License = (dictionary.GetValueOrDefault("license", "") ?? ""),
			Compatibility = (dictionary.GetValueOrDefault("compatibility", "") ?? ""),
			AllowedTools = (dictionary.GetValueOrDefault("allowed-tools", "") ?? ""),
			Requires = (dictionary.GetValueOrDefault("requires", "") ?? ""),
			Tabs = (dictionary.GetValueOrDefault("tabs", "all") ?? "all")
		};
	}

	private static string ConvertUnicodeEscape(string value)
	{
		if (string.IsNullOrEmpty(value))
		{
			return value;
		}
		return Regex.Replace(value, "\\\\u([0-9a-fA-F]{4})", (Match m) => ((char)Convert.ToInt32(m.Groups[1].Value, 16)).ToString());
	}
}
