using System.Net;
using System.Net.Http;
using System.Text.Json;
using NexusShell.Models;
using NexusShell.Services;
using Xunit;

namespace NexusShell.Tests;

public sealed class NexusUpdateServiceTests
{
    private static NexusUpdateManifest ParseManifest(string json)
    {
        var manifest = JsonSerializer.Deserialize<NexusUpdateManifest>(json, new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true,
        });
        Assert.NotNull(manifest);
        return manifest!;
    }

    [Fact]
    public void Manifest_parse_reads_canonical_artifact_fields()
    {
        const string json = """
            {
              "version": "0.1.0-identity3",
              "build": 88,
              "artifact": {
                "url": "https://example.test/pkg.zip",
                "sha256": "abc123",
                "sizeBytes": 42
              },
              "minimumSupportedBuild": 80,
              "mandatory": false,
              "notes": ["smoke"]
            }
            """;

        var manifest = ParseManifest(json);

        Assert.Equal(88, manifest.Build);
        Assert.Equal("https://example.test/pkg.zip", manifest.EffectivePackageUrl);
        Assert.Equal("abc123", manifest.EffectiveSha256);
        Assert.Equal(42, manifest.EffectiveSizeBytes);
        Assert.Equal(80, manifest.EffectiveMinimumSupportedBuild);
        Assert.Single(manifest.EffectiveNotes);
        Assert.Equal("smoke", manifest.EffectiveNotes[0]);
    }

    [Fact]
    public void Manifest_parse_falls_back_to_legacy_packageUrl_and_sha256()
    {
        const string json = """
            {
              "version": "0.1.0-identity2",
              "build": 87,
              "packageUrl": "https://example.test/legacy.zip",
              "sha256": "deadbeef",
              "sizeBytes": 100,
              "releaseNotes": ["legacy note"]
            }
            """;

        var manifest = ParseManifest(json);

        Assert.Equal("https://example.test/legacy.zip", manifest.EffectivePackageUrl);
        Assert.Equal("deadbeef", manifest.EffectiveSha256);
        Assert.Equal(100, manifest.EffectiveSizeBytes);
        Assert.Equal("legacy note", manifest.EffectiveNotes[0]);
    }

    [Fact]
    public void IsRemoteNewer_true_when_remote_build_higher()
    {
        var remote = new NexusUpdateManifest { Build = 88, Version = "0.1.0-identity3" };
        Assert.True(NexusUpdateManifest.IsRemoteNewer(remote, "0.1.0-identity2", 87));
    }

    [Fact]
    public void IsRemoteNewer_false_when_remote_build_older()
    {
        var remote = new NexusUpdateManifest { Build = 86, Version = "0.1.0-old" };
        Assert.False(NexusUpdateManifest.IsRemoteNewer(remote, "0.1.0-identity2", 87));
    }

    [Fact]
    public void IsRemoteNewer_false_when_build_equal_even_if_version_differs()
    {
        var remote = new NexusUpdateManifest { Build = 94, Version = "0.1.0-cdn" };
        Assert.False(NexusUpdateManifest.IsRemoteNewer(remote, "0.1.0-local", 94));
    }

    [Fact]
    public void IsUpdateApplicable_false_when_local_dev_build_is_ahead()
    {
        var remote = new NexusUpdateManifest { Build = 93, Version = "0.1.0-workspace2" };
        Assert.False(NexusUpdateManifest.IsUpdateApplicable(remote, "0.1.0-osxp2", 95));
    }

    [Fact]
    public void IsUpdateApplicable_false_when_below_minimumSupportedBuild()
    {
        var remote = new NexusUpdateManifest
        {
            Build = 88,
            Version = "0.1.0-identity3",
            MinimumSupportedBuild = 90,
        };

        Assert.False(NexusUpdateManifest.IsUpdateApplicable(remote, "0.1.0-identity2", 87));
        Assert.False(remote.MeetsMinimumBuild(87));
    }

    [Fact]
    public void IsUpdateApplicable_true_when_newer_and_meets_minimum()
    {
        var remote = new NexusUpdateManifest
        {
            Build = 88,
            Version = "0.1.0-identity3",
            MinimumSupportedBuild = 80,
        };

        Assert.True(NexusUpdateManifest.IsUpdateApplicable(remote, "0.1.0-identity2", 87));
    }

    [Fact]
    public async Task CheckForUpdates_marks_local_build_ahead_without_downgrade()
    {
        using var handler = new ManifestOnlyHttpHandler("""
            {
              "version": "0.1.0-workspace2",
              "build": 93,
              "packageUrl": "https://example.test/pkg.zip",
              "sha256": "abc123"
            }
            """);
        using var client = new HttpClient(handler);
        var service = new UpdateService(
            client,
            "https://example.test/latest.json",
            "0.1.0-osxp2",
            95,
            autoInstall: false);

        var result = await service.CheckForUpdatesAsync();

        Assert.Equal(UpdateCheckStatus.LocalBuildAhead, result.Status);
        Assert.False(result.IsUpdateAvailable);
        Assert.Equal(ClientUpdateFlowState.UpToDate, service.FlowState);
        Assert.Contains("nyere lokal build", result.ErrorMessage, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task CheckForUpdates_marks_sha_mismatch_as_failed_on_download()
    {
        using var handler = new ShaMismatchHttpHandler();
        using var client = new HttpClient(handler);
        var service = new UpdateService(
            client,
            "https://example.test/latest.json",
            "0.1.0-identity2",
            87,
            autoInstall: false);

        await service.CheckForUpdatesAsync();
        Assert.True(service.LastResult?.IsUpdateAvailable);

        var manifest = service.LastResult!.RemoteManifest!;
        var result = await service.DownloadUpdateAsync(manifest);
        Assert.Equal(UpdateApplyStatus.Failed, result.Status);
        Assert.Contains("SHA256", result.ErrorMessage, StringComparison.OrdinalIgnoreCase);
        Assert.Equal(ClientUpdateFlowState.Failed, service.FlowState);
    }

    private sealed class ShaMismatchHttpHandler : HttpMessageHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            if (request.RequestUri!.AbsolutePath.EndsWith("latest.json", StringComparison.OrdinalIgnoreCase))
            {
                const string manifest = """
                    {
                      "version": "0.1.0-identity3",
                      "build": 88,
                      "packageUrl": "https://example.test/pkg.zip",
                      "sha256": "0000000000000000000000000000000000000000000000000000000000000000",
                      "sizeBytes": 4
                    }
                    """;
                return Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)
                {
                    Content = new StringContent(manifest),
                });
            }

            var bytes = new byte[] { 1, 2, 3, 4 };
            return Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)
            {
                Content = new ByteArrayContent(bytes),
            });
        }
    }

    private sealed class ManifestOnlyHttpHandler(string manifestJson) : HttpMessageHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent(manifestJson),
            });
        }
    }
}
