using Microsoft.EntityFrameworkCore;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;

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

/// <summary>
/// Verifies <see cref="UnsTreeService.DeleteClusterAsync"/> and
/// <see cref="UnsTreeService.DeleteEnterpriseAsync"/>: refuse-if-children deletes that
/// mirror the Area/Line/Equipment convention but carry no <c>RowVersion</c> concurrency
/// token (the <see cref="ServerCluster"/> entity has none — Option B, no migration).
/// </summary>
/// <remarks>
/// The EF InMemory provider does not enforce FK <c>Restrict</c>, so the refuse-if-children
/// behaviour is driven by an explicit child pre-check in the service (not the DB throwing),
/// which is what these tests exercise.
/// </remarks>
[Trait("Category", "Unit")]
public sealed class UnsTreeServiceDeleteClusterTests
{
    private static (UnsTreeService Service, string DbName) Fresh()
    {
        var dbName = $"uns-delcluster-{Guid.NewGuid():N}";
        return (new UnsTreeService(UnsTreeTestDb.Factory(dbName)), dbName);
    }

    private static ServerCluster NewCluster(string clusterId, string enterprise) => new()
    {
        ClusterId = clusterId,
        Name = clusterId,
        Enterprise = enterprise,
        Site = "site",
        RedundancyMode = RedundancyMode.None,
        CreatedBy = "test",
    };

    // ----- DeleteCluster -----

    /// <summary>Deleting a child-free cluster removes the row.</summary>
    [Fact]
    public async Task DeleteCluster_empty_removes_row()
    {
        var (service, dbName) = Fresh();
        using (var db = UnsTreeTestDb.CreateNamed(dbName))
        {
            db.ServerClusters.Add(NewCluster("CL-EMPTY", "zb"));
            db.SaveChanges();
        }

        var result = await service.DeleteClusterAsync("CL-EMPTY");

        result.Ok.ShouldBeTrue();
        result.Error.ShouldBeNull();

        using var verify = UnsTreeTestDb.CreateNamed(dbName);
        verify.ServerClusters.Any(c => c.ClusterId == "CL-EMPTY").ShouldBeFalse();
    }

    /// <summary>Deleting a cluster that is already gone is a no-op success (idempotent).</summary>
    [Fact]
    public async Task DeleteCluster_already_gone_returns_ok()
    {
        var (service, _) = Fresh();

        var result = await service.DeleteClusterAsync("GHOST-CLUSTER");

        result.Ok.ShouldBeTrue();
        result.Error.ShouldBeNull();
    }

    /// <summary>A cluster with a UNS area refuses deletion with a friendly message and stays put.</summary>
    [Fact]
    public async Task DeleteCluster_with_area_refuses_and_keeps_row()
    {
        var (service, dbName) = Fresh();
        using (var db = UnsTreeTestDb.CreateNamed(dbName))
        {
            db.ServerClusters.Add(NewCluster("CL-AREA", "zb"));
            db.UnsAreas.Add(new UnsArea { UnsAreaId = "AREA-CL", ClusterId = "CL-AREA", Name = "a" });
            db.SaveChanges();
        }

        var result = await service.DeleteClusterAsync("CL-AREA");

        result.Ok.ShouldBeFalse();
        result.Error.ShouldNotBeNull();
        result.Error.ShouldContain("CL-AREA");

        using var verify = UnsTreeTestDb.CreateNamed(dbName);
        verify.ServerClusters.Any(c => c.ClusterId == "CL-AREA").ShouldBeTrue();
    }

    /// <summary>A cluster with a driver instance refuses deletion and stays put.</summary>
    [Fact]
    public async Task DeleteCluster_with_driver_refuses_and_keeps_row()
    {
        var (service, dbName) = Fresh();
        using (var db = UnsTreeTestDb.CreateNamed(dbName))
        {
            db.ServerClusters.Add(NewCluster("CL-DRV", "zb"));
            db.DriverInstances.Add(new DriverInstance
            {
                DriverInstanceId = "DRV-CL",
                ClusterId = "CL-DRV",
                Name = "drv",
                DriverType = "Modbus",
                DriverConfig = "{}",
            });
            db.SaveChanges();
        }

        var result = await service.DeleteClusterAsync("CL-DRV");

        result.Ok.ShouldBeFalse();
        result.Error.ShouldNotBeNull();

        using var verify = UnsTreeTestDb.CreateNamed(dbName);
        verify.ServerClusters.Any(c => c.ClusterId == "CL-DRV").ShouldBeTrue();
    }

    /// <summary>A cluster with a cluster node refuses deletion with a friendly message and stays put.</summary>
    [Fact]
    public async Task DeleteCluster_with_cluster_node_refuses_and_keeps_row()
    {
        var (service, dbName) = Fresh();
        using (var db = UnsTreeTestDb.CreateNamed(dbName))
        {
            db.ServerClusters.Add(NewCluster("CL-NODE", "zb"));
            db.ClusterNodes.Add(new ClusterNode
            {
                NodeId = "NODE-CL",
                ClusterId = "CL-NODE",
                Host = "host-a",
                ApplicationUri = "urn:zb:cl-node:a",
                CreatedBy = "test",
            });
            db.SaveChanges();
        }

        var result = await service.DeleteClusterAsync("CL-NODE");

        result.Ok.ShouldBeFalse();
        result.Error.ShouldNotBeNull();
        result.Error.ShouldContain("CL-NODE");

        using var verify = UnsTreeTestDb.CreateNamed(dbName);
        verify.ServerClusters.Any(c => c.ClusterId == "CL-NODE").ShouldBeTrue();
    }

    // v3 Batch-1: the Namespace entity was deleted (Raw/UNS split), so DescribeClusterChildrenAsync
    // no longer carries a namespace child-check. The former DeleteCluster_with_namespace_refuses test
    // was removed. The cluster-nodes / UNS-areas / driver-instances refuse-if-children checks (above)
    // remain the enforced set.

    // ----- DeleteEnterprise -----

    /// <summary>Deleting an enterprise whose every cluster is child-free removes them all.</summary>
    [Fact]
    public async Task DeleteEnterprise_all_empty_removes_all_clusters()
    {
        var (service, dbName) = Fresh();
        using (var db = UnsTreeTestDb.CreateNamed(dbName))
        {
            db.ServerClusters.Add(NewCluster("ENT1-A", "ent-1"));
            db.ServerClusters.Add(NewCluster("ENT1-B", "ent-1"));
            // A cluster under a different enterprise must survive.
            db.ServerClusters.Add(NewCluster("ENT2-A", "ent-2"));
            db.SaveChanges();
        }

        var result = await service.DeleteEnterpriseAsync("ent-1");

        result.Ok.ShouldBeTrue();
        result.Error.ShouldBeNull();

        using var verify = UnsTreeTestDb.CreateNamed(dbName);
        verify.ServerClusters.Any(c => c.Enterprise == "ent-1").ShouldBeFalse();
        verify.ServerClusters.Any(c => c.ClusterId == "ENT2-A").ShouldBeTrue();
    }

    /// <summary>Deleting an enterprise with no matching clusters is a no-op success.</summary>
    [Fact]
    public async Task DeleteEnterprise_no_matches_returns_ok()
    {
        var (service, _) = Fresh();

        var result = await service.DeleteEnterpriseAsync("nonexistent-enterprise");

        result.Ok.ShouldBeTrue();
        result.Error.ShouldBeNull();
    }

    /// <summary>
    /// When one cluster under the enterprise has children, the whole delete refuses (naming the
    /// blocking cluster) and nothing is deleted — no partial enterprise removal.
    /// </summary>
    [Fact]
    public async Task DeleteEnterprise_one_cluster_with_children_refuses_and_deletes_nothing()
    {
        var (service, dbName) = Fresh();
        using (var db = UnsTreeTestDb.CreateNamed(dbName))
        {
            db.ServerClusters.Add(NewCluster("ENT3-EMPTY", "ent-3"));
            db.ServerClusters.Add(NewCluster("ENT3-FULL", "ent-3"));
            db.UnsAreas.Add(new UnsArea { UnsAreaId = "AREA-ENT3", ClusterId = "ENT3-FULL", Name = "a" });
            db.SaveChanges();
        }

        var result = await service.DeleteEnterpriseAsync("ent-3");

        result.Ok.ShouldBeFalse();
        result.Error.ShouldNotBeNull();
        result.Error.ShouldContain("ENT3-FULL");

        // Nothing partial: BOTH clusters must still be present.
        using var verify = UnsTreeTestDb.CreateNamed(dbName);
        verify.ServerClusters.Any(c => c.ClusterId == "ENT3-EMPTY").ShouldBeTrue();
        verify.ServerClusters.Any(c => c.ClusterId == "ENT3-FULL").ShouldBeTrue();
    }
}
