using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
using ZB.MOM.WW.OtOpcUa.Server.Redundancy;

namespace ZB.MOM.WW.OtOpcUa.Server.Tests;

[Trait("Category", "Unit")]
public sealed class ServiceLevelCalculatorTests
{
    // --- Reserved bands (0, 1, 2) ---

    [Fact]
    public void OperatorMaintenance_Overrides_Everything()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Primary,
            selfHealthy: true, peerUaHealthy: true, peerHttpHealthy: true,
            applyInProgress: false, recoveryDwellMet: true, topologyValid: true,
            operatorMaintenance: true);

        v.ShouldBe((byte)ServiceLevelBand.Maintenance);
    }

    [Fact]
    public void UnhealthySelf_ReturnsNoData()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Primary,
            selfHealthy: false, peerUaHealthy: true, peerHttpHealthy: true,
            applyInProgress: false, recoveryDwellMet: true, topologyValid: true);

        v.ShouldBe((byte)ServiceLevelBand.NoData);
    }

    [Fact]
    public void InvalidTopology_Demotes_BothNodes_To_2()
    {
        var primary = ServiceLevelCalculator.Compute(
            RedundancyRole.Primary,
            selfHealthy: true, peerUaHealthy: true, peerHttpHealthy: true,
            applyInProgress: false, recoveryDwellMet: true, topologyValid: false);
        var secondary = ServiceLevelCalculator.Compute(
            RedundancyRole.Secondary,
            selfHealthy: true, peerUaHealthy: true, peerHttpHealthy: true,
            applyInProgress: false, recoveryDwellMet: true, topologyValid: false);

        primary.ShouldBe((byte)ServiceLevelBand.InvalidTopology);
        secondary.ShouldBe((byte)ServiceLevelBand.InvalidTopology);
    }

    // --- Operational bands (authoritative) ---

    [Fact]
    public void Authoritative_Primary_Is_255()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Primary,
            selfHealthy: true, peerUaHealthy: true, peerHttpHealthy: true,
            applyInProgress: false, recoveryDwellMet: true, topologyValid: true);

        v.ShouldBe((byte)ServiceLevelBand.AuthoritativePrimary);
        v.ShouldBe((byte)255);
    }

    [Fact]
    public void Authoritative_Backup_Is_100()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Secondary,
            selfHealthy: true, peerUaHealthy: true, peerHttpHealthy: true,
            applyInProgress: false, recoveryDwellMet: true, topologyValid: true);

        v.ShouldBe((byte)100);
    }

    // --- Isolated bands ---

    [Fact]
    public void IsolatedPrimary_PeerUnreachable_Is_230_RetainsAuthority()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Primary,
            selfHealthy: true, peerUaHealthy: false, peerHttpHealthy: true,
            applyInProgress: false, recoveryDwellMet: true, topologyValid: true);

        v.ShouldBe((byte)230);
    }

    [Fact]
    public void IsolatedBackup_PrimaryUnreachable_Is_80_DoesNotPromote()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Secondary,
            selfHealthy: true, peerUaHealthy: false, peerHttpHealthy: false,
            applyInProgress: false, recoveryDwellMet: true, topologyValid: true);

        v.ShouldBe((byte)80, "Backup isolates at 80 — doesn't auto-promote to 255");
    }

    [Fact]
    public void HttpOnly_Unreachable_TriggersIsolated()
    {
        // Either probe failing marks peer unreachable — UA probe is authoritative but HTTP is
        // the fast-fail short-circuit; either missing means "not a valid peer right now".
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Primary,
            selfHealthy: true, peerUaHealthy: true, peerHttpHealthy: false,
            applyInProgress: false, recoveryDwellMet: true, topologyValid: true);

        v.ShouldBe((byte)230);
    }

    // --- Apply-mid bands ---

    [Fact]
    public void PrimaryMidApply_Is_200()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Primary,
            selfHealthy: true, peerUaHealthy: true, peerHttpHealthy: true,
            applyInProgress: true, recoveryDwellMet: true, topologyValid: true);

        v.ShouldBe((byte)200);
    }

    [Fact]
    public void BackupMidApply_Is_50()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Secondary,
            selfHealthy: true, peerUaHealthy: true, peerHttpHealthy: true,
            applyInProgress: true, recoveryDwellMet: true, topologyValid: true);

        v.ShouldBe((byte)50);
    }

    [Fact]
    public void ApplyInProgress_Dominates_PeerUnreachable()
    {
        // Per Stream C.4 integration-test expectation: mid-apply + peer down → apply wins (200).
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Primary,
            selfHealthy: true, peerUaHealthy: false, peerHttpHealthy: false,
            applyInProgress: true, recoveryDwellMet: true, topologyValid: true);

        v.ShouldBe((byte)200);
    }

    // --- Recovering bands ---

    [Fact]
    public void RecoveringPrimary_Is_180()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Primary,
            selfHealthy: true, peerUaHealthy: true, peerHttpHealthy: true,
            applyInProgress: false, recoveryDwellMet: false, topologyValid: true);

        v.ShouldBe((byte)180);
    }

    [Fact]
    public void RecoveringBackup_Is_30()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Secondary,
            selfHealthy: true, peerUaHealthy: true, peerHttpHealthy: true,
            applyInProgress: false, recoveryDwellMet: false, topologyValid: true);

        v.ShouldBe((byte)30);
    }

    // --- Standalone node (no peer) ---

    [Fact]
    public void Standalone_IsAuthoritativePrimary_WhenHealthy()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Standalone,
            selfHealthy: true, peerUaHealthy: false, peerHttpHealthy: false,
            applyInProgress: false, recoveryDwellMet: true, topologyValid: true);

        v.ShouldBe((byte)255, "Standalone has no peer — treat healthy as authoritative");
    }

    [Fact]
    public void Standalone_MidApply_Is_200()
    {
        var v = ServiceLevelCalculator.Compute(
            RedundancyRole.Standalone,
            selfHealthy: true, peerUaHealthy: false, peerHttpHealthy: false,
            applyInProgress: true, recoveryDwellMet: true, topologyValid: true);

        v.ShouldBe((byte)200);
    }

    // --- Classify round-trip ---

    [Theory]
    [InlineData((byte)0,   ServiceLevelBand.Maintenance)]
    [InlineData((byte)1,   ServiceLevelBand.NoData)]
    [InlineData((byte)2,   ServiceLevelBand.InvalidTopology)]
    [InlineData((byte)30,  ServiceLevelBand.RecoveringBackup)]
    [InlineData((byte)50,  ServiceLevelBand.BackupMidApply)]
    [InlineData((byte)80,  ServiceLevelBand.IsolatedBackup)]
    [InlineData((byte)100, ServiceLevelBand.AuthoritativeBackup)]
    [InlineData((byte)180, ServiceLevelBand.RecoveringPrimary)]
    [InlineData((byte)200, ServiceLevelBand.PrimaryMidApply)]
    [InlineData((byte)230, ServiceLevelBand.IsolatedPrimary)]
    [InlineData((byte)255, ServiceLevelBand.AuthoritativePrimary)]
    [InlineData((byte)123, ServiceLevelBand.Unknown)]
    public void Classify_RoundTrips_EveryBand(byte value, ServiceLevelBand expected)
    {
        ServiceLevelCalculator.Classify(value).ShouldBe(expected);
    }
}
