using Metin2Launcher.Formats;
using Metin2Launcher.Manifest;
using Metin2Launcher.Runtime;
using Xunit;
using ManifestDto = Metin2Launcher.Manifest.Manifest;

namespace Metin2Launcher.Tests;

public class M2PackFormatTests
{
    private const string ValidHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";

    private static ManifestDto SampleManifest() => new()
    {
        Version = "2026.04.14-1",
        Format = "m2pack",
        Launcher = new ManifestLauncherEntry { Path = "Metin2Launcher.exe", Sha256 = "x", Size = 1 },
        Files =
        {
            new ManifestFile { Path = "pack/item.m2p", Sha256 = "h1", Size = 1000 },
            new ManifestFile { Path = "runtime-key.json", Sha256 = "h2", Size = 200 },
        },
    };

    private static string WriteRuntimeKey(string dir, string keyId = "2026.04.14-1")
    {
        var path = Path.Combine(dir, M2PackFormat.RuntimeKeyFileName);
        File.WriteAllText(path, $$"""
{
  "key_id": "{{keyId}}",
  "master_key_hex": "{{ValidHex}}",
  "sign_pubkey_hex": "{{ValidHex}}"
}
""");
        return path;
    }

    [Fact]
    public void Name_is_m2pack()
    {
        Assert.Equal("m2pack", new M2PackFormat().Name);
    }

    [Fact]
    public void FilterApplicable_includes_m2p_and_runtime_key()
    {
        var filtered = new M2PackFormat().FilterApplicable(SampleManifest(), "windows");
        Assert.Equal(2, filtered.Count);
        Assert.Contains(filtered, f => f.Path.EndsWith(".m2p"));
        Assert.Contains(filtered, f => f.Path == "runtime-key.json");
    }

    [Fact]
    public void OnApplied_loads_runtime_key_when_present()
    {
        var tmp = Path.Combine(Path.GetTempPath(), "m2p-" + Guid.NewGuid());
        Directory.CreateDirectory(tmp);
        try
        {
            WriteRuntimeKey(tmp);
            var outcome = new ReleaseOutcome();
            new M2PackFormat().OnApplied(
                tmp,
                new LoadedManifest(Array.Empty<byte>(), Array.Empty<byte>(), SampleManifest()),
                outcome);

            Assert.NotNull(outcome.RuntimeKey);
            Assert.Equal("2026.04.14-1", outcome.RuntimeKey!.KeyId);
            Assert.Equal(ValidHex, outcome.RuntimeKey.MasterKeyHex);
        }
        finally { Directory.Delete(tmp, true); }
    }

    [Fact]
    public void OnApplied_tolerates_missing_runtime_key()
    {
        var tmp = Path.Combine(Path.GetTempPath(), "m2p-" + Guid.NewGuid());
        Directory.CreateDirectory(tmp);
        try
        {
            var outcome = new ReleaseOutcome();
            new M2PackFormat().OnApplied(
                tmp,
                new LoadedManifest(Array.Empty<byte>(), Array.Empty<byte>(), SampleManifest()),
                outcome);
            Assert.Null(outcome.RuntimeKey); // warn-logged, not thrown
        }
        finally { Directory.Delete(tmp, true); }
    }

    [Fact]
    public void OnApplied_throws_on_malformed_runtime_key()
    {
        var tmp = Path.Combine(Path.GetTempPath(), "m2p-" + Guid.NewGuid());
        Directory.CreateDirectory(tmp);
        try
        {
            File.WriteAllText(Path.Combine(tmp, M2PackFormat.RuntimeKeyFileName), "{ not valid }");
            var outcome = new ReleaseOutcome();
            Assert.ThrowsAny<Exception>(() =>
                new M2PackFormat().OnApplied(
                    tmp,
                    new LoadedManifest(Array.Empty<byte>(), Array.Empty<byte>(), SampleManifest()),
                    outcome));
        }
        finally { Directory.Delete(tmp, true); }
    }

    [Fact]
    public void Launcher_never_opens_m2p_archive()
    {
        // Guard test: the format must not touch the .m2p file itself. We assert
        // by constructing a manifest whose .m2p entry points at a file that
        // does not exist on disk and would throw if opened — OnApplied must
        // ignore it entirely and only touch runtime-key.json.
        var tmp = Path.Combine(Path.GetTempPath(), "m2p-" + Guid.NewGuid());
        Directory.CreateDirectory(tmp);
        try
        {
            WriteRuntimeKey(tmp);
            // Intentionally no pack/item.m2p on disk.
            var outcome = new ReleaseOutcome();
            new M2PackFormat().OnApplied(
                tmp,
                new LoadedManifest(Array.Empty<byte>(), Array.Empty<byte>(), SampleManifest()),
                outcome);
            Assert.NotNull(outcome.RuntimeKey);
        }
        finally { Directory.Delete(tmp, true); }
    }
}
