using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Time.Testing;
using NeonSprawl.Server.Game.AbilityInput;
using NeonSprawl.Server.Game.Mastery;
using NeonSprawl.Server.Game.PositionState;
using NeonSprawl.Server.Game.Skills;
using Npgsql;

namespace NeonSprawl.Server.Tests.Game.Skills;

/// <summary>
/// Same in-memory overrides as <see cref="InMemoryWebApplicationFactory"/>, plus a stub <see cref="ISkillDefinitionRegistry"/>
/// where <c>salvage</c> does not allow <c>mission_reward</c> — NEO-43 defensive tests without mutating disk catalog.
/// </summary>
public sealed class MissionRewardDeniedRegistryWebApplicationFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment("Testing");

        var skillsDir = SkillCatalogPathResolution.TryDiscoverSkillsDirectory(AppContext.BaseDirectory)
            ?? throw new InvalidOperationException(
                "Could not discover repo content/skills from AppContext.BaseDirectory; run tests from the neon-sprawl clone.");
        var masteryDir = MasteryCatalogPathResolution.TryDiscoverMasteryDirectory(AppContext.BaseDirectory)
            ?? throw new InvalidOperationException(
                "Could not discover repo content/mastery from AppContext.BaseDirectory; run tests from the neon-sprawl clone.");
        builder.UseSetting("Content:SkillsDirectory", skillsDir);
        builder.UseSetting("Content:MasteryDirectory", masteryDir);

        builder.ConfigureTestServices(services =>
        {
            for (var i = services.Count - 1; i >= 0; i--)
            {
                var d = services[i];
                if (d.ServiceType == typeof(IPositionStateStore) ||
                    d.ServiceType == typeof(IPlayerHotbarLoadoutStore) ||
                    d.ServiceType == typeof(IPlayerSkillProgressionStore) ||
                    d.ServiceType == typeof(IPlayerPerkStateStore) ||
                    d.ServiceType == typeof(TimeProvider) ||
                    d.ServiceType == typeof(IPlayerAbilityCooldownStore) ||
                    d.ServiceType == typeof(ISkillDefinitionRegistry) ||
                    d.ServiceType == typeof(NpgsqlDataSource) ||
                    (d.ServiceType == typeof(IHostedService) &&
                     (d.ImplementationType == typeof(PostgresDevPlayerSeedHostedService) ||
                      d.ImplementationType == typeof(NpgsqlDataSourceShutdownHostedService))))
                {
                    services.RemoveAt(i);
                }
            }

            var fakeTime = new FakeTimeProvider(new DateTimeOffset(2026, 4, 30, 12, 0, 0, TimeSpan.Zero));
            services.AddSingleton<TimeProvider>(fakeTime);
            services.AddSingleton<IPositionStateStore, InMemoryPositionStateStore>();
            services.AddSingleton<IPlayerHotbarLoadoutStore, InMemoryPlayerHotbarLoadoutStore>();
            services.AddSingleton<IPlayerSkillProgressionStore, InMemoryPlayerSkillProgressionStore>();
            services.AddSingleton<IPlayerPerkStateStore, InMemoryPlayerPerkStateStore>();
            services.AddSingleton<IPlayerAbilityCooldownStore, InMemoryPlayerAbilityCooldownStore>();
            services.AddSingleton<ISkillDefinitionRegistry, MissionRewardDeniedSkillRegistry>();
        });
    }
}

/// <summary>Prototype trio ids; <c>salvage</c> excludes <c>mission_reward</c> so mission grant path denies without throwing.</summary>
internal sealed class MissionRewardDeniedSkillRegistry : ISkillDefinitionRegistry
{
    private static readonly SkillDefRow SalvageNoMissionReward = new(
        "salvage",
        "gather",
        "Salvage",
        new[] { "activity" });

    private static readonly SkillDefRow Refine = new(
        "refine",
        "process",
        "Refine",
        new[] { "activity", "mission_reward", "trainer" });

    private static readonly SkillDefRow Intrusion = new(
        "intrusion",
        "tech",
        "Intrusion",
        new[] { "activity", "mission_reward", "book_or_item" });

    private static readonly IReadOnlyList<SkillDefRow> Ordered =
    [
        Intrusion,
        Refine,
        SalvageNoMissionReward,
    ];

    public bool TryGetDefinition(string? skillId, [NotNullWhen(true)] out SkillDefRow? definition)
    {
        if (skillId is null)
        {
            definition = null;
            return false;
        }

        var k = skillId.Trim();
        foreach (var row in Ordered)
        {
            if (string.Equals(row.Id, k, StringComparison.OrdinalIgnoreCase))
            {
                definition = row;
                return true;
            }
        }

        definition = null;
        return false;
    }

    public IReadOnlyList<SkillDefRow> GetDefinitionsInIdOrder() => Ordered;
}
