using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using StellaOps.Vexer.Core;
using Xunit;

namespace StellaOps.Vexer.Core.Tests;

public sealed class VexConsensusResolverTests
{
    private static readonly VexProduct DemoProduct = new(
        key: "pkg:demo/app",
        name: "Demo App",
        version: "1.0.0",
        purl: "pkg:demo/app@1.0.0",
        cpe: "cpe:2.3:a:demo:app:1.0.0");

    [Fact]
    public void Resolve_SingleAcceptedClaim_SelectsStatus()
    {
        var provider = CreateProvider("redhat", VexProviderKind.Vendor);
        var claim = CreateClaim(
            "CVE-2025-0001",
            provider.Id,
            VexClaimStatus.Affected,
            justification: null);

        var resolver = new VexConsensusResolver(new BaselineVexConsensusPolicy());

        var result = resolver.Resolve(new VexConsensusRequest(
            claim.VulnerabilityId,
            DemoProduct,
            new[] { claim },
            new Dictionary<string, VexProvider> { [provider.Id] = provider },
            DateTimeOffset.Parse("2025-10-15T12:00:00Z")));

        Assert.Equal(VexConsensusStatus.Affected, result.Consensus.Status);
        Assert.Equal("baseline/v1", result.Consensus.PolicyVersion);
        Assert.Single(result.Consensus.Sources);
        Assert.Empty(result.Consensus.Conflicts);
        Assert.NotNull(result.Consensus.Summary);
        Assert.Contains("affected", result.Consensus.Summary!, StringComparison.Ordinal);

        var decision = Assert.Single(result.DecisionLog);
        Assert.True(decision.Included);
        Assert.Equal(provider.Id, decision.ProviderId);
        Assert.Null(decision.Reason);
    }

    [Fact]
    public void Resolve_NotAffectedWithoutJustification_IsRejected()
    {
        var provider = CreateProvider("cisco", VexProviderKind.Vendor);
        var claim = CreateClaim(
            "CVE-2025-0002",
            provider.Id,
            VexClaimStatus.NotAffected,
            justification: null);

        var resolver = new VexConsensusResolver(new BaselineVexConsensusPolicy());

        var result = resolver.Resolve(new VexConsensusRequest(
            claim.VulnerabilityId,
            DemoProduct,
            new[] { claim },
            new Dictionary<string, VexProvider> { [provider.Id] = provider },
            DateTimeOffset.Parse("2025-10-15T12:00:00Z")));

        Assert.Equal(VexConsensusStatus.UnderInvestigation, result.Consensus.Status);
        Assert.Empty(result.Consensus.Sources);
        var conflict = Assert.Single(result.Consensus.Conflicts);
        Assert.Equal("missing_justification", conflict.Reason);

        var decision = Assert.Single(result.DecisionLog);
        Assert.False(decision.Included);
        Assert.Equal("missing_justification", decision.Reason);
    }

    [Fact]
    public void Resolve_MajorityWeightWins_WithConflictingSources()
    {
        var vendor = CreateProvider("redhat", VexProviderKind.Vendor);
        var distro = CreateProvider("fedora", VexProviderKind.Distro);

        var claims = new[]
        {
            CreateClaim(
                "CVE-2025-0003",
                vendor.Id,
                VexClaimStatus.Affected,
                detail: "Vendor advisory",
                documentDigest: "sha256:vendor"),
            CreateClaim(
                "CVE-2025-0003",
                distro.Id,
                VexClaimStatus.NotAffected,
                justification: VexJustification.ComponentNotPresent,
                detail: "Distro package not shipped",
                documentDigest: "sha256:distro"),
        };

        var resolver = new VexConsensusResolver(new BaselineVexConsensusPolicy());

        var result = resolver.Resolve(new VexConsensusRequest(
            "CVE-2025-0003",
            DemoProduct,
            claims,
            new Dictionary<string, VexProvider>
            {
                [vendor.Id] = vendor,
                [distro.Id] = distro,
            },
            DateTimeOffset.Parse("2025-10-15T12:00:00Z")));

        Assert.Equal(VexConsensusStatus.Affected, result.Consensus.Status);
        Assert.Equal(2, result.Consensus.Sources.Length);
        Assert.Equal(1.0, result.Consensus.Sources.First(s => s.ProviderId == vendor.Id).Weight);
        Assert.Contains(result.Consensus.Conflicts, c => c.ProviderId == distro.Id && c.Reason == "status_conflict");
        Assert.NotNull(result.Consensus.Summary);
        Assert.Contains("affected", result.Consensus.Summary!, StringComparison.Ordinal);
    }

    [Fact]
    public void Resolve_TieFallsBackToUnderInvestigation()
    {
        var hub = CreateProvider("hub", VexProviderKind.Hub);
        var platform = CreateProvider("platform", VexProviderKind.Platform);

        var claims = new[]
        {
            CreateClaim(
                "CVE-2025-0004",
                hub.Id,
                VexClaimStatus.Affected,
                detail: "Hub escalation",
                documentDigest: "sha256:hub"),
            CreateClaim(
                "CVE-2025-0004",
                platform.Id,
                VexClaimStatus.NotAffected,
                justification: VexJustification.ProtectedByMitigatingControl,
                detail: "Runtime mitigations",
                documentDigest: "sha256:platform"),
        };

        var resolver = new VexConsensusResolver(new BaselineVexConsensusPolicy(
            new VexConsensusPolicyOptions(
                hubWeight: 0.5,
                platformWeight: 0.5)));

        var result = resolver.Resolve(new VexConsensusRequest(
            "CVE-2025-0004",
            DemoProduct,
            claims,
            new Dictionary<string, VexProvider>
            {
                [hub.Id] = hub,
                [platform.Id] = platform,
            },
            DateTimeOffset.Parse("2025-10-15T12:00:00Z")));

        Assert.Equal(VexConsensusStatus.UnderInvestigation, result.Consensus.Status);
        Assert.Equal(2, result.Consensus.Conflicts.Length);
        Assert.NotNull(result.Consensus.Summary);
        Assert.Contains("No majority consensus", result.Consensus.Summary!, StringComparison.Ordinal);
    }

    private static VexProvider CreateProvider(string id, VexProviderKind kind)
        => new(
            id,
            displayName: id.ToUpperInvariant(),
            kind,
            baseUris: Array.Empty<Uri>(),
            trust: new VexProviderTrust(weight: 1.0, cosign: null));

    private static VexClaim CreateClaim(
        string vulnerabilityId,
        string providerId,
        VexClaimStatus status,
        VexJustification? justification = null,
        string? detail = null,
        string? documentDigest = null)
        => new(
            vulnerabilityId,
            providerId,
            DemoProduct,
            status,
            new VexClaimDocument(
                VexDocumentFormat.Csaf,
                documentDigest ?? $"sha256:{providerId}",
                new Uri($"https://example.org/{providerId}/{vulnerabilityId}.json"),
                "1"),
            firstSeen: DateTimeOffset.Parse("2025-10-10T12:00:00Z"),
            lastSeen: DateTimeOffset.Parse("2025-10-11T12:00:00Z"),
            justification,
            detail,
            confidence: null,
            additionalMetadata: ImmutableDictionary<string, string>.Empty);
}
