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

namespace Theriapolis.Tests.Rules;

/// <summary>
/// Phase 6.5 M4 — hybrid character creation. Validates the Sire/Dam
/// picker logic, blended ability mods, dominant-parent presentation,
/// universal hybrid detriments (Scent Dysphoria save DC, Social Stigma
/// penalty, Medical Incompatibility healing scale), and cross-clade
/// enforcement.
/// </summary>
public sealed class HybridCharacterTests
{
    private readonly ContentResolver _content = new(new ContentLoader(TestHelpers.DataDirectory));

    // ── Validation ────────────────────────────────────────────────────────

    [Fact]
    public void TryBuildHybrid_RejectsSameClade()
    {
        var b = MakeHybridBuilder("canidae", "wolf", "canidae", "fox");
        bool ok = b.TryBuildHybrid(_content.Items, out _, out string err);
        Assert.False(ok);
        Assert.Contains("different clades", err.ToLowerInvariant());
    }

    [Fact]
    public void TryBuildHybrid_RejectsSpeciesNotInClade()
    {
        var b = MakeHybridBuilder("canidae", "wolf", "leporidae", "wolf");
        // dam species "wolf" doesn't belong to leporidae
        bool ok = b.TryBuildHybrid(_content.Items, out _, out string err);
        Assert.False(ok);
        Assert.Contains("clade", err.ToLowerInvariant());
    }

    [Fact]
    public void TryBuildHybrid_RejectsMissingSire()
    {
        var b = NewBuilderWithClassAndSkills();
        b.HybridDamClade   = _content.Clades["leporidae"];
        b.HybridDamSpecies = _content.Species["rabbit"];
        bool ok = b.TryBuildHybrid(_content.Items, out _, out string err);
        Assert.False(ok);
        Assert.Contains("sire", err.ToLowerInvariant());
    }

    [Fact]
    public void TryBuildHybrid_RejectsMissingClass()
    {
        var b = MakeHybridBuilder("canidae", "wolf", "leporidae", "rabbit");
        b.ClassDef = null;  // strip
        bool ok = b.TryBuildHybrid(_content.Items, out _, out string err);
        Assert.False(ok);
        Assert.Contains("class", err.ToLowerInvariant());
    }

    // ── Build happy path ──────────────────────────────────────────────────

    [Fact]
    public void TryBuildHybrid_ProducesHybridCharacterWithGenealogy()
    {
        var b = MakeHybridBuilder("canidae", "wolf", "leporidae", "rabbit");
        bool ok = b.TryBuildHybrid(_content.Items, out var c, out string err);
        Assert.True(ok, err);
        Assert.NotNull(c);
        Assert.True(c!.IsHybrid);
        Assert.NotNull(c.Hybrid);
        Assert.Equal("canidae",   c.Hybrid!.SireClade);
        Assert.Equal("wolf",      c.Hybrid.SireSpecies);
        Assert.Equal("leporidae", c.Hybrid.DamClade);
        Assert.Equal("rabbit",    c.Hybrid.DamSpecies);
        Assert.Equal(ParentLineage.Sire, c.Hybrid.DominantParent);  // default
        Assert.False(c.Hybrid.PassingActive);
    }

    [Fact]
    public void TryBuildHybrid_DominantParentDrivesPresentingClade()
    {
        var b = MakeHybridBuilder("canidae", "wolf", "leporidae", "rabbit");
        b.HybridDominantParent = ParentLineage.Dam;
        bool ok = b.TryBuildHybrid(_content.Items, out var c, out _);
        Assert.True(ok);
        // The character's primary Clade/Species should track the dominant
        // parent so existing systems keying off Character.Clade get the
        // presenting clade.
        Assert.Equal("leporidae", c!.Clade.Id);
        Assert.Equal("rabbit",    c.Species.Id);
        Assert.Equal("leporidae", c.Hybrid!.PresentingCladeId);
    }

    [Fact]
    public void TryBuildHybrid_BlendsAbilityMods()
    {
        // Wolf-Folk Sire:
        //   canidae clade: +1 CON, +1 WIS
        //   wolf species:  +1 STR
        // × Rabbit-Folk Dam:
        //   leporidae clade: -1 STR, +2 DEX
        //   rabbit species:  +1 WIS
        // Base 10 across the board.
        var b = MakeHybridBuilder("canidae", "wolf", "leporidae", "rabbit");
        b.BaseAbilities = new AbilityScores(10, 10, 10, 10, 10, 10);
        bool ok = b.TryBuildHybrid(_content.Items, out var c, out _);
        Assert.True(ok);
        // Net STR: 10 + 1 (wolf) - 1 (leporid) = 10.
        // Net DEX: 10 + 2 (leporid) = 12.
        // Net CON: 10 + 1 (canid) = 11.
        // Net WIS: 10 + 1 (canid) + 1 (rabbit) = 12.
        Assert.Equal(10, c!.Abilities.STR);
        Assert.Equal(12, c.Abilities.DEX);
        Assert.Equal(11, c.Abilities.CON);
        Assert.Equal(12, c.Abilities.WIS);
    }

    // ── Cross-clade pairings smoke ────────────────────────────────────────

    [Theory]
    [InlineData("canidae",   "wolf",       "felidae",   "lion")]
    [InlineData("canidae",   "coyote",     "leporidae", "hare")]
    [InlineData("ursidae",   "brown_bear", "bovidae",   "bull")]
    [InlineData("felidae",   "leopard",    "cervidae",  "deer")]
    [InlineData("mustelidae","badger",     "leporidae", "rabbit")]
    [InlineData("bovidae",   "sheep",      "cervidae",  "elk")]
    [InlineData("leporidae", "rabbit",     "felidae",   "housecat")]
    public void TryBuildHybrid_AllCrossCladeCombinationsValid(
        string sireClade, string sireSpecies, string damClade, string damSpecies)
    {
        var b = MakeHybridBuilder(sireClade, sireSpecies, damClade, damSpecies);
        bool ok = b.TryBuildHybrid(_content.Items, out var c, out string err);
        Assert.True(ok, err);
        Assert.True(c!.MaxHp > 0);
        Assert.True(c.IsAlive);
        Assert.True(c.IsHybrid);
    }

    // ── HybridDetriments ──────────────────────────────────────────────────

    [Fact]
    public void HybridDetriments_HaveDocumentedConstants()
    {
        Assert.Equal(10,    HybridDetriments.ScentDysphoriaSaveDc);
        Assert.Equal(-2,    HybridDetriments.SocialStigmaFirstCheckPenalty);
        Assert.Equal(0.75f, HybridDetriments.MedicalIncompatibilityMultiplier);
        Assert.True(HybridDetriments.IllegibleBodyLanguagePenalty);
    }

    [Fact]
    public void ScaleHealForHybrid_AppliesMultiplierToHybrids()
    {
        var hybrid = MakeHybrid();
        Assert.Equal(6, HybridDetriments.ScaleHealForHybrid(hybrid, 8));   // floor(8*0.75)=6
        Assert.Equal(3, HybridDetriments.ScaleHealForHybrid(hybrid, 4));   // floor(4*0.75)=3
        // Min 1 floor: a hybrid healed for 1 raw still gets 1.
        Assert.Equal(1, HybridDetriments.ScaleHealForHybrid(hybrid, 1));
    }

    [Fact]
    public void ScaleHealForHybrid_NoOpForPurebreds()
    {
        var purebred = MakePurebred();
        Assert.Equal(8, HybridDetriments.ScaleHealForHybrid(purebred, 8));
        Assert.Equal(1, HybridDetriments.ScaleHealForHybrid(purebred, 1));
    }

    [Fact]
    public void ScaleHealForHybrid_PassesThroughZeroAndNegative()
    {
        var hybrid = MakeHybrid();
        Assert.Equal(0,  HybridDetriments.ScaleHealForHybrid(hybrid, 0));
        Assert.Equal(-3, HybridDetriments.ScaleHealForHybrid(hybrid, -3));
    }

    // ── Save round-trip ───────────────────────────────────────────────────

    [Fact]
    public void Hybrid_RoundTripsThroughCharacterCodec()
    {
        var c = MakeHybrid();
        c.Hybrid!.PassingActive = true;
        c.Hybrid.NpcsWhoKnow.Add(42);
        c.Hybrid.NpcsWhoKnow.Add(99);

        var snap = Theriapolis.Core.Persistence.CharacterCodec.Capture(c);
        Assert.NotNull(snap.Hybrid);
        Assert.Equal("canidae",   snap.Hybrid!.SireClade);
        Assert.Equal("leporidae", snap.Hybrid.DamClade);
        Assert.True(snap.Hybrid.PassingActive);
        Assert.Equal(2, snap.Hybrid.NpcsWhoKnow.Length);

        var restored = Theriapolis.Core.Persistence.CharacterCodec.Restore(snap, _content);
        Assert.NotNull(restored.Hybrid);
        Assert.Equal("canidae",   restored.Hybrid!.SireClade);
        Assert.Equal("wolf",      restored.Hybrid.SireSpecies);
        Assert.Equal("leporidae", restored.Hybrid.DamClade);
        Assert.Equal("rabbit",    restored.Hybrid.DamSpecies);
        Assert.True(restored.Hybrid.PassingActive);
        Assert.Contains(42, restored.Hybrid.NpcsWhoKnow);
        Assert.Contains(99, restored.Hybrid.NpcsWhoKnow);
    }

    [Fact]
    public void Purebred_RoundTripDoesNotEmitHybridSection()
    {
        var c = MakePurebred();
        var snap = Theriapolis.Core.Persistence.CharacterCodec.Capture(c);
        Assert.Null(snap.Hybrid);
        var restored = Theriapolis.Core.Persistence.CharacterCodec.Restore(snap, _content);
        Assert.Null(restored.Hybrid);
        Assert.False(restored.IsHybrid);
    }

    [Fact]
    public void Hybrid_RoundTripsThroughBinarySaveCodec()
    {
        var c = MakeHybrid();
        c.Hybrid!.PassingActive = true;

        var header = new Theriapolis.Core.Persistence.SaveHeader
        {
            Version       = Theriapolis.Core.C.SAVE_SCHEMA_VERSION,
            WorldSeedHex  = "0xFEED",
        };
        var body = new Theriapolis.Core.Persistence.SaveBody
        {
            PlayerCharacter = Theriapolis.Core.Persistence.CharacterCodec.Capture(c),
        };
        body.Player.Id   = 1;
        body.Player.Name = "Hybrid";

        var bytes = Theriapolis.Core.Persistence.SaveCodec.Serialize(header, body);
        var (h2, body2) = Theriapolis.Core.Persistence.SaveCodec.Deserialize(bytes);

        Assert.Equal(header.Version, h2.Version);
        Assert.NotNull(body2.PlayerCharacter);
        Assert.NotNull(body2.PlayerCharacter!.Hybrid);
        Assert.Equal("canidae",   body2.PlayerCharacter.Hybrid!.SireClade);
        Assert.Equal("leporidae", body2.PlayerCharacter.Hybrid.DamClade);
        Assert.True(body2.PlayerCharacter.Hybrid.PassingActive);
    }

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

    private CharacterBuilder NewBuilderWithClassAndSkills()
    {
        var b = new CharacterBuilder
        {
            ClassDef     = _content.Classes["fangsworn"],
            Background   = _content.Backgrounds["pack_raised"],
            BaseAbilities = new AbilityScores(15, 14, 13, 12, 10, 8),
            Name         = "Test",
            IsHybridOrigin = true,
        };
        int n = b.ClassDef.SkillsChoose;
        foreach (var raw in b.ClassDef.SkillOptions)
        {
            if (b.ChosenClassSkills.Count >= n) break;
            try { b.ChosenClassSkills.Add(SkillIdExtensions.FromJson(raw)); } catch { }
        }
        return b;
    }

    private CharacterBuilder MakeHybridBuilder(
        string sireClade, string sireSpecies,
        string damClade,  string damSpecies)
    {
        var b = NewBuilderWithClassAndSkills();
        b.HybridSireClade   = _content.Clades[sireClade];
        b.HybridSireSpecies = _content.Species[sireSpecies];
        b.HybridDamClade    = _content.Clades[damClade];
        b.HybridDamSpecies  = _content.Species[damSpecies];
        return b;
    }

    private Character MakeHybrid()
    {
        var b = MakeHybridBuilder("canidae", "wolf", "leporidae", "rabbit");
        Assert.True(b.TryBuildHybrid(_content.Items, out var c, out _));
        return c!;
    }

    private Character MakePurebred()
    {
        var b = new CharacterBuilder
        {
            Clade        = _content.Clades["canidae"],
            Species      = _content.Species["wolf"],
            ClassDef     = _content.Classes["fangsworn"],
            Background   = _content.Backgrounds["pack_raised"],
            BaseAbilities = new AbilityScores(15, 14, 13, 12, 10, 8),
            Name         = "Test",
        };
        int n = b.ClassDef.SkillsChoose;
        foreach (var raw in b.ClassDef.SkillOptions)
        {
            if (b.ChosenClassSkills.Count >= n) break;
            try { b.ChosenClassSkills.Add(SkillIdExtensions.FromJson(raw)); } catch { }
        }
        return b.Build(_content.Items);
    }
}
