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

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

/// <summary>
///     Covers <see cref="WriteCommand.ParseValue"/> across every S7 atomic type.
/// </summary>
[Trait("Category", "Unit")]
public sealed class WriteCommandParseValueTests
{
    /// <summary>Verifies ParseValue accepts common boolean aliases.</summary>
    /// <param name="raw">The raw string value 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_Bool_accepts_common_aliases(string raw, bool expected)
    {
        WriteCommand.ParseValue(raw, S7DataType.Bool).ShouldBe(expected);
    }

    /// <summary>Verifies ParseValue rejects invalid boolean values.</summary>
    [Fact]
    public void ParseValue_Bool_rejects_garbage()
    {
        Should.Throw<CliFx.Exceptions.CommandException>(
            () => WriteCommand.ParseValue("maybe", S7DataType.Bool));
    }

    /// <summary>Verifies ParseValue handles the full byte range.</summary>
    [Fact]
    public void ParseValue_Byte_ranges()
    {
        WriteCommand.ParseValue("0", S7DataType.Byte).ShouldBe((byte)0);
        WriteCommand.ParseValue("255", S7DataType.Byte).ShouldBe((byte)255);
    }

    /// <summary>Verifies ParseValue handles signed 16-bit integers.</summary>
    [Fact]
    public void ParseValue_Int16_signed_range()
    {
        WriteCommand.ParseValue("-32768", S7DataType.Int16).ShouldBe((short)-32768);
    }

    /// <summary>Verifies ParseValue handles unsigned 16-bit integer maximum.</summary>
    [Fact]
    public void ParseValue_UInt16_unsigned_max()
    {
        WriteCommand.ParseValue("65535", S7DataType.UInt16).ShouldBe((ushort)65535);
    }

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

    /// <summary>Verifies ParseValue parses unsigned 32-bit integer maximum.</summary>
    [Fact]
    public void ParseValue_UInt32_parses_max()
    {
        WriteCommand.ParseValue("4294967295", S7DataType.UInt32).ShouldBe(uint.MaxValue);
    }

    /// <summary>Verifies ParseValue parses signed 64-bit integer minimum.</summary>
    [Fact]
    public void ParseValue_Int64_parses_min()
    {
        WriteCommand.ParseValue("-9223372036854775808", S7DataType.Int64).ShouldBe(long.MinValue);
    }

    /// <summary>Verifies ParseValue parses unsigned 64-bit integer maximum.</summary>
    [Fact]
    public void ParseValue_UInt64_parses_max()
    {
        WriteCommand.ParseValue("18446744073709551615", S7DataType.UInt64).ShouldBe(ulong.MaxValue);
    }

    /// <summary>Verifies ParseValue parses 32-bit floats using invariant culture.</summary>
    [Fact]
    public void ParseValue_Float32_invariant_culture()
    {
        WriteCommand.ParseValue("3.14", S7DataType.Float32).ShouldBe(3.14f);
    }

    /// <summary>Verifies ParseValue parses 64-bit floats with higher precision.</summary>
    [Fact]
    public void ParseValue_Float64_higher_precision()
    {
        WriteCommand.ParseValue("2.718281828", S7DataType.Float64).ShouldBeOfType<double>();
    }

    /// <summary>Verifies ParseValue passes through strings unchanged.</summary>
    [Fact]
    public void ParseValue_String_passthrough()
    {
        WriteCommand.ParseValue("hallo siemens", S7DataType.String).ShouldBe("hallo siemens");
    }

    /// <summary>Verifies ParseValue parses DateTime in roundtrip format.</summary>
    [Fact]
    public void ParseValue_DateTime_parses_roundtrip_form()
    {
        var result = WriteCommand.ParseValue("2026-04-21T12:34:56Z", S7DataType.DateTime);
        result.ShouldBeOfType<DateTime>();
        ((DateTime)result).Year.ShouldBe(2026);
    }

    /// <summary>Verifies ParseValue rejects non-numeric input for numeric types.</summary>
    [Fact]
    public void ParseValue_non_numeric_for_numeric_types_throws()
    {
        // Driver.S7.Cli-001: malformed input must produce a clean CommandException
        // (friendly one-line error), not a raw FormatException stack trace.
        Should.Throw<CliFx.Exceptions.CommandException>(
            () => WriteCommand.ParseValue("xyz", S7DataType.Int16));
    }

    /// <summary>Verifies ParseValue throws CommandException on numeric overflow.</summary>
    [Fact]
    public void ParseValue_overflow_for_numeric_types_throws_CommandException()
    {
        // OverflowException from out-of-range input must also surface as CommandException.
        Should.Throw<CliFx.Exceptions.CommandException>(
            () => WriteCommand.ParseValue("256", S7DataType.Byte));
    }

    /// <summary>Verifies SynthesiseTagName preserves S7 address verbatim.</summary>
    /// <param name="address">The S7 address string to test.</param>
    /// <param name="type">The S7 data type.</param>
    /// <param name="expected">The expected synthesized tag name.</param>
    [Theory]
    [InlineData("DB1.DBW0", S7DataType.Int16, "DB1.DBW0:Int16")]
    [InlineData("M0.0", S7DataType.Bool, "M0.0:Bool")]
    [InlineData("IW4", S7DataType.UInt16, "IW4:UInt16")]
    [InlineData("QD8", S7DataType.UInt32, "QD8:UInt32")]
    [InlineData("DB10.STRING[0]", S7DataType.String, "DB10.STRING[0]:String")]
    public void SynthesiseTagName_preserves_S7_address_verbatim(
        string address, S7DataType type, string expected)
    {
        ReadCommand.SynthesiseTagName(address, type).ShouldBe(expected);
    }
}
