using System.Linq;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using NeonSprawl.Server.Game.Skills;
using Xunit;

namespace NeonSprawl.Server.Tests.Game.Skills;

/// <summary>Regression tests for <c>POST …/skill-progression</c> (NEO-38). NEO-40 adds comment-only hook sites; JSON contract unchanged. E2.M2 implementation-alignment row documents NEO-40 landed.</summary>
public sealed class SkillProgressionGrantApiTests
{
    private static SkillProgressionGrantRequest Grant(
        string skillId,
        int schemaVersion,
        int amount = 50,
        string sourceKind = "activity") =>
        new SkillProgressionGrantRequest
        {
            SchemaVersion = schemaVersion,
            SkillId = skillId,
            Amount = amount,
            SourceKind = sourceKind,
        };

    private static SkillProgressionGrantRequest ValidGrant(string skillId, int amount = 50, string sourceKind = "activity") =>
        Grant(skillId, SkillProgressionGrantRequest.CurrentSchemaVersion, amount, sourceKind);

    [Fact]
    public async Task PostSkillProgression_ShouldReturnBadRequest_WhenSchemaVersionMismatch()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var request = Grant("salvage", SkillProgressionGrantRequest.CurrentSchemaVersion + 42, amount: 1);

        // Act
        var response = await client.PostAsJsonAsync("/game/players/dev-local-1/skill-progression", request);

        // Assert
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task PostSkillProgression_ShouldReturnNotFound_WhenPlayerUnknown()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/missing-player/skill-progression",
            ValidGrant("salvage"));

        // Assert
        Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
    }

    [Fact]
    public async Task PostSkillProgression_ShouldDenyUnknownSkill_WithReasonUnknownSkill_AndEchoProgression()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/skill-progression",
            ValidGrant("not-a-prototype-skill"));

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var envelope = await response.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
        Assert.NotNull(envelope);
        Assert.False(envelope!.Granted);
        Assert.Equal(SkillProgressionSnapshotApi.ReasonUnknownSkill, envelope.ReasonCode);
        Assert.NotNull(envelope.Progression);
        var salvage = envelope.Progression!.Skills!.Single(static s => s.Id == "salvage");
        Assert.Equal(0, salvage.Xp);
        Assert.Equal(1, salvage.Level);
    }

    [Fact]
    public async Task PostSkillProgression_ShouldDenyTrainerOnSalvage_WithReasonSourceKindNotAllowed()
    {
        // Arrange — prototype_skills: salvage allowedXpSourceKinds = activity, mission_reward only
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/skill-progression",
            ValidGrant("salvage", amount: 10, sourceKind: "trainer"));

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var envelope = await response.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
        Assert.NotNull(envelope);
        Assert.False(envelope!.Granted);
        Assert.Equal(SkillProgressionSnapshotApi.ReasonSourceKindNotAllowed, envelope.ReasonCode);
    }

    [Fact]
    public async Task PostSkillProgression_ShouldDenyNonPositiveAmount_WithReasonInvalidAmount()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/skill-progression",
            ValidGrant("salvage", amount: 0));

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var envelope = await response.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
        Assert.NotNull(envelope);
        Assert.False(envelope!.Granted);
        Assert.Equal(SkillProgressionSnapshotApi.ReasonInvalidAmount, envelope.ReasonCode);
    }

    [Fact]
    public async Task PostSkillProgression_ShouldSerializeReasonCodeNull_OnSuccess()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/skill-progression",
            ValidGrant("salvage", amount: 10));

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);
        Assert.True(doc.RootElement.TryGetProperty("reasonCode", out var reasonCode));
        Assert.Equal(JsonValueKind.Null, reasonCode.ValueKind);
    }

    [Fact]
    public async Task PostSkillProgression_ShouldApplyGrant_AndGetMatches_WhenTrainerAllowedForRefine()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var post = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/skill-progression",
            ValidGrant("refine", amount: 25, sourceKind: "trainer"));

        // Assert
        Assert.Equal(HttpStatusCode.OK, post.StatusCode);
        var envelope = await post.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
        Assert.NotNull(envelope);
        Assert.True(envelope!.Granted);
        Assert.Empty(envelope.LevelUps);
        Assert.Single(envelope.Progression!.Skills!, static s => s.Id == "refine" && s.Xp == 25 && s.Level == 1);

        var get = await client.GetFromJsonAsync<SkillProgressionSnapshotResponse>(
            "/game/players/dev-local-1/skill-progression");
        Assert.NotNull(get);
        var refine = Assert.Single(get!.Skills!, static s => s.Id == "refine");
        Assert.Equal(25, refine.Xp);
        Assert.Equal(1, refine.Level);
    }

    [Fact]
    public async Task PostSkillProgression_ShouldReportLevelUps_WhenThresholdCrossed()
    {
        // Arrange — content curve (prototype_level_curve.json): +100 xp from 0 → level 2
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var post = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/skill-progression",
            ValidGrant("intrusion", amount: 100, sourceKind: "activity"));

        // Assert
        var envelope = await post.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
        Assert.NotNull(envelope);
        Assert.True(envelope!.Granted);
        var up = Assert.Single(envelope.LevelUps!);
        Assert.Equal("intrusion", up.SkillId);
        Assert.Equal(1, up.PreviousLevel);
        Assert.Equal(2, up.NewLevel);
        Assert.Single(envelope.Progression!.Skills!, static s => s.Id == "intrusion" && s.Xp == 100 && s.Level == 2);
    }

    [Fact]
    public async Task PostSkillProgression_ShouldAcceptSourceKind_WithIgnoreCase_OnAllowedList()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var post = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/skill-progression",
            ValidGrant("salvage", amount: 5, sourceKind: "ACTIVITY"));

        // Assert
        var envelope = await post.Content.ReadFromJsonAsync<SkillProgressionGrantResponse>();
        Assert.NotNull(envelope);
        Assert.True(envelope!.Granted);
    }
}
