using Moq;
using ScadaLink.Commons.Entities.Templates;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Interfaces.Services;
using ScadaLink.TemplateEngine.Services;

namespace ScadaLink.TemplateEngine.Tests.Services;

public class TemplateFolderServiceTests
{
    private readonly Mock<ITemplateEngineRepository> _repoMock = new();
    private readonly Mock<IAuditService> _auditMock = new();
    private readonly TemplateFolderService _sut;

    public TemplateFolderServiceTests()
    {
        _sut = new TemplateFolderService(_repoMock.Object, _auditMock.Object);
    }

    [Fact]
    public async Task CreateFolder_ValidInput_ReturnsSuccess()
    {
        _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateFolder>());

        var result = await _sut.CreateFolderAsync("Dev", null, "admin");

        Assert.True(result.IsSuccess);
        Assert.Equal("Dev", result.Value.Name);
        Assert.Null(result.Value.ParentFolderId);
        _repoMock.Verify(r => r.AddFolderAsync(It.IsAny<TemplateFolder>(), It.IsAny<CancellationToken>()), Times.Once);
        _repoMock.Verify(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task CreateFolder_EmptyName_ReturnsFailure()
    {
        var result = await _sut.CreateFolderAsync("  ", null, "admin");

        Assert.True(result.IsFailure);
        Assert.Contains("required", result.Error, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task CreateFolder_DuplicateSiblingName_CaseInsensitive_ReturnsFailure()
    {
        _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateFolder>
            {
                new("Dev") { Id = 1, ParentFolderId = null }
            });

        var result = await _sut.CreateFolderAsync("dev", null, "admin");

        Assert.True(result.IsFailure);
        Assert.Contains("already exists", result.Error);
    }

    [Fact]
    public async Task CreateFolder_ParentNotFound_ReturnsFailure()
    {
        _repoMock.Setup(r => r.GetFolderByIdAsync(99, It.IsAny<CancellationToken>()))
            .ReturnsAsync((TemplateFolder?)null);

        var result = await _sut.CreateFolderAsync("Sub", 99, "admin");

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

    [Fact]
    public async Task RenameFolder_ValidInput_ReturnsSuccess()
    {
        var folder = new TemplateFolder("Old") { Id = 1, ParentFolderId = null };
        _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(folder);
        _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateFolder> { folder });

        var result = await _sut.RenameFolderAsync(1, "New", "admin");

        Assert.True(result.IsSuccess);
        Assert.Equal("New", result.Value.Name);
    }

    [Fact]
    public async Task RenameFolder_NotFound_ReturnsFailure()
    {
        _repoMock.Setup(r => r.GetFolderByIdAsync(99, It.IsAny<CancellationToken>()))
            .ReturnsAsync((TemplateFolder?)null);

        var result = await _sut.RenameFolderAsync(99, "New", "admin");

        Assert.True(result.IsFailure);
    }

    [Fact]
    public async Task RenameFolder_DuplicateSibling_ReturnsFailure()
    {
        var folder = new TemplateFolder("Old") { Id = 1, ParentFolderId = null };
        var sibling = new TemplateFolder("Other") { Id = 2, ParentFolderId = null };
        _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(folder);
        _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateFolder> { folder, sibling });

        var result = await _sut.RenameFolderAsync(1, "Other", "admin");

        Assert.True(result.IsFailure);
        Assert.Contains("already exists", result.Error);
    }

    [Fact]
    public async Task MoveFolder_ValidParent_ReturnsSuccess()
    {
        var f1 = new TemplateFolder("A") { Id = 1, ParentFolderId = null };
        var f2 = new TemplateFolder("B") { Id = 2, ParentFolderId = null };
        _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(f1);
        _repoMock.Setup(r => r.GetFolderByIdAsync(2, It.IsAny<CancellationToken>())).ReturnsAsync(f2);
        _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateFolder> { f1, f2 });

        var result = await _sut.MoveFolderAsync(1, 2, "admin");

        Assert.True(result.IsSuccess);
        Assert.Equal(2, result.Value.ParentFolderId);
    }

    [Fact]
    public async Task MoveFolder_OntoSelf_ReturnsFailure()
    {
        var f1 = new TemplateFolder("A") { Id = 1 };
        _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(f1);

        var result = await _sut.MoveFolderAsync(1, 1, "admin");

        Assert.True(result.IsFailure);
        Assert.Contains("cycle", result.Error, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task MoveFolder_OntoDescendant_ReturnsFailure()
    {
        // A -> B -> C; attempting to move A under C must fail.
        var fa = new TemplateFolder("A") { Id = 1, ParentFolderId = null };
        var fb = new TemplateFolder("B") { Id = 2, ParentFolderId = 1 };
        var fc = new TemplateFolder("C") { Id = 3, ParentFolderId = 2 };
        _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(fa);
        _repoMock.Setup(r => r.GetFolderByIdAsync(3, It.IsAny<CancellationToken>())).ReturnsAsync(fc);
        _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateFolder> { fa, fb, fc });

        var result = await _sut.MoveFolderAsync(1, 3, "admin");

        Assert.True(result.IsFailure);
        Assert.Contains("cycle", result.Error, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task MoveFolder_ToRoot_ReturnsSuccess()
    {
        var f = new TemplateFolder("Sub") { Id = 1, ParentFolderId = 99 };
        _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(f);
        _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateFolder> { f });

        var result = await _sut.MoveFolderAsync(1, null, "admin");

        Assert.True(result.IsSuccess);
        Assert.Null(result.Value.ParentFolderId);
    }

    [Fact]
    public async Task MoveFolder_PreExistingCycleInGraph_ReturnsFailure_DoesNotInfiniteLoop()
    {
        // Manufactured malformed graph: X.parent=Y, Y.parent=X. We move Z under X.
        // The ancestor walk would loop forever without a guard.
        var x = new TemplateFolder("X") { Id = 1, ParentFolderId = 2 };
        var y = new TemplateFolder("Y") { Id = 2, ParentFolderId = 1 };
        var z = new TemplateFolder("Z") { Id = 3, ParentFolderId = null };
        _repoMock.Setup(r => r.GetFolderByIdAsync(3, It.IsAny<CancellationToken>())).ReturnsAsync(z);
        _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(x);
        _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateFolder> { x, y, z });

        var result = await _sut.MoveFolderAsync(3, 1, "admin");

        Assert.True(result.IsFailure);
        Assert.Contains("cycle", result.Error, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task DeleteFolder_Empty_ReturnsSuccess()
    {
        var f = new TemplateFolder("Empty") { Id = 1 };
        _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(f);
        _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateFolder> { f });
        _repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Template>());

        var result = await _sut.DeleteFolderAsync(1, "admin");

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

    [Fact]
    public async Task DeleteFolder_HasChildFolders_ReturnsFailure_WithCounts()
    {
        var parent = new TemplateFolder("P") { Id = 1 };
        var child = new TemplateFolder("C") { Id = 2, ParentFolderId = 1 };
        _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(parent);
        _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateFolder> { parent, child });
        _repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Template>());

        var result = await _sut.DeleteFolderAsync(1, "admin");

        Assert.True(result.IsFailure);
        Assert.Contains("1 subfolder", result.Error);
    }

    [Fact]
    public async Task DeleteFolder_HasTemplates_ReturnsFailure_WithCounts()
    {
        var f = new TemplateFolder("P") { Id = 1 };
        var t = new Template("X") { Id = 5, FolderId = 1 };
        _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(f);
        _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<TemplateFolder> { f });
        _repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
            .ReturnsAsync(new List<Template> { t });

        var result = await _sut.DeleteFolderAsync(1, "admin");

        Assert.True(result.IsFailure);
        Assert.Contains("1 template", result.Error);
    }
}
