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

namespace Theriapolis.Tests.Reputation;

/// <summary>
/// Phase 6 M2 — round-trip <see cref="PlayerReputation"/> through the
/// codec. Every field must survive Capture → Restore identically. Plus
/// the SaveCodec serialization path itself: write a SaveBody with rep
/// data, parse it back, compare.
/// </summary>
public sealed class ReputationRoundTripTests
{
    [Fact]
    public void CaptureRestore_FactionStandings_RoundTrips()
    {
        var factions = new ContentResolver(new ContentLoader(TestHelpers.DataDirectory)).Factions;
        var rep = new PlayerReputation();
        rep.Factions.Apply("inheritors", 30, factions);
        rep.Factions.Apply("merchant_guilds", 50, factions);

        var snap = ReputationCodec.Capture(rep);
        var restored = ReputationCodec.Restore(snap);

        Assert.Equal(rep.Factions.Get("inheritors"),         restored.Factions.Get("inheritors"));
        Assert.Equal(rep.Factions.Get("covenant_enforcers"), restored.Factions.Get("covenant_enforcers"));
        Assert.Equal(rep.Factions.Get("merchant_guilds"),    restored.Factions.Get("merchant_guilds"));
    }

    [Fact]
    public void CaptureRestore_PersonalDispositions_RoundTrip()
    {
        var rep = new PlayerReputation();
        var pd  = rep.PersonalFor("millhaven.innkeeper");
        pd.Score    = 25;
        pd.Trust    = TrustLevel.Familiar;
        pd.Betrayed = false;
        pd.Memory.Add("saved-her-kit");
        pd.Memory.Add("paid-for-the-window");
        pd.Log.Add(new RepEvent { Kind = RepEventKind.Aid, RoleTag = "millhaven.innkeeper",
                                   Magnitude = 10, Note = "first" });

        var snap = ReputationCodec.Capture(rep);
        var restored = ReputationCodec.Restore(snap);

        Assert.True(restored.Personal.ContainsKey("millhaven.innkeeper"));
        var rPd = restored.Personal["millhaven.innkeeper"];
        Assert.Equal(25, rPd.Score);
        Assert.Equal(TrustLevel.Familiar, rPd.Trust);
        Assert.Contains("saved-her-kit",      rPd.Memory);
        Assert.Contains("paid-for-the-window", rPd.Memory);
        Assert.Single(rPd.Log);
        Assert.Equal("first", rPd.Log[0].Note);
    }

    [Fact]
    public void CaptureRestore_Ledger_RoundTrips()
    {
        var factions = new ContentResolver(new ContentLoader(TestHelpers.DataDirectory)).Factions;
        var rep = new PlayerReputation();
        rep.Submit(new RepEvent { Kind = RepEventKind.Quest,    FactionId = "inheritors", Magnitude =  10, Note = "a" }, factions);
        rep.Submit(new RepEvent { Kind = RepEventKind.Betrayal, FactionId = "thorn_council", Magnitude = -25, Note = "b" }, factions);

        var snap     = ReputationCodec.Capture(rep);
        var restored = ReputationCodec.Restore(snap);

        Assert.Equal(rep.Ledger.Count, restored.Ledger.Count);
        Assert.Equal(rep.Ledger.Entries[0].Note, restored.Ledger.Entries[0].Note);
        Assert.Equal(rep.Ledger.Entries[1].Kind, restored.Ledger.Entries[1].Kind);
    }

    [Fact]
    public void SaveCodec_RoundTripsReputationState()
    {
        var factions = new ContentResolver(new ContentLoader(TestHelpers.DataDirectory)).Factions;

        var body = new SaveBody();
        body.Player.Name = "Tester";
        body.Player.PositionX = 100;
        body.Player.PositionY = 200;

        // Populate reputation state.
        var rep = new PlayerReputation();
        rep.Submit(new RepEvent { Kind = RepEventKind.Quest, FactionId = "inheritors", Magnitude = 10 }, factions);
        var pd = rep.PersonalFor("millhaven.constable_fenn");
        pd.Score = 7;
        pd.Memory.Add("met-at-the-magistrate");
        body.ReputationState = ReputationCodec.Capture(rep);

        var header = new SaveHeader { Version = C.SAVE_SCHEMA_VERSION, WorldSeedHex = "0xCAFE" };
        var bytes = SaveCodec.Serialize(header, body);
        var (h2, b2) = SaveCodec.Deserialize(bytes);

        Assert.Equal(C.SAVE_SCHEMA_VERSION, h2.Version);
        Assert.Equal( 10, b2.ReputationState.FactionStandings["inheritors"]);
        Assert.Equal( -5, b2.ReputationState.FactionStandings["covenant_enforcers"]);
        Assert.Single(b2.ReputationState.Personal);
        Assert.Equal("millhaven.constable_fenn", b2.ReputationState.Personal[0].RoleTag);
        Assert.Contains("met-at-the-magistrate",  b2.ReputationState.Personal[0].MemoryTags);
        Assert.Single(b2.ReputationState.Ledger);
    }
}
