using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS;

namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests;

/// <summary>
///     Version-matrix coverage for <see cref="FocasCapabilityMatrix"/>. Encodes the
///     documented Fanuc FOCAS Developer Kit support boundaries per CNC series so a
///     config-time change that widens or narrows a range without updating
///     <c>docs/v2/focas-version-matrix.md</c> fails a test. Every assertion cites the
///     specific matrix row it reflects.
/// </summary>
[Trait("Category", "Unit")]
public sealed class FocasCapabilityMatrixTests
{
    // ---- Macro ranges ----

    /// <summary>Verifies that macro range matches series.</summary>
    /// <param name="series">The FOCAS CNC series to validate.</param>
    /// <param name="number">The macro number to test.</param>
    /// <param name="accepted">Whether the address should be accepted.</param>
    [Theory]
    [InlineData(FocasCncSeries.Sixteen_i,     999,   true)]
    [InlineData(FocasCncSeries.Sixteen_i,     1000,  false)]  // above legacy ceiling
    [InlineData(FocasCncSeries.Zero_i_D,      999,   true)]
    [InlineData(FocasCncSeries.Zero_i_D,      9999,  false)]  // 0i-D is still legacy-ceiling
    [InlineData(FocasCncSeries.Zero_i_F,      9999,  true)]   // widened on 0i-F
    [InlineData(FocasCncSeries.Zero_i_F,      10000, false)]
    [InlineData(FocasCncSeries.Thirty_i,      99999, true)]   // highest-end
    [InlineData(FocasCncSeries.Thirty_i,      100000, false)]
    [InlineData(FocasCncSeries.PowerMotion_i, 999,   true)]
    [InlineData(FocasCncSeries.PowerMotion_i, 1000,  false)]  // atypical coverage
    public void Macro_range_matches_series(FocasCncSeries series, int number, bool accepted)
    {
        var address = new FocasAddress(FocasAreaKind.Macro, null, number, null);
        var result = FocasCapabilityMatrix.Validate(series, address);
        (result is null).ShouldBe(accepted,
            $"Macro #{number} on {series}: expected {(accepted ? "accept" : "reject")}, got {(result ?? "accept")}");
    }

    // ---- Parameter ranges ----

    /// <summary>Verifies that parameter range matches series.</summary>
    /// <param name="series">The FOCAS CNC series to validate.</param>
    /// <param name="number">The parameter number to test.</param>
    /// <param name="accepted">Whether the address should be accepted.</param>
    [Theory]
    [InlineData(FocasCncSeries.Sixteen_i, 9999,  true)]
    [InlineData(FocasCncSeries.Sixteen_i, 10000, false)]  // 16i capped at 9999
    [InlineData(FocasCncSeries.Zero_i_F,  14999, true)]
    [InlineData(FocasCncSeries.Zero_i_F,  15000, false)]
    [InlineData(FocasCncSeries.Thirty_i,  29999, true)]
    [InlineData(FocasCncSeries.Thirty_i,  30000, false)]
    public void Parameter_range_matches_series(FocasCncSeries series, int number, bool accepted)
    {
        var address = new FocasAddress(FocasAreaKind.Parameter, null, number, null);
        var result = FocasCapabilityMatrix.Validate(series, address);
        (result is null).ShouldBe(accepted);
    }

    // ---- PMC letters ----

    /// <summary>Verifies that PMC letter matches series.</summary>
    /// <param name="series">The FOCAS CNC series to validate.</param>
    /// <param name="letter">The PMC signal group letter to test.</param>
    /// <param name="accepted">Whether the address should be accepted.</param>
    [Theory]
    [InlineData(FocasCncSeries.Sixteen_i,  "X", true)]
    [InlineData(FocasCncSeries.Sixteen_i,  "Y", true)]
    [InlineData(FocasCncSeries.Sixteen_i,  "R", true)]
    [InlineData(FocasCncSeries.Sixteen_i,  "F", false)]  // 16i has no F/G signal groups
    [InlineData(FocasCncSeries.Sixteen_i,  "G", false)]
    [InlineData(FocasCncSeries.Sixteen_i,  "K", false)]
    [InlineData(FocasCncSeries.Zero_i_D,   "E", true)]   // widened since 0i-D
    [InlineData(FocasCncSeries.Zero_i_D,   "F", false)]  // still no F on 0i-D
    [InlineData(FocasCncSeries.Zero_i_F,   "F", true)]   // F/G added on 0i-F
    [InlineData(FocasCncSeries.Zero_i_F,   "K", false)]  // K/T still 30i-only
    [InlineData(FocasCncSeries.Thirty_i,   "K", true)]
    [InlineData(FocasCncSeries.Thirty_i,   "T", true)]
    [InlineData(FocasCncSeries.Thirty_i,   "Q", false)]  // unsupported even on 30i
    public void Pmc_letter_matches_series(FocasCncSeries series, string letter, bool accepted)
    {
        var address = new FocasAddress(FocasAreaKind.Pmc, letter, 0, null);
        var result = FocasCapabilityMatrix.Validate(series, address);
        (result is null).ShouldBe(accepted,
            $"PMC letter '{letter}' on {series}: expected {(accepted ? "accept" : "reject")}, got {(result ?? "accept")}");
    }

    // ---- PMC number ceiling ----

    /// <summary>Verifies that PMC number ceiling matches series.</summary>
    /// <param name="series">The FOCAS CNC series to validate.</param>
    /// <param name="letter">The PMC signal group letter to test.</param>
    /// <param name="number">The PMC address number to test.</param>
    /// <param name="accepted">Whether the address should be accepted.</param>
    [Theory]
    [InlineData(FocasCncSeries.Sixteen_i,  "R", 999,  true)]
    [InlineData(FocasCncSeries.Sixteen_i,  "R", 1000, false)]
    [InlineData(FocasCncSeries.Zero_i_D,   "R", 1999, true)]
    [InlineData(FocasCncSeries.Zero_i_D,   "R", 2000, false)]
    [InlineData(FocasCncSeries.Zero_i_F,   "R", 9999, true)]
    [InlineData(FocasCncSeries.Zero_i_F,   "R", 10000, false)]
    [InlineData(FocasCncSeries.Thirty_i,   "R", 59999, true)]
    [InlineData(FocasCncSeries.Thirty_i,   "R", 60000, false)]
    public void Pmc_number_ceiling_matches_series(FocasCncSeries series, string letter, int number, bool accepted)
    {
        var address = new FocasAddress(FocasAreaKind.Pmc, letter, number, null);
        var result = FocasCapabilityMatrix.Validate(series, address);
        (result is null).ShouldBe(accepted);
    }

    // ---- Unknown series is permissive ----

    /// <summary>Verifies that unknown series accepts any PMC.</summary>
    /// <param name="letter">The PMC signal group letter to test.</param>
    /// <param name="number">The PMC address number to test.</param>
    [Theory]
    [InlineData("Z", 999_999)]   // absurd PMC address
    [InlineData("Q", 0)]         // non-existent letter
    public void Unknown_series_accepts_any_PMC(string letter, int number)
    {
        var address = new FocasAddress(FocasAreaKind.Pmc, letter, number, null);
        FocasCapabilityMatrix.Validate(FocasCncSeries.Unknown, address).ShouldBeNull();
    }

    /// <summary>Verifies that unknown series accepts any macro number.</summary>
    [Fact]
    public void Unknown_series_accepts_any_macro_number()
    {
        var address = new FocasAddress(FocasAreaKind.Macro, null, 999_999, null);
        FocasCapabilityMatrix.Validate(FocasCncSeries.Unknown, address).ShouldBeNull();
    }

    /// <summary>Verifies that unknown series accepts any parameter number.</summary>
    [Fact]
    public void Unknown_series_accepts_any_parameter_number()
    {
        var address = new FocasAddress(FocasAreaKind.Parameter, null, 999_999, null);
        FocasCapabilityMatrix.Validate(FocasCncSeries.Unknown, address).ShouldBeNull();
    }

    // ---- Reason messages include enough context to diagnose ----

    /// <summary>Verifies that rejection message names series and limit.</summary>
    [Fact]
    public void Rejection_message_names_series_and_limit()
    {
        var address = new FocasAddress(FocasAreaKind.Macro, null, 100_000, null);
        var reason = FocasCapabilityMatrix.Validate(FocasCncSeries.Zero_i_F, address);
        reason.ShouldNotBeNull();
        reason.ShouldContain("100000");
        reason.ShouldContain("Zero_i_F");
        reason.ShouldContain("9999");
    }

    /// <summary>Verifies that PMC rejection lists accepted letters.</summary>
    [Fact]
    public void Pmc_rejection_lists_accepted_letters()
    {
        var address = new FocasAddress(FocasAreaKind.Pmc, "Q", 0, null);
        var reason = FocasCapabilityMatrix.Validate(FocasCncSeries.Thirty_i, address);
        reason.ShouldNotBeNull();
        reason.ShouldContain("'Q'");
        reason.ShouldContain("X");  // some accepted letter should appear
        reason.ShouldContain("Y");
    }

    // ---- PMC address letter is case-insensitive ----

    /// <summary>Verifies that PMC letter match is case insensitive on 30i.</summary>
    /// <param name="letter">The PMC signal group letter to test in various cases.</param>
    [Theory]
    [InlineData("x")]
    [InlineData("X")]
    [InlineData("f")]
    public void Pmc_letter_match_is_case_insensitive_on_30i(string letter)
    {
        var address = new FocasAddress(FocasAreaKind.Pmc, letter, 0, null);
        FocasCapabilityMatrix.Validate(FocasCncSeries.Thirty_i, address).ShouldBeNull();
    }
}
