using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace ShrinkNetwork.ServerHost.Framework;

public sealed class UnityNetworkScanManifest
{
    public string AssetsPath { get; set; } = string.Empty;
    public DateTimeOffset ScannedAtUtc { get; set; }
    public List<UnityNetworkMessageEntry> Messages { get; set; } = new();
    public List<UnityNetworkSubscriberEntry> Subscribers { get; set; } = new();
}

public sealed class UnityNetworkMessageEntry
{
    public string FilePath { get; set; } = string.Empty;
    public string TypeName { get; set; } = string.Empty;
    public int Opcode { get; set; }
    public string Route { get; set; } = string.Empty;
}

public sealed class UnityNetworkSubscriberEntry
{
    public string FilePath { get; set; } = string.Empty;
    public string MemberName { get; set; } = string.Empty;
    public string Authority { get; set; } = string.Empty;
    public string Permission { get; set; } = string.Empty;
}

public static class UnityNetworkCodeScanner
{
    private static readonly Regex MessageRegex = new(
        @"\[ShrinkNetworkMessage\((?<opcode>-?\d+)\s*,\s*""(?<route>[^""]+)""\)\][\s\S]*?(?:class|struct)\s+(?<name>\w+)",
        RegexOptions.Compiled);

    private static readonly Regex SubscriberRegex = new(
        @"\[ShrinkNetworkSubscribe\((?<args>.*?)\)\][\s\S]*?(?:UniTask<.*?>|UniTask|void|Task<.*?>|Task)\s+(?<name>\w+)\s*\(",
        RegexOptions.Compiled);

    private static readonly Regex AuthorityRegex = new(
        @"Authority\s*=\s*ShrinkNetworkAuthority\.(?<value>\w+)",
        RegexOptions.Compiled);

    private static readonly Regex PermissionRegex = new(
        @"Permission\s*=\s*""(?<value>[^""]+)""",
        RegexOptions.Compiled);

    public static UnityNetworkScanManifest Scan(string assetsPath)
    {
        var manifest = new UnityNetworkScanManifest
        {
            AssetsPath = assetsPath,
            ScannedAtUtc = DateTimeOffset.UtcNow
        };

        if (!Directory.Exists(assetsPath))
            return manifest;

        foreach (var file in Directory.EnumerateFiles(assetsPath, "*.cs", SearchOption.AllDirectories))
        {
            if (file.IndexOf($"{Path.DirectorySeparatorChar}Editor{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) >= 0)
                continue;

            var content = File.ReadAllText(file);
            var relativePath = Path.GetRelativePath(assetsPath, file).Replace('\\', '/');

            foreach (Match match in MessageRegex.Matches(content))
            {
                manifest.Messages.Add(new UnityNetworkMessageEntry
                {
                    FilePath = relativePath,
                    TypeName = match.Groups["name"].Value,
                    Opcode = int.Parse(match.Groups["opcode"].Value),
                    Route = match.Groups["route"].Value
                });
            }

            foreach (Match match in SubscriberRegex.Matches(content))
            {
                var args = match.Groups["args"].Value;
                manifest.Subscribers.Add(new UnityNetworkSubscriberEntry
                {
                    FilePath = relativePath,
                    MemberName = match.Groups["name"].Value,
                    Authority = AuthorityRegex.Match(args).Groups["value"].Value,
                    Permission = PermissionRegex.Match(args).Groups["value"].Value
                });
            }
        }

        manifest.Messages = manifest.Messages
            .OrderBy(item => item.Opcode)
            .ThenBy(item => item.TypeName, StringComparer.Ordinal)
            .ToList();

        manifest.Subscribers = manifest.Subscribers
            .OrderBy(item => item.Permission, StringComparer.Ordinal)
            .ThenBy(item => item.MemberName, StringComparer.Ordinal)
            .ToList();

        return manifest;
    }

    public static void WriteOutputs(UnityNetworkScanManifest manifest, string outputDirectory)
    {
        Directory.CreateDirectory(outputDirectory);

        var jsonPath = Path.Combine(outputDirectory, "unity-network-scan.json");
        var markdownPath = Path.Combine(outputDirectory, "UNITY_NETWORK_SCAN.md");

        File.WriteAllText(jsonPath, JsonSerializer.Serialize(manifest, new JsonSerializerOptions
        {
            WriteIndented = true
        }));

        var markdown = new StringBuilder();
        markdown.AppendLine("# Unity 网络代码扫描清单");
        markdown.AppendLine();
        markdown.AppendLine($"扫描时间：{manifest.ScannedAtUtc:yyyy-MM-dd HH:mm:ss} UTC");
        markdown.AppendLine($"Assets 路径：`{manifest.AssetsPath}`");
        markdown.AppendLine();
        markdown.AppendLine("## 网络消息");
        markdown.AppendLine();

        foreach (var message in manifest.Messages)
            markdown.AppendLine($"- `{message.Opcode}` `{message.Route}` `{message.TypeName}` [{message.FilePath}]");

        markdown.AppendLine();
        markdown.AppendLine("## 订阅与权限");
        markdown.AppendLine();

        if (manifest.Subscribers.Count == 0)
        {
            markdown.AppendLine("- 本次扫描未发现 `[ShrinkNetworkSubscribe]`。");
        }
        else
        {
            foreach (var subscriber in manifest.Subscribers)
                markdown.AppendLine($"- `{subscriber.MemberName}` authority=`{subscriber.Authority}` permission=`{subscriber.Permission}` [{subscriber.FilePath}]");
        }

        File.WriteAllText(markdownPath, markdown.ToString());
    }
}
