using Theriapolis.Core;
using Theriapolis.Core.Data;
using Theriapolis.Core.Entities;
using Theriapolis.Core.Rules.Character;
using Theriapolis.Core.Rules.Reputation;
using Theriapolis.Core.Rules.Stats;
using Xunit;

namespace Theriapolis.Tests.Reputation;

/// <summary>
/// Phase 6 M5 — patrol/guard NPC allegiance flips when local disposition
/// drops to HOSTILE.
/// </summary>
public sealed class FactionAggressionTests : IClassFixture<WorldCache>
{
    private readonly WorldCache _cache;
    public FactionAggressionTests(WorldCache c) => _cache = c;

    private static Character WolfPc(ContentResolver content)
    {
        var b = new CharacterBuilder()
            .WithClade(content.Clades["canidae"])
            .WithSpecies(content.Species["wolf"])
            .WithClass(content.Classes["fangsworn"])
            .WithBackground(content.Backgrounds["pack_raised"])
            .WithAbilities(new AbilityScores(13, 12, 14, 10, 10, 11));
        var classD = content.Classes["fangsworn"];
        var added = new HashSet<SkillId>();
        for (int i = 0; i < classD.SkillOptions.Length && added.Count < classD.SkillsChoose; i++)
        {
            try
            {
                var sk = SkillIdExtensions.FromJson(classD.SkillOptions[i]);
                if (added.Add(sk)) b.ChooseSkill(sk);
            }
            catch (System.ArgumentException) { }
        }
        return b.Build();
    }

    [Fact]
    public void EnforcerPatrol_FlipsToHostile_WhenStandingTanks()
    {
        var content = new ContentResolver(new ContentLoader(TestHelpers.DataDirectory));
        var pc      = WolfPc(content);
        var rep     = new PlayerReputation();
        var actors  = new ActorManager();
        actors.SpawnPlayer(new Theriapolis.Core.Util.Vec2(50, 50));
        var world   = _cache.Get(0xCAFEBABEUL).World;
        var s       = world.Settlements.First();

        // Spawn a militia patroller manually using the npc template so it
        // carries the covenant_enforcers faction id.
        var template = content.Npcs.Templates.First(t => t.Id == "militia_patrol");
        var npc = actors.SpawnNpc(template, new Theriapolis.Core.Util.Vec2(s.TileX * C.WORLD_TILE_PIXELS, s.TileY * C.WORLD_TILE_PIXELS));
        // Give them a home settlement so propagation has somewhere to land.
        // (The init-only HomeSettlementId requires reconstruction; instead
        // we re-spawn through the (NpcActor pre) path with the field set.)
        actors.RemoveActor(npc.Id);
        var pre = new NpcActor(template)
        {
            Id = -1,
            Position         = new Theriapolis.Core.Util.Vec2(s.TileX * C.WORLD_TILE_PIXELS, s.TileY * C.WORLD_TILE_PIXELS),
            HomeSettlementId = s.Id,
        };
        var live = actors.SpawnNpc(pre);
        Assert.Equal(Allegiance.Neutral, live.Allegiance);
        Assert.Equal("covenant_enforcers", live.FactionId);

        // Tank the player's Enforcer standing with a single big crime.
        rep.Submit(new RepEvent
        {
            Kind        = RepEventKind.Crime,
            FactionId   = "covenant_enforcers",
            Magnitude   = -80,
            OriginTileX = s.TileX, OriginTileY = s.TileY,
        }, content.Factions);

        int flipped = FactionAggression.UpdateAllegiances(actors, pc, rep, content, world, 0xCAFEBABEUL);
        Assert.True(flipped >= 1, $"expected ≥1 flip; got {flipped}");
        Assert.Equal(Allegiance.Hostile, live.Allegiance);
    }

    [Fact]
    public void Resident_WithoutFaction_DoesNotFlip()
    {
        // A friendly non-faction resident (innkeeper without faction) should
        // never flip even with player infamy.
        var content = new ContentResolver(new ContentLoader(TestHelpers.DataDirectory));
        var pc      = WolfPc(content);
        var rep     = new PlayerReputation();
        var actors  = new ActorManager();
        actors.SpawnPlayer(new Theriapolis.Core.Util.Vec2(50, 50));
        var world   = _cache.Get(0xCAFEBABEUL).World;

        var template = content.Residents["generic_innkeeper"];   // no faction
        var pre = new NpcActor(template)
        {
            Id       = -1,
            Position = new Theriapolis.Core.Util.Vec2(0, 0),
        };
        var live = actors.SpawnNpc(pre);

        rep.Factions.Set("covenant_enforcers", -100);
        FactionAggression.UpdateAllegiances(actors, pc, rep, content, world, 0xCAFEBABEUL);
        Assert.NotEqual(Allegiance.Hostile, live.Allegiance);
    }

    [Fact]
    public void HostileNpc_StaysHostile_AndDoesNotChangeAgain()
    {
        // Sanity: a hostile NPC isn't "promoted" further (no "extra hostile").
        var content = new ContentResolver(new ContentLoader(TestHelpers.DataDirectory));
        var pc      = WolfPc(content);
        var rep     = new PlayerReputation();
        var actors  = new ActorManager();
        actors.SpawnPlayer(new Theriapolis.Core.Util.Vec2(50, 50));
        var world   = _cache.Get(0xCAFEBABEUL).World;

        // brigand_footpad has default_allegiance = hostile and no faction.
        var template = content.Npcs.Templates.First(t => t.Id == "brigand_footpad");
        var live = actors.SpawnNpc(template, new Theriapolis.Core.Util.Vec2(0, 0));
        Assert.Equal(Allegiance.Hostile, live.Allegiance);

        int flipped = FactionAggression.UpdateAllegiances(actors, pc, rep, content, world, 0xCAFEBABEUL);
        Assert.Equal(0, flipped);
        Assert.Equal(Allegiance.Hostile, live.Allegiance);
    }
}
