using ColaFlow.Modules.Mcp.Application.Services;
using ColaFlow.Modules.Mcp.Contracts.Resources;
using FluentAssertions;
using Microsoft.Extensions.Logging;
using NSubstitute;

namespace ColaFlow.Modules.Mcp.Tests.Services;

/// <summary>
/// Unit tests for McpResourceRegistry
/// </summary>
public class McpResourceRegistryTests
{
    private readonly ILogger<McpResourceRegistry> _logger;
    private readonly McpResourceRegistry _sut;

    public McpResourceRegistryTests()
    {
        _logger = Substitute.For<ILogger<McpResourceRegistry>>();
        _sut = new McpResourceRegistry(_logger);
    }

    [Fact]
    public void RegisterResource_AddsResourceToRegistry()
    {
        // Arrange
        var resource = CreateMockResource("colaflow://test", "Test Resource");

        // Act
        _sut.RegisterResource(resource);

        // Assert
        var resources = _sut.GetAllResources();
        resources.Should().ContainSingle();
        resources[0].Uri.Should().Be("colaflow://test");
    }

    [Fact]
    public void RegisterResource_WithDuplicateUri_Overwrites()
    {
        // Arrange
        var resource1 = CreateMockResource("colaflow://test", "Test Resource 1");
        var resource2 = CreateMockResource("colaflow://test", "Test Resource 2");

        // Act
        _sut.RegisterResource(resource1);
        _sut.RegisterResource(resource2);

        // Assert
        var resources = _sut.GetAllResources();
        resources.Count.Should().Be(2); // Both are in the list
        var foundResource = _sut.GetResourceByUri("colaflow://test");
        foundResource!.Name.Should().Be("Test Resource 2"); // But only the last one is returned
    }

    [Fact]
    public void GetResourceByUri_WithExactMatch_ReturnsResource()
    {
        // Arrange
        var resource = CreateMockResource("colaflow://projects.list", "Projects List");
        _sut.RegisterResource(resource);

        // Act
        var result = _sut.GetResourceByUri("colaflow://projects.list");

        // Assert
        result.Should().NotBeNull();
        result!.Uri.Should().Be("colaflow://projects.list");
    }

    [Fact]
    public void GetResourceByUri_WithTemplateMatch_ReturnsResource()
    {
        // Arrange
        var resource = CreateMockResource("colaflow://projects.get/{id}", "Project Details");
        _sut.RegisterResource(resource);

        // Act
        var result = _sut.GetResourceByUri("colaflow://projects.get/123");

        // Assert
        result.Should().NotBeNull();
        result!.Uri.Should().Be("colaflow://projects.get/{id}");
    }

    [Fact]
    public void GetResourceByUri_WithNonExistentUri_ReturnsNull()
    {
        // Act
        var result = _sut.GetResourceByUri("colaflow://nonexistent");

        // Assert
        result.Should().BeNull();
    }

    [Fact]
    public void GetResourceDescriptors_ReturnsAllDescriptors()
    {
        // Arrange
        var resource1 = CreateMockResource("colaflow://test1", "Test 1");
        var resource2 = CreateMockResource("colaflow://test2", "Test 2");
        _sut.RegisterResource(resource1);
        _sut.RegisterResource(resource2);

        // Act
        var descriptors = _sut.GetResourceDescriptors();

        // Assert
        descriptors.Should().HaveCount(2);
        descriptors.Should().Contain(d => d.Uri == "colaflow://test1");
        descriptors.Should().Contain(d => d.Uri == "colaflow://test2");
    }

    [Fact]
    public void GetAllResources_ReturnsReadOnlyList()
    {
        // Arrange
        var resource = CreateMockResource("colaflow://test", "Test");
        _sut.RegisterResource(resource);

        // Act
        var resources = _sut.GetAllResources();

        // Assert
        resources.Should().BeAssignableTo<IReadOnlyList<IMcpResource>>();
    }

    private IMcpResource CreateMockResource(string uri, string name)
    {
        var resource = Substitute.For<IMcpResource>();
        resource.UriReturns(uri);
        resource.NameReturns(name);
        resource.DescriptionReturns($"Description for {name}");
        resource.MimeTypeReturns("application/json");
        resource.GetContentAsync(Arg.Any<McpResourceRequest>(), Arg.Any<CancellationToken>())
            Returns(new McpResourceContent
            {
                Uri = uri,
                MimeType = "application/json",
                Text = "{}"
            });
        return resource;
    }

    [Fact]
    public void GetCategories_ShouldReturnAllUniqueCategories()
    {
        // Arrange
        var mockLogger = Substitute.For<ILogger<McpResourceRegistry>>();
        var registry = new McpResourceRegistry(mockLogger);

        var resource1 = Substitute.For<IMcpResource>();
        resource1.Setup(r => r.Uri)Returns("test://resource1");
        resource1.Setup(r => r.Name)Returns("Resource 1");
        resource1.Setup(r => r.Category)Returns("Projects");

        var resource2 = Substitute.For<IMcpResource>();
        resource2.Setup(r => r.Uri)Returns("test://resource2");
        resource2.Setup(r => r.Name)Returns("Resource 2");
        resource2.Setup(r => r.Category)Returns("Issues");

        var resource3 = Substitute.For<IMcpResource>();
        resource3.Setup(r => r.Uri)Returns("test://resource3");
        resource3.Setup(r => r.Name)Returns("Resource 3");
        resource3.Setup(r => r.Category)Returns("Projects"); // Duplicate category

        registry.RegisterResource(resource1.Object);
        registry.RegisterResource(resource2.Object);
        registry.RegisterResource(resource3.Object);

        // Act
        var categories = registry.GetCategories();

        // Assert
        Assert.Equal(2, categories.Count);
        Assert.Contains("Projects", categories);
        Assert.Contains("Issues", categories);
    }

    [Fact]
    public void GetResourcesByCategory_ShouldReturnOnlyResourcesInCategory()
    {
        // Arrange
        var mockLogger = Substitute.For<ILogger<McpResourceRegistry>>();
        var registry = new McpResourceRegistry(mockLogger);

        var projectResource1 = Substitute.For<IMcpResource>();
        projectResource1.Setup(r => r.Uri)Returns("test://project1");
        projectResource1.Setup(r => r.Name)Returns("Project 1");
        projectResource1.Setup(r => r.Category)Returns("Projects");

        var projectResource2 = Substitute.For<IMcpResource>();
        projectResource2.Setup(r => r.Uri)Returns("test://project2");
        projectResource2.Setup(r => r.Name)Returns("Project 2");
        projectResource2.Setup(r => r.Category)Returns("Projects");

        var issueResource = Substitute.For<IMcpResource>();
        issueResource.Setup(r => r.Uri)Returns("test://issue1");
        issueResource.Setup(r => r.Name)Returns("Issue 1");
        issueResource.Setup(r => r.Category)Returns("Issues");

        registry.RegisterResource(projectResource1.Object);
        registry.RegisterResource(projectResource2.Object);
        registry.RegisterResource(issueResource.Object);

        // Act
        var projectResources = registry.GetResourcesByCategory("Projects");

        // Assert
        Assert.Equal(2, projectResources.Count);
        Assert.All(projectResources, r => Assert.Equal("Projects", r.Category));
    }

    [Fact]
    public void GetResourcesGroupedByCategory_ShouldGroupCorrectly()
    {
        // Arrange
        var mockLogger = Substitute.For<ILogger<McpResourceRegistry>>();
        var registry = new McpResourceRegistry(mockLogger);

        var resource1 = Substitute.For<IMcpResource>();
        resource1.Setup(r => r.Uri)Returns("test://resource1");
        resource1.Setup(r => r.Name)Returns("Resource 1");
        resource1.Setup(r => r.Category)Returns("Projects");

        var resource2 = Substitute.For<IMcpResource>();
        resource2.Setup(r => r.Uri)Returns("test://resource2");
        resource2.Setup(r => r.Name)Returns("Resource 2");
        resource2.Setup(r => r.Category)Returns("Issues");

        var resource3 = Substitute.For<IMcpResource>();
        resource3.Setup(r => r.Uri)Returns("test://resource3");
        resource3.Setup(r => r.Name)Returns("Resource 3");
        resource3.Setup(r => r.Category)Returns("Projects");

        registry.RegisterResource(resource1.Object);
        registry.RegisterResource(resource2.Object);
        registry.RegisterResource(resource3.Object);

        // Act
        var grouped = registry.GetResourcesGroupedByCategory();

        // Assert
        Assert.Equal(2, grouped.Count);
        Assert.True(grouped.ContainsKey("Projects"));
        Assert.True(grouped.ContainsKey("Issues"));
        Assert.Equal(2, grouped["Projects"].Count);
        Assert.Single(grouped["Issues"]);
    }

    [Fact]
    public void GetDescriptor_ShouldIncludeEnhancedMetadata()
    {
        // Arrange
        var mockLogger = Substitute.For<ILogger<McpResourceRegistry>>();
        var registry = new McpResourceRegistry(mockLogger);

        var mockResource = Substitute.For<IMcpResource>();
        mockResource(r => r.Uri)Returns("test://resource");
        mockResource(r => r.Name)Returns("Test Resource");
        mockResource(r => r.Description)Returns("Test description");
        mockResource(r => r.MimeType)Returns("application/json");
        mockResource(r => r.Category)Returns("Testing");
        mockResource(r => r.Version)Returns("1.0");
        mockResource(r => r.GetDescriptor())Returns(new McpResourceDescriptor
        {
            Uri = "test://resource",
            Name = "Test Resource",
            Description = "Test description",
            MimeType = "application/json",
            Category = "Testing",
            Version = "1.0",
            Parameters = new Dictionary<string, string> { { "id", "Resource ID" } },
            Examples = new List<string> { "GET test://resource?id=123" },
            Tags = new List<string> { "test", "example" },
            IsEnabled = true
        });

        registry.RegisterResource(mockResource);

        // Act
        var descriptors = registry.GetResourceDescriptors();

        // Assert
        Assert.Single(descriptors);
        var descriptor = descriptors[0];
        Assert.Equal("Testing", descriptor.Category);
        Assert.Equal("1.0", descriptor.Version);
        Assert.NotNull(descriptor.Parameters);
        Assert.NotNull(descriptor.Examples);
        Assert.NotNull(descriptor.Tags);
        Assert.True(descriptor.IsEnabled);
    }
}
