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

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

/// <summary>
///     Covers <see cref="WriteCommand.ParseValue"/> across every FOCAS atomic type.
///     Driver.FOCAS.Cli-001: malformed numeric input must surface as a friendly
///     <see cref="CliFx.Exceptions.CommandException"/>, not a raw
///     <see cref="FormatException"/> / <see cref="OverflowException"/> stack trace.
/// </summary>
[Trait("Category", "Unit")]
public sealed class WriteCommandParseValueTests
{
    /// <summary>Verifies that ParseValue accepts common boolean aliases for Bit type.</summary>
    /// <param name="raw">The raw string input to parse.</param>
    /// <param name="expected">The expected boolean result.</param>
    [Theory]
    [InlineData("true", true)]
    [InlineData("0", false)]
    [InlineData("yes", true)]
    [InlineData("OFF", false)]
    public void ParseValue_Bit_accepts_common_boolean_aliases(string raw, bool expected)
    {
        WriteCommand.ParseValue(raw, FocasDataType.Bit).ShouldBe(expected);
    }

    /// <summary>Verifies that ParseValue rejects garbage Bit input as CommandException.</summary>
    [Fact]
    public void ParseValue_Bit_rejects_garbage_as_CommandException()
    {
        Should.Throw<CliFx.Exceptions.CommandException>(
            () => WriteCommand.ParseValue("maybe", FocasDataType.Bit));
    }

    /// <summary>Verifies that ParseValue accepts signed byte range.</summary>
    [Fact]
    public void ParseValue_Byte_signed_range()
    {
        // FocasDataType.Byte is signed (PMC byte read returns int8).
        WriteCommand.ParseValue("-128", FocasDataType.Byte).ShouldBe((sbyte)-128);
        WriteCommand.ParseValue("127", FocasDataType.Byte).ShouldBe((sbyte)127);
    }

    /// <summary>Verifies that ParseValue accepts signed 16-bit range.</summary>
    [Fact]
    public void ParseValue_Int16_signed_range()
    {
        WriteCommand.ParseValue("-32768", FocasDataType.Int16).ShouldBe(short.MinValue);
        WriteCommand.ParseValue("32767", FocasDataType.Int16).ShouldBe(short.MaxValue);
    }

    /// <summary>Verifies that ParseValue parses negative Int32 values.</summary>
    [Fact]
    public void ParseValue_Int32_parses_negative()
    {
        WriteCommand.ParseValue("-2147483648", FocasDataType.Int32).ShouldBe(int.MinValue);
    }

    /// <summary>Verifies that ParseValue parses Float32 in invariant culture.</summary>
    [Fact]
    public void ParseValue_Float32_invariant_culture()
    {
        WriteCommand.ParseValue("3.14", FocasDataType.Float32).ShouldBe(3.14f);
    }

    /// <summary>Verifies that ParseValue preserves Float64 higher precision.</summary>
    [Fact]
    public void ParseValue_Float64_higher_precision()
    {
        WriteCommand.ParseValue("2.718281828", FocasDataType.Float64).ShouldBeOfType<double>();
    }

    /// <summary>Verifies that ParseValue passes through string values unchanged.</summary>
    [Fact]
    public void ParseValue_String_passthrough()
    {
        WriteCommand.ParseValue("hello fanuc", FocasDataType.String).ShouldBe("hello fanuc");
    }

    // Driver.FOCAS.Cli-001: malformed input must produce a CommandException (a clean
    // one-line CliFx error), NOT a raw FormatException stack trace. Previously the raw
    // BCL parser exceptions leaked, contradicting how the Bit path already handled bad
    // boolean input.
    /// <summary>Verifies that ParseValue throws CommandException for non-numeric input to numeric types.</summary>
    /// <param name="raw">The non-numeric raw input string.</param>
    /// <param name="type">The FOCAS data type to attempt parsing into.</param>
    [Theory]
    [InlineData("xyz", FocasDataType.Byte)]
    [InlineData("xyz", FocasDataType.Int16)]
    [InlineData("xyz", FocasDataType.Int32)]
    [InlineData("not-a-number", FocasDataType.Float32)]
    [InlineData("also-bad", FocasDataType.Float64)]
    public void ParseValue_non_numeric_for_numeric_types_throws_CommandException(
        string raw, FocasDataType type)
    {
        Should.Throw<CliFx.Exceptions.CommandException>(
            () => WriteCommand.ParseValue(raw, type));
    }

    // OverflowException from out-of-range input must also surface as CommandException.
    /// <summary>Verifies that ParseValue throws CommandException for overflow in numeric types.</summary>
    /// <param name="raw">The out-of-range raw input string.</param>
    /// <param name="type">The FOCAS data type whose range is exceeded.</param>
    [Theory]
    [InlineData("128", FocasDataType.Byte)]        // sbyte max + 1
    [InlineData("-129", FocasDataType.Byte)]       // sbyte min - 1
    [InlineData("32768", FocasDataType.Int16)]     // short max + 1
    [InlineData("9999999999", FocasDataType.Int32)] // > int max
    public void ParseValue_overflow_for_numeric_types_throws_CommandException(
        string raw, FocasDataType type)
    {
        Should.Throw<CliFx.Exceptions.CommandException>(
            () => WriteCommand.ParseValue(raw, type));
    }

    /// <summary>Verifies that ParseValue CommandException message names the type and value.</summary>
    [Fact]
    public void ParseValue_CommandException_message_names_the_type_and_value()
    {
        var ex = Should.Throw<CliFx.Exceptions.CommandException>(
            () => WriteCommand.ParseValue("xyz", FocasDataType.Int16));
        ex.Message.ShouldContain("xyz");
        ex.Message.ShouldContain("Int16");
    }

    /// <summary>Verifies that SynthesiseTagName preserves FOCAS address verbatim.</summary>
    /// <param name="address">The FOCAS address string.</param>
    /// <param name="type">The FOCAS data type appended to the tag name.</param>
    /// <param name="expected">The expected synthesised tag name.</param>
    [Theory]
    [InlineData("R100", FocasDataType.Int16, "R100:Int16")]
    [InlineData("X0.0", FocasDataType.Bit, "X0.0:Bit")]
    [InlineData("PARAM:1815/0", FocasDataType.Int32, "PARAM:1815/0:Int32")]
    [InlineData("MACRO:500", FocasDataType.Float64, "MACRO:500:Float64")]
    public void SynthesiseTagName_preserves_FOCAS_address_verbatim(
        string address, FocasDataType type, string expected)
    {
        ReadCommand.SynthesiseTagName(address, type).ShouldBe(expected);
    }
}
