using Mbproxy.Configuration;
using Mbproxy.Options;
using Xunit;

namespace Mbproxy.Tests.Configuration;

/// <summary>
/// Unit tests for <see cref="ReloadValidator.Validate"/>.
/// Each test covers one specific failure mode or the happy path.
/// </summary>
[Trait("Category", "Unit")]
public sealed class ReloadValidatorTests
{
    // ── Helpers ───────────────────────────────────────────────────────────────────────────

    private static PlcOptions MakePlc(string name, int listenPort, string host = "127.0.0.1")
        => new() { Name = name, ListenPort = listenPort, Host = host, Port = 502 };

    private static MbproxyOptions MakeOptions(
        PlcOptions[] plcs,
        int adminPort = 8080,
        BcdTagListOptions? global = null)
        => new()
        {
            Plcs      = plcs,
            AdminPort = adminPort,
            BcdTags   = global ?? new BcdTagListOptions(),
        };

    // ── 1. Duplicate PLC name → fails ────────────────────────────────────────────────────

    [Fact]
    public void Validate_DuplicatePlcName_Fails()
    {
        var opts = MakeOptions([
            MakePlc("PLC-A", 5020),
            MakePlc("PLC-A", 5021),  // same name
        ]);

        bool valid = ReloadValidator.Validate(opts, out var errors);

        Assert.False(valid);
        Assert.Contains(errors, e => e.Contains("PLC-A") && e.Contains("uplicate"));
    }

    // ── 2. Duplicate ListenPort → fails ──────────────────────────────────────────────────

    [Fact]
    public void Validate_DuplicateListenPort_Fails()
    {
        var opts = MakeOptions([
            MakePlc("PLC-A", 5020),
            MakePlc("PLC-B", 5020),  // same port
        ]);

        bool valid = ReloadValidator.Validate(opts, out var errors);

        Assert.False(valid);
        Assert.Contains(errors, e => e.Contains("5020") && e.Contains("uplicate"));
    }

    // ── 3. AdminPort collides with a PLC's ListenPort → fails ────────────────────────────

    [Fact]
    public void Validate_AdminPortCollidesWith_PlcListenPort_Fails()
    {
        var opts = MakeOptions(
            plcs: [MakePlc("PLC-A", 5020)],
            adminPort: 5020);  // collides with PLC-A

        bool valid = ReloadValidator.Validate(opts, out var errors);

        Assert.False(valid);
        Assert.Contains(errors, e => e.Contains("AdminPort") && e.Contains("5020"));
    }

    // ── 4. Per-PLC BCD map build error → fails ────────────────────────────────────────────

    [Fact]
    public void Validate_PerPlc_BcdMapBuildError_Fails()
    {
        // A 32-bit tag at address 100 and a 16-bit tag at 101 collide on high register.
        var global = new BcdTagListOptions
        {
            Global =
            [
                new BcdTagOptions { Address = 100, Width = 32 },
                new BcdTagOptions { Address = 101, Width = 16 },  // overlaps 100's high register
            ],
        };
        var opts = MakeOptions([MakePlc("PLC-A", 5020)], global: global);

        bool valid = ReloadValidator.Validate(opts, out var errors);

        Assert.False(valid);
        Assert.Contains(errors, e => e.Contains("PLC-A"));
    }

    // ── 5. Port out of range → fails ─────────────────────────────────────────────────────

    [Fact]
    public void Validate_PortOutOfRange_Fails()
    {
        // ListenPort 0 is below the valid [1, 65535] range.
        var opts = MakeOptions([MakePlc("PLC-A", 0)]);

        bool valid = ReloadValidator.Validate(opts, out var errors);

        Assert.False(valid);
        Assert.Contains(errors, e => e.Contains("0") && e.Contains("range"));
    }

    // ── 5b. AdminPort out of range → fails ───────────────────────────────────────────────

    [Fact]
    public void Validate_AdminPortOutOfRange_Fails()
    {
        var opts = MakeOptions([MakePlc("PLC-A", 5020)], adminPort: 70000);

        bool valid = ReloadValidator.Validate(opts, out var errors);

        Assert.False(valid);
        Assert.Contains(errors, e => e.Contains("70000") && e.Contains("range"));
    }

    // ── 6. Happy path → passes ───────────────────────────────────────────────────────────

    [Fact]
    public void Validate_HappyPath_Passes()
    {
        var global = new BcdTagListOptions
        {
            Global = [new BcdTagOptions { Address = 1072, Width = 16 }],
        };
        var opts = MakeOptions(
            plcs: [MakePlc("PLC-A", 5020), MakePlc("PLC-B", 5021)],
            adminPort: 8080,
            global: global);

        bool valid = ReloadValidator.Validate(opts, out var errors);

        Assert.True(valid);
        Assert.Empty(errors);
    }

    // ── 7. Empty PLC name → fails ────────────────────────────────────────────────────────

    [Fact]
    public void Validate_EmptyPlcName_Fails()
    {
        var opts = MakeOptions([MakePlc("", 5020)]);

        bool valid = ReloadValidator.Validate(opts, out var errors);

        Assert.False(valid);
        Assert.Contains(errors, e => e.Contains("non-empty"));
    }
}
