using System.Text;
using Metin2Launcher.Manifest;
using Xunit;

namespace Metin2Launcher.Tests;

public class ManifestLoaderTests
{
    private const string SampleJson = """
{
  "version": "2026.04.14-1",
  "created_at": "2026-04-14T14:00:00Z",
  "previous": "2026.04.13-3",
  "notes": "example",
  "launcher": {
    "path": "Metin2Launcher.exe",
    "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "size": 15728640,
    "platform": "windows"
  },
  "files": [
    {
      "path": "Metin2.exe",
      "sha256": "2653e87ecd8ba305b70a96098788478e8b60c125ec83bcd1307486eafb5c5289",
      "size": 27982848,
      "platform": "windows"
    },
    {
      "path": "pack/item.pck",
      "sha256": "7aa9d46724a921fecf5af14c10372d0d03922e92a4cace4b5c15c451416f36b7",
      "size": 128547328
    },
    {
      "path": "pack/optional.pck",
      "sha256": "3b9dfe45317a14fcb70a138c1b9d57d984fe130e833c4341deaaff93d615ac67",
      "size": 4587520,
      "required": false
    }
  ]
}
""";

    [Fact]
    public void Parse_reads_all_fields()
    {
        var m = ManifestLoader.Parse(Encoding.UTF8.GetBytes(SampleJson));
        Assert.Equal("2026.04.14-1", m.Version);
        Assert.Equal("2026-04-14T14:00:00Z", m.CreatedAt);
        Assert.Equal("2026.04.13-3", m.Previous);
        Assert.Equal("example", m.Notes);
        Assert.Equal("Metin2Launcher.exe", m.Launcher.Path);
        Assert.Equal(3, m.Files.Count);
        Assert.Equal("Metin2.exe", m.Files[0].Path);
        Assert.True(m.Files[0].IsRequired);
        Assert.False(m.Files[2].IsRequired);
        Assert.Equal("all", m.Files[1].EffectivePlatform);
        Assert.True(m.Files[1].AppliesTo("windows"));
        Assert.True(m.Files[1].AppliesTo("linux"));
        Assert.True(m.Files[0].AppliesTo("windows"));
        Assert.False(m.Files[0].AppliesTo("linux"));
    }

    [Fact]
    public void Roundtrip_parse_serialize_parse_is_stable()
    {
        var raw = Encoding.UTF8.GetBytes(SampleJson);
        var first = ManifestLoader.Parse(raw);
        var bytes2 = ManifestLoader.Serialize(first);
        var second = ManifestLoader.Parse(bytes2);
        Assert.Equal(first.Version, second.Version);
        Assert.Equal(first.Files.Count, second.Files.Count);
        for (int i = 0; i < first.Files.Count; i++)
        {
            Assert.Equal(first.Files[i].Path, second.Files[i].Path);
            Assert.Equal(first.Files[i].Sha256, second.Files[i].Sha256);
            Assert.Equal(first.Files[i].Size, second.Files[i].Size);
            Assert.Equal(first.Files[i].IsRequired, second.Files[i].IsRequired);
        }
    }

    [Fact]
    public void Parse_ignores_unknown_optional_fields()
    {
        const string withExtra = """
{
  "version": "2026.04.14-1",
  "created_at": "2026-04-14T14:00:00Z",
  "future_field": {"nested": 1},
  "launcher": {
    "path": "Metin2Launcher.exe",
    "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "size": 100
  },
  "files": []
}
""";
        var m = ManifestLoader.Parse(Encoding.UTF8.GetBytes(withExtra));
        Assert.Equal("2026.04.14-1", m.Version);
        Assert.Empty(m.Files);
    }

    [Fact]
    public void Parse_rejects_missing_version()
    {
        const string bad = """
{
  "created_at": "2026-04-14T14:00:00Z",
  "launcher": {"path": "x", "sha256": "y", "size": 1},
  "files": []
}
""";
        Assert.Throws<InvalidDataException>(() => ManifestLoader.Parse(Encoding.UTF8.GetBytes(bad)));
    }

    [Fact]
    public void Files_are_sorted_by_path_in_sample()
    {
        // The spec mandates lexicographic sort; assert our sample is consistent with that.
        var m = ManifestLoader.Parse(Encoding.UTF8.GetBytes(SampleJson));
        for (int i = 1; i < m.Files.Count; i++)
        {
            Assert.True(
                string.CompareOrdinal(m.Files[i - 1].Path, m.Files[i].Path) < 0,
                $"file list not sorted: {m.Files[i - 1].Path} >= {m.Files[i].Path}");
        }
    }
}
