using Theriapolis.Core.Data;
using Theriapolis.Core.Entities;
using Xunit;

namespace Theriapolis.Tests.Entities;

/// <summary>
/// Phase 6.5 M6 — per-NPC scent profile data layer. Verifies the
/// ScentTag derivation: faction id → affiliation tag, runtime flags →
/// distress / activity tags, count truncation per the Scent Literacy
/// (top 1) vs Scent Mastery (top 3) tier.
/// </summary>
public sealed class ScentTagTests
{
    // ── Faction → tag mapping ─────────────────────────────────────────────

    [Theory]
    [InlineData("maw",                ScentTag.MawAffiliated)]
    [InlineData("inheritors",         ScentTag.InheritorAffiliated)]
    [InlineData("thorn_council",      ScentTag.ThornCouncilAffiliated)]
    [InlineData("covenant_enforcers", ScentTag.CovenantEnforcerAffiliated)]
    [InlineData("hybrid_underground", ScentTag.HybridUndergroundAffiliated)]
    [InlineData("unsheathed",         ScentTag.UnsheathedAffiliated)]
    [InlineData("merchant_guilds",    ScentTag.MerchantAffiliated)]
    public void FromFactionId_MapsKnownFactions(string factionId, ScentTag expected)
    {
        Assert.Equal(expected, ScentTagExtensions.FromFactionId(factionId));
    }

    [Theory]
    [InlineData("")]
    [InlineData("not_a_real_faction")]
    [InlineData("random_string")]
    public void FromFactionId_ReturnsNoneForUnknown(string factionId)
    {
        Assert.Equal(ScentTag.None, ScentTagExtensions.FromFactionId(factionId));
    }

    [Fact]
    public void FromFactionId_IsCaseInsensitive()
    {
        Assert.Equal(ScentTag.MawAffiliated,        ScentTagExtensions.FromFactionId("MAW"));
        Assert.Equal(ScentTag.InheritorAffiliated,  ScentTagExtensions.FromFactionId("Inheritors"));
    }

    // ── ComputeScentTags: faction-derived ─────────────────────────────────

    [Fact]
    public void ComputeScentTags_LacroixSurfacesMawAffiliated()
    {
        // Lacroix scenario: faction=maw, full HP, no kills.
        var npc = MakeResidentNpc("canidae", "coyote", factionId: "maw");
        var tags = npc.ComputeScentTags(maxCount: 1);
        Assert.Single(tags);
        Assert.Equal(ScentTag.MawAffiliated, tags[0]);
    }

    [Fact]
    public void ComputeScentTags_MerchantSurfacesMerchantAffiliated()
    {
        var npc = MakeResidentNpc("canidae", "fox", factionId: "merchant_guilds");
        var tags = npc.ComputeScentTags(maxCount: 1);
        Assert.Single(tags);
        Assert.Equal(ScentTag.MerchantAffiliated, tags[0]);
    }

    [Fact]
    public void ComputeScentTags_NoFactionEmpty()
    {
        var npc = MakeResidentNpc("canidae", "wolf", factionId: "");
        var tags = npc.ComputeScentTags(maxCount: 1);
        Assert.Empty(tags);
    }

    // ── ComputeScentTags: runtime-derived ─────────────────────────────────

    [Fact]
    public void ComputeScentTags_RecentlyKilledSurfaces()
    {
        var npc = MakeResidentNpc("canidae", "wolf", factionId: "");
        npc.HasRecentlyKilled = true;
        var tags = npc.ComputeScentTags(maxCount: 1);
        Assert.Single(tags);
        Assert.Equal(ScentTag.RecentlyKilled, tags[0]);
    }

    [Fact]
    public void ComputeScentTags_FrightenedAtLowHp()
    {
        var npc = MakeResidentNpc("canidae", "wolf", factionId: "");
        // Drop HP to 20% (below 25% threshold).
        npc.CurrentHp = npc.MaxHp / 5;
        var tags = npc.ComputeScentTags(maxCount: 1);
        Assert.Single(tags);
        Assert.Equal(ScentTag.Frightened, tags[0]);
    }

    [Fact]
    public void ComputeScentTags_WoundedAtHalfHp()
    {
        var npc = MakeResidentNpc("canidae", "wolf", factionId: "");
        // Drop HP to ~40% (below 50% but above 25%).
        npc.CurrentHp = (int)(npc.MaxHp * 0.4f);
        var tags = npc.ComputeScentTags(maxCount: 1);
        Assert.Single(tags);
        Assert.Equal(ScentTag.Wounded, tags[0]);
    }

    [Fact]
    public void ComputeScentTags_DeadEmitsNoFrightened()
    {
        var npc = MakeResidentNpc("canidae", "wolf", factionId: "");
        npc.CurrentHp = 0;
        var tags = npc.ComputeScentTags(maxCount: 5);
        // Dead NPCs don't carry distress markers (they're past distress).
        Assert.DoesNotContain(ScentTag.Frightened, tags);
        Assert.DoesNotContain(ScentTag.Wounded,    tags);
    }

    [Fact]
    public void ComputeScentTags_ContrabandFlagSurfaces()
    {
        var npc = MakeResidentNpc("canidae", "wolf", factionId: "");
        npc.CarriesContrabandFlag = true;
        var tags = npc.ComputeScentTags(maxCount: 5);
        Assert.Contains(ScentTag.CarriesContraband, tags);
    }

    // ── ComputeScentTags: priority + truncation ───────────────────────────

    [Fact]
    public void ComputeScentTags_FactionTagWinsAtMaxCount1()
    {
        // Faction (priority 1–8) leads runtime tags (priority 16+) when
        // truncating to a single read.
        var npc = MakeResidentNpc("canidae", "coyote", factionId: "maw");
        npc.HasRecentlyKilled = true;
        npc.CarriesContrabandFlag = true;
        npc.CurrentHp = npc.MaxHp / 5;  // also Frightened
        var tags = npc.ComputeScentTags(maxCount: 1);
        Assert.Single(tags);
        Assert.Equal(ScentTag.MawAffiliated, tags[0]);
    }

    [Fact]
    public void ComputeScentTags_MasteryReadsUpToThree()
    {
        var npc = MakeResidentNpc("canidae", "coyote", factionId: "maw");
        npc.HasRecentlyKilled = true;
        npc.CurrentHp = npc.MaxHp / 5;  // Frightened
        npc.CarriesContrabandFlag = true;
        var tags = npc.ComputeScentTags(maxCount: 3);
        Assert.Equal(3, tags.Count);
        // Order: faction (1), then runtime in declaration order.
        Assert.Equal(ScentTag.MawAffiliated,   tags[0]);
        Assert.Equal(ScentTag.RecentlyKilled,  tags[1]);
        Assert.Equal(ScentTag.Frightened,      tags[2]);
    }

    [Fact]
    public void ComputeScentTags_MaxCountZeroReturnsEmpty()
    {
        var npc = MakeResidentNpc("canidae", "coyote", factionId: "maw");
        npc.HasRecentlyKilled = true;
        var tags = npc.ComputeScentTags(maxCount: 0);
        Assert.Empty(tags);
    }

    [Fact]
    public void ComputeScentTags_MaxCountFiveCapsAtAvailable()
    {
        // Only faction tag available; cap at 5 returns just the one.
        var npc = MakeResidentNpc("canidae", "coyote", factionId: "maw");
        var tags = npc.ComputeScentTags(maxCount: 5);
        Assert.Single(tags);
    }

    // ── DisplayName ───────────────────────────────────────────────────────

    [Fact]
    public void DisplayName_ProducesReadableText()
    {
        Assert.Equal("Maw-affiliated",         ScentTag.MawAffiliated.DisplayName());
        Assert.Equal("Recently killed",        ScentTag.RecentlyKilled.DisplayName());
        Assert.Equal("Inheritor-affiliated",   ScentTag.InheritorAffiliated.DisplayName());
        Assert.Equal("Frightened",             ScentTag.Frightened.DisplayName());
    }

    [Fact]
    public void IsNarrative_TrueForFactionTags_FalseForRuntimeTags()
    {
        Assert.True(ScentTag.MawAffiliated.IsNarrative());
        Assert.True(ScentTag.MerchantAffiliated.IsNarrative());
        Assert.False(ScentTag.RecentlyKilled.IsNarrative());
        Assert.False(ScentTag.Frightened.IsNarrative());
        Assert.False(ScentTag.None.IsNarrative());
    }

    // ── Helpers ───────────────────────────────────────────────────────────

    private NpcActor MakeResidentNpc(string clade, string species, string factionId)
    {
        var resident = new ResidentTemplateDef
        {
            Id      = "test_npc",
            Name    = "Test NPC",
            Clade   = clade,
            Species = species,
            Faction = factionId,
            Hp      = 20,
        };
        return new NpcActor(resident) { Id = 1 };
    }
}
