using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;

/// <summary>
/// Verifies <see cref="UnsTreeService.CreateScriptAsync"/> — the create-new-script path the inline
/// virtual-tag panel calls when no script is bound yet. A new <c>Script</c> row is inserted with a
/// system-generated <c>SC-…</c> id (mirroring the Script-edit page convention), a blank source, a
/// hash matching that blank source, and the <c>CSharp</c> language; the new id is returned via
/// <see cref="UnsMutationResult.CreatedId"/> so the panel can bind the virtual tag to it. Two calls
/// produce two distinct rows with distinct ids.
/// </summary>
[Trait("Category", "Unit")]
public sealed class UnsTreeServiceCreateScriptTests
{
    private static (UnsTreeService Service, string DbName) Fresh()
    {
        var dbName = $"uns-createscript-{Guid.NewGuid():N}";
        return (new UnsTreeService(UnsTreeTestDb.Factory(dbName)), dbName);
    }

    private static string HashOf(string source) =>
        Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(source)));

    /// <summary>
    /// A create inserts exactly one Script row carrying a non-empty generated id, the expected blank
    /// source + matching hash, the CSharp language, and a name; the generated id is echoed back.
    /// </summary>
    [Fact]
    public async Task CreateScript_inserts_one_row_with_generated_id_and_blank_source()
    {
        var (service, dbName) = Fresh();

        var result = await service.CreateScriptAsync("my new script");

        result.Ok.ShouldBeTrue();
        result.Error.ShouldBeNull();
        result.CreatedId.ShouldNotBeNullOrWhiteSpace();
        result.CreatedId!.ShouldStartWith("SC-");

        using var db = UnsTreeTestDb.CreateNamed(dbName);
        db.Scripts.Count().ShouldBe(1);
        var script = db.Scripts.Single();
        script.ScriptId.ShouldBe(result.CreatedId);
        script.Name.ShouldBe("my new script");
        script.SourceCode.ShouldBe("");
        script.SourceHash.ShouldBe(HashOf(""));
        script.Language.ShouldBe("CSharp");
    }

    /// <summary>A whitespace/empty name falls back to a sensible non-empty default.</summary>
    [Fact]
    public async Task CreateScript_blank_name_uses_a_default_name()
    {
        var (service, dbName) = Fresh();

        var result = await service.CreateScriptAsync("   ");

        result.Ok.ShouldBeTrue();
        using var db = UnsTreeTestDb.CreateNamed(dbName);
        db.Scripts.Single().Name.ShouldNotBeNullOrWhiteSpace();
    }

    /// <summary>Two creates insert two rows with distinct, non-empty ids.</summary>
    [Fact]
    public async Task CreateScript_twice_produces_distinct_ids()
    {
        var (service, dbName) = Fresh();

        var first = await service.CreateScriptAsync("first");
        var second = await service.CreateScriptAsync("second");

        first.Ok.ShouldBeTrue();
        second.Ok.ShouldBeTrue();
        first.CreatedId.ShouldNotBe(second.CreatedId);

        using var db = UnsTreeTestDb.CreateNamed(dbName);
        db.Scripts.Count().ShouldBe(2);
        db.Scripts.Select(s => s.ScriptId).Distinct().Count().ShouldBe(2);
    }
}
