using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using NeonSprawl.Server.Game.Gathering;
using NeonSprawl.Server.Tests;
using Xunit;

namespace NeonSprawl.Server.Tests.Game.Gathering;

public class ResourceNodeDefinitionRegistryTests
{
    private static ResourceNodeDefinitionRegistry CreateRegistryFromRows(
        IReadOnlyDictionary<string, ResourceNodeDefRow> nodesById,
        IReadOnlyDictionary<string, ResourceYieldRow> yieldsByNodeDefId)
    {
        var catalog = new ResourceNodeCatalog(
            "/tmp/resource-nodes",
            nodesById,
            yieldsByNodeDefId,
            nodeCatalogJsonFileCount: 1,
            yieldCatalogJsonFileCount: 1);
        return new ResourceNodeDefinitionRegistry(catalog);
    }

    [Fact]
    public void TryGetDefinition_ShouldReturnTrueAndExpectedMetadata_WhenNodeDefIdExists()
    {
        // Arrange
        const string nodeDefId = "prototype_resource_node_alpha";
        var nodes = new Dictionary<string, ResourceNodeDefRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceNodeDefRow(
                nodeDefId,
                "Prototype Salvage Heap",
                "consumer_salvage",
                MaxGathers: 10),
        };
        var yields = new Dictionary<string, ResourceYieldRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceYieldRow(nodeDefId, "scrap_metal_bulk", Quantity: 1),
        };
        var registry = CreateRegistryFromRows(nodes, yields);
        // Act
        var found = registry.TryGetDefinition(nodeDefId, out var def);
        // Assert
        Assert.True(found);
        Assert.NotNull(def);
        Assert.Equal("Prototype Salvage Heap", def.DisplayName);
        Assert.Equal("consumer_salvage", def.GatherLens);
        Assert.Equal(10, def.MaxGathers);
    }

    [Fact]
    public void TryGetYield_ShouldReturnTrueAndExpectedMetadata_WhenNodeDefIdExists()
    {
        // Arrange
        const string nodeDefId = "prototype_resource_node_alpha";
        var nodes = new Dictionary<string, ResourceNodeDefRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceNodeDefRow(
                nodeDefId,
                "Prototype Salvage Heap",
                "consumer_salvage",
                MaxGathers: 10),
        };
        var yields = new Dictionary<string, ResourceYieldRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceYieldRow(nodeDefId, "scrap_metal_bulk", Quantity: 1),
        };
        var registry = CreateRegistryFromRows(nodes, yields);
        // Act
        var found = registry.TryGetYield(nodeDefId, out var yield);
        // Assert
        Assert.True(found);
        Assert.NotNull(yield);
        Assert.Equal("scrap_metal_bulk", yield.ItemId);
        Assert.Equal(1, yield.Quantity);
    }

    [Fact]
    public void TryGetDefinition_ShouldReturnFalse_WhenNodeDefIdIsNull()
    {
        // Arrange
        const string nodeDefId = "prototype_resource_node_alpha";
        var nodes = new Dictionary<string, ResourceNodeDefRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceNodeDefRow(
                nodeDefId,
                "Prototype Salvage Heap",
                "consumer_salvage",
                MaxGathers: 10),
        };
        var yields = new Dictionary<string, ResourceYieldRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceYieldRow(nodeDefId, "scrap_metal_bulk", Quantity: 1),
        };
        var registry = CreateRegistryFromRows(nodes, yields);
        // Act
        var found = registry.TryGetDefinition(null, out var def);
        // Assert
        Assert.False(found);
        Assert.Null(def);
    }

    [Fact]
    public void TryGetYield_ShouldReturnFalse_WhenNodeDefIdIsNull()
    {
        // Arrange
        const string nodeDefId = "prototype_resource_node_alpha";
        var nodes = new Dictionary<string, ResourceNodeDefRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceNodeDefRow(
                nodeDefId,
                "Prototype Salvage Heap",
                "consumer_salvage",
                MaxGathers: 10),
        };
        var yields = new Dictionary<string, ResourceYieldRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceYieldRow(nodeDefId, "scrap_metal_bulk", Quantity: 1),
        };
        var registry = CreateRegistryFromRows(nodes, yields);
        // Act
        var found = registry.TryGetYield(null, out var yield);
        // Assert
        Assert.False(found);
        Assert.Null(yield);
    }

    [Fact]
    public void TryGetDefinition_ShouldReturnFalse_WhenNodeDefIdUnknown()
    {
        // Arrange
        const string nodeDefId = "prototype_resource_node_alpha";
        var nodes = new Dictionary<string, ResourceNodeDefRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceNodeDefRow(
                nodeDefId,
                "Prototype Salvage Heap",
                "consumer_salvage",
                MaxGathers: 10),
        };
        var yields = new Dictionary<string, ResourceYieldRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceYieldRow(nodeDefId, "scrap_metal_bulk", Quantity: 1),
        };
        var registry = CreateRegistryFromRows(nodes, yields);
        // Act
        var found = registry.TryGetDefinition("not_a_real_node", out var def);
        // Assert
        Assert.False(found);
        Assert.Null(def);
    }

    [Fact]
    public void TryGetYield_ShouldReturnFalse_WhenNodeDefIdUnknown()
    {
        // Arrange
        const string nodeDefId = "prototype_resource_node_alpha";
        var nodes = new Dictionary<string, ResourceNodeDefRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceNodeDefRow(
                nodeDefId,
                "Prototype Salvage Heap",
                "consumer_salvage",
                MaxGathers: 10),
        };
        var yields = new Dictionary<string, ResourceYieldRow>(StringComparer.Ordinal)
        {
            [nodeDefId] = new ResourceYieldRow(nodeDefId, "scrap_metal_bulk", Quantity: 1),
        };
        var registry = CreateRegistryFromRows(nodes, yields);
        // Act
        var found = registry.TryGetYield("not_a_real_node", out var yield);
        // Assert
        Assert.False(found);
        Assert.Null(yield);
    }

    [Fact]
    public void GetDefinitionsInIdOrder_ShouldListAllRowsOrderedByNodeDefId_WhenMultipleNodes()
    {
        // Arrange
        var nodes = new Dictionary<string, ResourceNodeDefRow>(StringComparer.Ordinal)
        {
            ["prototype_urban_bulk_delta"] = new ResourceNodeDefRow(
                "prototype_urban_bulk_delta",
                "Prototype Urban Bulk",
                "urban_bulk",
                MaxGathers: 10),
            ["prototype_bio_mat_gamma"] = new ResourceNodeDefRow(
                "prototype_bio_mat_gamma",
                "Prototype Bio Mat",
                "bio",
                MaxGathers: 10),
            ["prototype_resource_node_alpha"] = new ResourceNodeDefRow(
                "prototype_resource_node_alpha",
                "Prototype Salvage Heap",
                "consumer_salvage",
                MaxGathers: 10),
            ["prototype_subsurface_vein_beta"] = new ResourceNodeDefRow(
                "prototype_subsurface_vein_beta",
                "Prototype Subsurface Vein",
                "subsurface",
                MaxGathers: 10),
        };
        var yields = new Dictionary<string, ResourceYieldRow>(StringComparer.Ordinal)
        {
            ["prototype_urban_bulk_delta"] = new ResourceYieldRow(
                "prototype_urban_bulk_delta",
                "scrap_metal_bulk",
                Quantity: 5),
            ["prototype_bio_mat_gamma"] = new ResourceYieldRow(
                "prototype_bio_mat_gamma",
                "scrap_metal_bulk",
                Quantity: 3),
            ["prototype_resource_node_alpha"] = new ResourceYieldRow(
                "prototype_resource_node_alpha",
                "scrap_metal_bulk",
                Quantity: 1),
            ["prototype_subsurface_vein_beta"] = new ResourceYieldRow(
                "prototype_subsurface_vein_beta",
                "scrap_metal_bulk",
                Quantity: 2),
        };
        var registry = CreateRegistryFromRows(nodes, yields);
        // Act
        var list = registry.GetDefinitionsInIdOrder();
        // Assert
        Assert.Equal(4, list.Count);
        Assert.Equal("prototype_bio_mat_gamma", list[0].NodeDefId);
        Assert.Equal("prototype_resource_node_alpha", list[1].NodeDefId);
        Assert.Equal("prototype_subsurface_vein_beta", list[2].NodeDefId);
        Assert.Equal("prototype_urban_bulk_delta", list[3].NodeDefId);
    }

    [Fact]
    public void TryGetDefinition_ShouldMatchLoaderCatalog_WhenUsingPrototypeFixture()
    {
        // Arrange
        var root = Directory.CreateTempSubdirectory("neon-sprawl-resource-node-registry-loader-");
        try
        {
            var resourceNodesDir = Path.Combine(root.FullName, "content", "resource-nodes");
            var schemaDir = Path.Combine(root.FullName, "content", "schemas");
            Directory.CreateDirectory(resourceNodesDir);
            Directory.CreateDirectory(schemaDir);
            var nodeSchemaPath = Path.Combine(schemaDir, "resource-node-def.schema.json");
            var yieldSchemaPath = Path.Combine(schemaDir, "resource-yield-row.schema.json");
            File.Copy(ResourceNodeCatalogTestPaths.DiscoverRepoResourceNodeDefSchemaPath(), nodeSchemaPath, overwrite: true);
            File.Copy(ResourceNodeCatalogTestPaths.DiscoverRepoResourceYieldRowSchemaPath(), yieldSchemaPath, overwrite: true);
            File.Copy(
                Path.Combine(ResourceNodeCatalogTestPaths.DiscoverRepoResourceNodesDirectory(), "prototype_resource_nodes.json"),
                Path.Combine(resourceNodesDir, "prototype_resource_nodes.json"),
                overwrite: true);
            File.Copy(
                Path.Combine(ResourceNodeCatalogTestPaths.DiscoverRepoResourceNodesDirectory(), "prototype_resource_yields.json"),
                Path.Combine(resourceNodesDir, "prototype_resource_yields.json"),
                overwrite: true);
            var itemIds = new HashSet<string>(StringComparer.Ordinal) { "scrap_metal_bulk" };
            var loaded = ResourceNodeCatalogLoader.Load(
                resourceNodesDir,
                nodeSchemaPath,
                yieldSchemaPath,
                itemIds,
                NullLogger.Instance);
            var registry = new ResourceNodeDefinitionRegistry(loaded);
            // Act
            var ok = registry.TryGetDefinition("prototype_resource_node_alpha", out var node);
            var yieldOk = registry.TryGetYield("prototype_resource_node_alpha", out var yield);
            // Assert
            Assert.True(ok);
            Assert.NotNull(node);
            Assert.Equal("consumer_salvage", node.GatherLens);
            Assert.Equal(10, node.MaxGathers);
            Assert.True(yieldOk);
            Assert.NotNull(yield);
            Assert.Equal("scrap_metal_bulk", yield.ItemId);
            Assert.Equal(1, yield.Quantity);
        }
        finally
        {
            try
            {
                Directory.Delete(root.FullName, recursive: true);
            }
            catch (IOException)
            {
                // Best-effort: transient lock or race on some hosts; temp dir is unique per run.
            }
        }
    }

    [Fact]
    public async Task Host_ShouldResolveRegistryFromDi_WhenStartupSucceeds()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        using var client = factory.CreateClient();
        _ = await client.GetAsync("/health");
        // Act
        var registry = factory.Services.GetRequiredService<IResourceNodeDefinitionRegistry>();
        var found = registry.TryGetDefinition("prototype_resource_node_alpha", out var node);
        var yieldFound = registry.TryGetYield("prototype_resource_node_alpha", out var yield);
        // Assert
        Assert.True(found);
        Assert.NotNull(node);
        Assert.Equal("consumer_salvage", node.GatherLens);
        Assert.Equal(10, node.MaxGathers);
        Assert.True(yieldFound);
        Assert.NotNull(yield);
        Assert.Equal("scrap_metal_bulk", yield.ItemId);
        Assert.Equal(1, yield.Quantity);
    }
}
