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

namespace NeonSprawl.Server.Tests.Game.Skills;

public class SkillDefinitionRegistryTests
{
    private static SkillDefinitionRegistry CreateRegistryFromRows(IReadOnlyDictionary<string, SkillDefRow> byId)
    {
        var catalog = new SkillDefinitionCatalog("/tmp/catalog", byId, catalogJsonFileCount: 1);
        return new SkillDefinitionRegistry(catalog);
    }

    [Fact]
    public void TryGetDefinition_ShouldReturnTrueAndExpectedXpKinds_WhenIdExists()
    {
        // Arrange
        var rows = new Dictionary<string, SkillDefRow>(StringComparer.Ordinal)
        {
            ["intrusion"] = new SkillDefRow(
                "intrusion",
                "tech",
                "Intrusion",
                new[] { "activity", "mission_reward", "book_or_item" }),
        };
        var registry = CreateRegistryFromRows(rows);
        // Act
        var found = registry.TryGetDefinition("intrusion", out var def);
        // Assert
        Assert.True(found);
        Assert.NotNull(def);
        Assert.Equal("tech", def.Category);
        Assert.Equal("Intrusion", def.DisplayName);
        Assert.Contains("activity", def.AllowedXpSourceKinds, StringComparer.Ordinal);
        Assert.Contains("book_or_item", def.AllowedXpSourceKinds, StringComparer.Ordinal);
    }

    [Fact]
    public void TryGetDefinition_ShouldReturnFalse_WhenSkillIdIsNull()
    {
        // Arrange
        var rows = new Dictionary<string, SkillDefRow>(StringComparer.Ordinal)
        {
            ["salvage"] = new SkillDefRow("salvage", "gather", "Salvage", new[] { "activity" }),
        };
        var registry = CreateRegistryFromRows(rows);
        // Act
        var found = registry.TryGetDefinition(null, out var def);
        // Assert
        Assert.False(found);
        Assert.Null(def);
    }

    [Fact]
    public void TryGetDefinition_ShouldReturnFalse_WhenIdUnknown()
    {
        // Arrange
        var rows = new Dictionary<string, SkillDefRow>(StringComparer.Ordinal)
        {
            ["salvage"] = new SkillDefRow("salvage", "gather", "Salvage", new[] { "activity" }),
        };
        var registry = CreateRegistryFromRows(rows);
        // Act
        var found = registry.TryGetDefinition("not_a_real_skill", out var def);
        // Assert
        Assert.False(found);
        Assert.Null(def);
    }

    [Fact]
    public void GetDefinitionsInIdOrder_ShouldListAllRowsOrderedById_WhenMultipleSkills()
    {
        // Arrange
        var rows = new Dictionary<string, SkillDefRow>(StringComparer.Ordinal)
        {
            ["salvage"] = new SkillDefRow("salvage", "gather", "Salvage", new[] { "activity", "mission_reward" }),
            ["refine"] = new SkillDefRow("refine", "process", "Refine", new[] { "activity" }),
            ["intrusion"] = new SkillDefRow("intrusion", "tech", "Intrusion", new[] { "trainer" }),
        };
        var registry = CreateRegistryFromRows(rows);
        // Act
        var list = registry.GetDefinitionsInIdOrder();
        // Assert
        Assert.Equal(3, list.Count);
        Assert.Equal("intrusion", list[0].Id);
        Assert.Equal("refine", list[1].Id);
        Assert.Equal("salvage", list[2].Id);
    }

    [Fact]
    public void TryGetDefinition_ShouldMatchLoaderCatalog_WhenUsingPrototypeFixture()
    {
        // Arrange
        var root = Directory.CreateTempSubdirectory("neon-sprawl-registry-loader-");
        try
        {
            var skillsDir = Path.Combine(root.FullName, "content", "skills");
            var schemaDir = Path.Combine(root.FullName, "content", "schemas");
            Directory.CreateDirectory(skillsDir);
            Directory.CreateDirectory(schemaDir);
            var schemaPath = Path.Combine(schemaDir, "skill-def.schema.json");
            File.Copy(SkillCatalogTestPaths.DiscoverRepoSkillDefSchemaPath(), schemaPath, overwrite: true);
            var json = """
                       {
                         "skills": [
                           {
                             "id": "salvage",
                             "category": "gather",
                             "displayName": "Salvage",
                             "allowedXpSourceKinds": ["activity", "mission_reward"]
                           },
                           {
                             "id": "refine",
                             "category": "process",
                             "displayName": "Refine",
                             "allowedXpSourceKinds": ["activity", "mission_reward", "trainer"]
                           },
                           {
                             "id": "intrusion",
                             "category": "tech",
                             "displayName": "Intrusion",
                             "allowedXpSourceKinds": ["activity", "mission_reward", "book_or_item"]
                           }
                         ]
                       }
                       """;
            File.WriteAllText(Path.Combine(skillsDir, "prototype_skills.json"), json, Encoding.UTF8);
            var loaded = SkillDefinitionCatalogLoader.Load(skillsDir, schemaPath, NullLogger.Instance);
            var registry = new SkillDefinitionRegistry(loaded);
            // Act
            var ok = registry.TryGetDefinition("refine", out var refine);
            // Assert
            Assert.True(ok);
            Assert.NotNull(refine);
            Assert.Equal("process", refine.Category);
            Assert.Contains("trainer", refine.AllowedXpSourceKinds, StringComparer.Ordinal);
        }
        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<ISkillDefinitionRegistry>();
        var found = registry.TryGetDefinition("salvage", out var salvage);
        // Assert
        Assert.True(found);
        Assert.NotNull(salvage);
        Assert.Contains("activity", salvage.AllowedXpSourceKinds, StringComparer.Ordinal);
    }
}
