using Theriapolis.Core;
using Theriapolis.Core.Data;
using Theriapolis.Core.Rules.Reputation;
using Xunit;

namespace Theriapolis.Tests.Reputation;

/// <summary>
/// Phase 6 M2 — opposition matrix application.
///
/// The plan §I-2 spells out the exact multipliers; this suite verifies
/// the cascade fires in both directions (gains and losses) and stays
/// inside the clamp range.
/// </summary>
public sealed class FactionOppositionTests
{
    private static IReadOnlyDictionary<string, FactionDef> LoadFactions()
        => new ContentResolver(new ContentLoader(TestHelpers.DataDirectory)).Factions;

    [Fact]
    public void GainWithInheritors_CascadesIntoOppositionLosses()
    {
        var factions = LoadFactions();
        var standing = new FactionStanding();

        var applied = standing.Apply("inheritors", 10, factions);

        // Per the doc: +10 with Inheritors should yield -5 Enforcers,
        // -2 Thorn Council, -3 Hybrid Underground, -3 Unsheathed.
        Assert.Equal( 10, standing.Get("inheritors"));
        Assert.Equal( -5, standing.Get("covenant_enforcers"));
        Assert.Equal( -2, standing.Get("thorn_council"));
        Assert.Equal( -3, standing.Get("hybrid_underground"));
        Assert.Equal( -3, standing.Get("unsheathed"));
        Assert.Equal(  0, standing.Get("merchant_guilds"));   // multiplier 0

        // The applied list reports every actually-changed faction.
        Assert.Contains(applied, t => t.FactionId == "inheritors"        && t.Delta ==  10);
        Assert.Contains(applied, t => t.FactionId == "covenant_enforcers" && t.Delta == -5);
    }

    [Fact]
    public void LossWithInheritors_CascadesIntoOppositionGains()
    {
        var factions = LoadFactions();
        var standing = new FactionStanding();
        standing.Apply("inheritors", -20, factions);

        // -20 × -0.5 = +10 with Enforcers.
        Assert.Equal(-20, standing.Get("inheritors"));
        Assert.Equal( 10, standing.Get("covenant_enforcers"));
        Assert.Equal(  4, standing.Get("thorn_council"));
        Assert.Equal(  6, standing.Get("hybrid_underground"));
        Assert.Equal(  6, standing.Get("unsheathed"));
    }

    [Fact]
    public void Standing_IsClampedToRepRange()
    {
        var factions = LoadFactions();
        var standing = new FactionStanding();
        standing.Apply("inheritors", 200, factions);
        Assert.Equal(C.REP_MAX, standing.Get("inheritors"));
        // Cascaded -100 is below the floor — verify clamping.
        Assert.Equal(C.REP_MIN, standing.Get("covenant_enforcers"));
    }

    [Fact]
    public void ZeroDelta_DoesNothing()
    {
        var factions = LoadFactions();
        var standing = new FactionStanding();
        standing.Set("inheritors", 25);
        standing.Apply("inheritors", 0, factions);
        Assert.Equal(25, standing.Get("inheritors"));
        Assert.Equal( 0, standing.Get("covenant_enforcers"));
    }

    [Fact]
    public void UnknownFaction_NoCascade()
    {
        var factions = LoadFactions();
        var standing = new FactionStanding();
        // No throw; standing accumulates against the unknown id.
        standing.Apply("not_a_real_faction", 10, factions);
        Assert.Equal(10, standing.Get("not_a_real_faction"));
        Assert.Equal( 0, standing.Get("covenant_enforcers"));
    }

    [Fact]
    public void SubmitEvent_AppliesFactionAndPersonal()
    {
        var factions = LoadFactions();
        var rep      = new PlayerReputation();

        rep.Submit(new RepEvent
        {
            Kind      = RepEventKind.Quest,
            FactionId = "inheritors",
            RoleTag   = "test.someone",
            Magnitude = 10,
            Note      = "test event",
        }, factions);

        Assert.Equal(10,  rep.Factions.Get("inheritors"));
        Assert.Equal(-5,  rep.Factions.Get("covenant_enforcers"));
        Assert.Equal(10,  rep.PersonalFor("test.someone").Score);
        Assert.Single(rep.Ledger.Entries);
    }
}
