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

namespace Kompas.Mcp.Tests;

/// <summary>Манифест плагина и пин версии сервера должны быть согласованы между собой.</summary>
[Trait("Category", "Unit")]
public sealed class PluginManifestTests
{
    private const string NoReleaseLock =
        """{"version":"0.0.0","sourceSha":"","url":"","sha256":""}""";

    private const string ValidReleaseLock =
        """{"version":"1.2.3","sourceSha":"deadbeef","url":"https://example.com/kompas-mcp.zip","sha256":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}""";

    private const string ReleaseLockMissingUrl =
        """{"version":"1.2.3","sourceSha":"deadbeef","url":"","sha256":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}""";

    private const string ReleaseLockBadSha256 =
        """{"version":"1.2.3","sourceSha":"deadbeef","url":"https://example.com/kompas-mcp.zip","sha256":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}""";

    private static string PluginRoot => Path.Combine(TestPaths.RepoRoot(), "plugin");

    private static JsonElement Read(params string[] relativeParts)
    {
        var path = Path.Combine(new[] { PluginRoot }.Concat(relativeParts).ToArray());
        Assert.True(File.Exists(path), $"нет файла {path}");
        using var doc = JsonDocument.Parse(File.ReadAllText(path));
        return doc.RootElement.Clone();
    }

    private static JsonElement Parse(string json)
    {
        using var doc = JsonDocument.Parse(json);
        return doc.RootElement.Clone();
    }

    /// <summary>
    /// Правило релизного лока: version == "0.0.0" — релиза ещё не было, правило не применяется;
    /// иначе url обязателен и sha256 — 64 hex-символа в нижнем регистре.
    /// </summary>
    private static bool IsReleasedLockValid(JsonElement serverLock)
    {
        var version = serverLock.GetProperty("version").GetString();
        if (version == "0.0.0")
            return true;

        var url = serverLock.GetProperty("url").GetString();
        var sha256 = serverLock.GetProperty("sha256").GetString();
        return !string.IsNullOrWhiteSpace(url) && Regex.IsMatch(sha256 ?? "", "^[0-9a-f]{64}$");
    }

    [Fact]
    public void Manifest_name_is_kompas()
    {
        var manifest = Read(".claude-plugin", "plugin.json");
        Assert.Equal("kompas", manifest.GetProperty("name").GetString());
    }

    [Fact]
    public void Manifest_version_matches_lock()
    {
        var manifest = Read(".claude-plugin", "plugin.json");
        var serverLock = Read("server.lock.json");
        Assert.Equal(manifest.GetProperty("version").GetString(),
                     serverLock.GetProperty("version").GetString());
    }

    [Theory]
    [InlineData(NoReleaseLock, true)]
    [InlineData(ValidReleaseLock, true)]
    [InlineData(ReleaseLockMissingUrl, false)]
    [InlineData(ReleaseLockBadSha256, false)]
    public void IsReleasedLockValid_matches_release_rule(string json, bool expected)
        => Assert.Equal(expected, IsReleasedLockValid(Parse(json)));

    [Fact]
    public void Lock_file_satisfies_release_rule()
    {
        var serverLock = Read("server.lock.json");
        Assert.True(IsReleasedLockValid(serverLock));
    }

    [Fact]
    public void Doctor_command_exists_and_checks_connection()
    {
        var path = Path.Combine(PluginRoot, "commands", "doctor.md");
        Assert.True(File.Exists(path), $"нет файла {path}");

        var text = File.ReadAllText(path);
        Assert.StartsWith("---", text, StringComparison.Ordinal);
        Assert.Contains("description:", text, StringComparison.Ordinal);
        Assert.Contains("server.lock.json", text, StringComparison.Ordinal);
        Assert.Contains("kompas_status", text, StringComparison.Ordinal);
    }

    [Theory]
    [InlineData("README.md")]
    [InlineData("adapters/codex/config.snippet.toml")]
    [InlineData("adapters/opencode/opencode.json")]
    public void Adapters_reference_the_shared_launcher(string relativePath)
    {
        var path = Path.Combine(PluginRoot, relativePath.Replace('/', Path.DirectorySeparatorChar));
        Assert.True(File.Exists(path), $"нет файла {path}");
        Assert.Contains("launch-kompas-mcp.ps1", File.ReadAllText(path), StringComparison.Ordinal);
    }
}
