using Moq;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.TemplateEngine.Services;

namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests.Services;

public class TemplateDeletionServiceTests
{
    private readonly Mock<ITemplateEngineRepository> _repoMock = new();
    private readonly TemplateDeletionService _sut;

    public TemplateDeletionServiceTests()
    {
        _sut = new TemplateDeletionService(_repoMock.Object);
    }

    [Fact]
    public async Task CanDeleteTemplate_NoReferences_ReturnsSuccess()
    {
        _repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new Template("Orphan") { Id = 1 });
        _repoMock.Setup(r => r.GetInstancesByTemplateIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Instance>());
        _repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Template> { new("Orphan") { Id = 1 } });
        _repoMock.Setup(r => r.GetCompositionsByTemplateIdAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateComposition>());

        var result = await _sut.CanDeleteTemplateAsync(1);

        Assert.True(result.IsSuccess);
    }

    [Fact]
    public async Task CanDeleteTemplate_WithInstances_ReturnsFailure()
    {
        _repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new Template("Used") { Id = 1 });
        _repoMock.Setup(r => r.GetInstancesByTemplateIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Instance>
            {
                new("Inst1") { Id = 1 },
                new("Inst2") { Id = 2 }
            });
        _repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Template> { new("Used") { Id = 1 } });
        _repoMock.Setup(r => r.GetCompositionsByTemplateIdAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateComposition>());

        var result = await _sut.CanDeleteTemplateAsync(1);

        Assert.True(result.IsFailure);
        Assert.Contains("2 instance(s)", result.Error);
        Assert.Contains("Inst1", result.Error);
    }

    [Fact]
    public async Task CanDeleteTemplate_WithChildTemplates_ReturnsFailure()
    {
        _repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new Template("Base") { Id = 1 });
        _repoMock.Setup(r => r.GetInstancesByTemplateIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Instance>());
        _repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Template>
            {
                new("Base") { Id = 1 },
                new("Child") { Id = 2, ParentTemplateId = 1 }
            });
        _repoMock.Setup(r => r.GetCompositionsByTemplateIdAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateComposition>());

        var result = await _sut.CanDeleteTemplateAsync(1);

        Assert.True(result.IsFailure);
        Assert.Contains("child template(s)", result.Error);
        Assert.Contains("Child", result.Error);
    }

    [Fact]
    public async Task CanDeleteTemplate_ComposedByOthers_ReturnsFailure()
    {
        var composer = new Template("Composer") { Id = 2 };
        composer.Compositions.Add(new TemplateComposition("PumpModule") { ComposedTemplateId = 1 });

        _repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new Template("Module") { Id = 1 });
        _repoMock.Setup(r => r.GetInstancesByTemplateIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Instance>());
        _repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Template>
            {
                new("Module") { Id = 1 },
                composer
            });

        var result = await _sut.CanDeleteTemplateAsync(1);

        Assert.True(result.IsFailure);
        Assert.Contains("compose it", result.Error);
        Assert.Contains("Composer", result.Error);
    }

    [Fact]
    public async Task CanDeleteTemplate_DoesNotIssuePerTemplateCompositionQuery()
    {
        // TemplateEngine-009: Check 3 must read the Compositions navigation
        // already loaded by GetAllTemplatesAsync rather than issuing one
        // GetCompositionsByTemplateIdAsync round-trip per template.
        var templates = new List<Template>
        {
            new("Module") { Id = 1 },
            new("A") { Id = 2 },
            new("B") { Id = 3 },
            new("C") { Id = 4 },
        };

        _repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new Template("Module") { Id = 1 });
        _repoMock.Setup(r => r.GetInstancesByTemplateIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Instance>());
        _repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(templates);

        var result = await _sut.CanDeleteTemplateAsync(1);

        Assert.True(result.IsSuccess);
        _repoMock.Verify(r => r.GetCompositionsByTemplateIdAsync(
            It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Never);
    }

    [Fact]
    public async Task CanDeleteTemplate_NotFound_ReturnsFailure()
    {
        _repoMock.Setup(r => r.GetTemplateByIdAsync(999, It.IsAny<CancellationToken>()))
            .ReturnsAsync((Template?)null);

        var result = await _sut.CanDeleteTemplateAsync(999);

        Assert.True(result.IsFailure);
        Assert.Contains("not found", result.Error);
    }

    [Fact]
    public async Task DeleteTemplate_AllConstraintsMet_Deletes()
    {
        _repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new Template("Safe") { Id = 1 });
        _repoMock.Setup(r => r.GetInstancesByTemplateIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Instance>());
        _repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Template> { new("Safe") { Id = 1 } });
        _repoMock.Setup(r => r.GetCompositionsByTemplateIdAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateComposition>());
        _repoMock.Setup(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(1);

        var result = await _sut.DeleteTemplateAsync(1);

        Assert.True(result.IsSuccess);
        _repoMock.Verify(r => r.DeleteTemplateAsync(1, It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task CanDeleteTemplate_MultipleConstraints_AllErrorsReported()
    {
        _repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new Template("Busy") { Id = 1 });
        _repoMock.Setup(r => r.GetInstancesByTemplateIdAsync(1, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Instance> { new("Inst1") { Id = 1 } });
        var composer = new Template("Composer") { Id = 3 };
        composer.Compositions.Add(new TemplateComposition("Module") { ComposedTemplateId = 1 });
        _repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Template>
            {
                new("Busy") { Id = 1 },
                new("Child") { Id = 2, ParentTemplateId = 1 },
                composer
            });

        var result = await _sut.CanDeleteTemplateAsync(1);

        Assert.True(result.IsFailure);
        // All three constraint types should be mentioned
        Assert.Contains("instance(s)", result.Error);
        Assert.Contains("child template(s)", result.Error);
        Assert.Contains("compose it", result.Error);
    }
}
