using Shouldly;
using Xunit;

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

/// <summary>
///     Driver.S7.Cli-004: every S7 CLI command must own one disposal mechanism for the
///     <c>S7Driver</c>, not two. The chosen mechanism is <c>await using var driver = ...</c>
///     — <c>S7Driver.DisposeAsync</c> already calls <c>ShutdownAsync</c>, so an additional
///     explicit <c>driver.ShutdownAsync(...)</c> in a <c>finally</c> block runs shutdown
///     twice (three times on subscribe). These tests guard against that regression by
///     scanning the command source files.
/// </summary>
[Trait("Category", "Unit")]
public sealed class CommandDisposalConventionsTests
{
    private static readonly string CommandsDir = LocateCommandsDir();

    /// <summary>Verifies that commands do not call ShutdownAsync explicitly.</summary>
    /// <param name="commandFile">The source file name of the command to inspect.</param>
    [Theory]
    [InlineData("ProbeCommand.cs")]
    [InlineData("ReadCommand.cs")]
    [InlineData("WriteCommand.cs")]
    [InlineData("SubscribeCommand.cs")]
    public void Command_does_not_call_ShutdownAsync_explicitly(string commandFile)
    {
        var path = Path.Combine(CommandsDir, commandFile);
        File.Exists(path).ShouldBeTrue($"Expected {path} to exist.");
        var source = File.ReadAllText(path);

        // The await-using statement is the single disposal mechanism. An explicit
        // driver.ShutdownAsync(...) call (typically inside a finally block) re-invokes
        // a shutdown path that DisposeAsync already runs and is the smell -004 flags.
        source.ShouldNotContain("driver.ShutdownAsync(");
    }

    /// <summary>Verifies that commands use await using for S7Driver disposal.</summary>
    /// <param name="commandFile">The source file name of the command to inspect.</param>
    [Theory]
    [InlineData("ProbeCommand.cs")]
    [InlineData("ReadCommand.cs")]
    [InlineData("WriteCommand.cs")]
    [InlineData("SubscribeCommand.cs")]
    public void Command_uses_await_using_for_S7Driver(string commandFile)
    {
        var path = Path.Combine(CommandsDir, commandFile);
        var source = File.ReadAllText(path);

        source.ShouldContain("await using var driver = new S7Driver(");
    }

    private static string LocateCommandsDir()
    {
        // Walk up from the test assembly bin/ folder to the repo root, then into the
        // source project's Commands/ directory. The test-host puts CWD somewhere under
        // bin/Debug/net10.0 so we resolve relative to AppContext.BaseDirectory.
        var dir = new DirectoryInfo(AppContext.BaseDirectory);
        while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "ZB.MOM.WW.OtOpcUa.slnx")))
            dir = dir.Parent;
        dir.ShouldNotBeNull("Could not find solution root (ZB.MOM.WW.OtOpcUa.slnx).");
        return Path.Combine(
            dir!.FullName, "src", "Drivers", "Cli", "ZB.MOM.WW.OtOpcUa.Driver.S7.Cli", "Commands");
    }
}
