using Theriapolis.Core.Entities;
using Theriapolis.Core.Rules.Reputation;
using Theriapolis.Core.Rules.Stats;
using Theriapolis.Core.Util;

namespace Theriapolis.Core.Rules.Character;

/// <summary>
/// Phase 6.5 M5 — hybrid passing detection.
///
/// When a hybrid PC (with <see cref="HybridState.PassingActive"/> = true)
/// interacts with an NPC who has a scent-detection capability (Canid clade
/// "Superior Scent" or any Scent-Broker class), the NPC rolls a WIS save
/// at <see cref="C.HYBRID_DETECTION_DC"/> against the PC's CHA Deception
/// counter-roll.
///
/// Outcomes:
///   <see cref="DetectionResult.Pass"/>   — PC remains hidden; treated as presenting clade.
///   <see cref="DetectionResult.Detected"/> — NPC sees through the cover; their bias
///                                            profile's <c>HybridBias</c> applies from now on.
///
/// Once detected by a specific NPC, the flag is permanent for that NPC
/// (per <c>theriapolis-rpg-clades.md</c> "Optional: Passing"). Other NPCs
/// roll independently — no per-settlement propagation in M5 (Phase 8
/// scent simulation may extend).
/// </summary>
public static class PassingCheck
{
    /// <summary>
    /// Roll detection for one NPC × PC interaction. Determinism:
    /// <c>seed = worldSeed ^ C.RNG_PASSING ^ npcId ^ encounterIdx</c>.
    /// Same seed → same outcome; mid-game saves resume identically.
    ///
    /// <paramref name="npcMemoryFlags"/> is consulted upfront — if the NPC
    /// already detected this PC in a prior interaction, the result is
    /// <see cref="DetectionResult.Detected"/> with no fresh roll. The
    /// caller writes the <c>"knows_hybrid"</c> tag into the NPC's
    /// <see cref="PersonalDisposition.Memory"/> on first detection.
    /// </summary>
    public static DetectionResult Roll(
        Character pc,
        NpcActor npc,
        ICollection<string> npcMemoryFlags,
        ulong seed)
    {
        // Non-hybrids never trigger detection.
        if (pc.Hybrid is null) return DetectionResult.NotApplicable;

        // Already detected? Permanent for this NPC.
        if (npcMemoryFlags.Contains("knows_hybrid"))
            return DetectionResult.PreviouslyDetected;

        // Not actively passing? The PC isn't trying to hide; detection
        // happens trivially. Marks the NPC as knowing.
        if (!pc.Hybrid.PassingActive)
            return DetectionResult.NotPassing;

        // Deep-cover scent mask suppresses all detection — even Superior Scent.
        if (pc.Hybrid.ActiveMaskTier == ScentMaskTier.DeepCover)
            return DetectionResult.MaskSuppressed;

        // Military mask: auto-suppress for non-Canid NPCs; Canids still roll
        // (Superior Scent overrides anything below deep cover).
        bool npcHasSuperiorScent = NpcHasSuperiorScent(npc);
        if (pc.Hybrid.ActiveMaskTier == ScentMaskTier.Military && !npcHasSuperiorScent)
            return DetectionResult.MaskSuppressed;

        // The detection mechanic: NPC WIS save vs the PC's CHA Deception
        // counter-roll. NPCs without scent capability never detect.
        if (!CanNpcDetectScent(npc)) return DetectionResult.NoCapability;

        var rng = new SeededRng(seed);
        // NPC rolls 1d20 + WIS mod against DC = pc Deception DC + (basic mask
        // gives PC advantage, which we model as +5 to the DC the NPC must beat).
        int npcWis = NpcWisMod(npc);
        int npcRoll  = (int)(rng.NextUInt64() % 20) + 1;
        int npcTotal = npcRoll + npcWis;

        int pcCha     = pc.Abilities.ModFor(AbilityId.CHA);
        int pcProf    = pc.ProficiencyBonus;
        int pcDecRoll = (int)(rng.NextUInt64() % 20) + 1;
        // Proficient in Deception? Add prof bonus; otherwise just CHA mod.
        bool deceptionProf = pc.SkillProficiencies.Contains(SkillId.Deception);
        int pcTotal   = pcDecRoll + pcCha + (deceptionProf ? pcProf : 0);

        // Basic mask shifts the contest in PC's favour (advantage = +5
        // approximation for a single non-rerolled compare).
        if (pc.Hybrid.ActiveMaskTier == ScentMaskTier.Basic) pcTotal += 5;

        // NPC must meet or exceed the DC AND beat the PC's deception
        // contest to detect. (Either failing means PC stays hidden.)
        bool npcMeetsDc   = npcTotal >= C.HYBRID_DETECTION_DC;
        bool pcBeatsCheck = pcTotal  >= C.HYBRID_DECEPTION_DC;
        bool detected    = npcMeetsDc && !pcBeatsCheck;

        return detected ? DetectionResult.Detected : DetectionResult.Pass;
    }

    /// <summary>
    /// True for NPCs who have *any* scent-reading capability. Phase 6.5 M5:
    /// canid-clade NPCs (Superior Scent) and scent-broker-flavoured roles.
    /// Generic / non-canid / non-scent-broker NPCs never roll detection.
    /// </summary>
    public static bool CanNpcDetectScent(NpcActor npc)
    {
        if (NpcHasSuperiorScent(npc)) return true;
        // Phase 6.5 M5 simplification: non-canid NPCs don't detect by
        // default. A Phase 8 scent-broker NPC role could extend this with
        // a tag check on `npc.Resident?.Traits` — out of scope for M5.
        return false;
    }

    /// <summary>True if the NPC's clade is Canid (granting Superior Scent).</summary>
    public static bool NpcHasSuperiorScent(NpcActor npc)
    {
        string? clade = npc.Resident?.Clade;
        return string.Equals(clade, "canidae", System.StringComparison.OrdinalIgnoreCase);
    }

    /// <summary>NPC's WIS modifier — derived from template if present, otherwise default 0.</summary>
    private static int NpcWisMod(NpcActor npc)
    {
        if (npc.Template is null) return 0;
        // Templates store ability scores as a string-keyed dict on the def.
        return npc.Template.AbilityScores.TryGetValue("WIS", out int wis)
            ? AbilityScores.Mod(wis)
            : 0;
    }

    /// <summary>
    /// Convenience: roll detection AND apply side effects on a positive
    /// outcome. Writes the <c>"knows_hybrid"</c> memory tag to the NPC's
    /// <see cref="PersonalDisposition.Memory"/>, mirrors the discovery in
    /// <see cref="HybridState.NpcsWhoKnow"/>, and appends a
    /// <see cref="RepEventKind.HybridDetected"/> event to the ledger.
    ///
    /// Returns the same <see cref="DetectionResult"/> the underlying
    /// <see cref="Roll"/> produced. Call sites that want to inspect the
    /// outcome before applying side effects can use <see cref="Roll"/>
    /// directly; this helper is the common-case one-liner.
    /// </summary>
    public static DetectionResult RollAndApply(
        Character pc,
        NpcActor npc,
        Reputation.PlayerReputation rep,
        long worldClockSeconds,
        ulong seed)
    {
        if (pc.Hybrid is null) return DetectionResult.NotApplicable;

        // Pull (or seed) the personal-disposition record so the roll sees
        // the existing memory state.
        var personal = string.IsNullOrEmpty(npc.RoleTag)
            ? null
            : rep.PersonalFor(npc.RoleTag);
        var memoryFlags = (ICollection<string>?)personal?.Memory ?? new HashSet<string>();

        var result = Roll(pc, npc, memoryFlags, seed);

        if (result == DetectionResult.Detected || result == DetectionResult.NotPassing)
        {
            // Write the detection through to all the places that care.
            pc.Hybrid.NpcsWhoKnow.Add(npc.Id);
            personal?.Memory.Add("knows_hybrid");

            // Log a per-NPC HybridDetected event. Personal-only — no
            // faction propagation in M5 (Phase 8 scent simulation can
            // extend). Magnitude is 0 because the *bias* shift is
            // applied via the bias-profile lookup in EffectiveDisposition,
            // not via the personal-disposition delta.
            var ev = new Reputation.RepEvent
            {
                Kind             = Reputation.RepEventKind.HybridDetected,
                RoleTag          = npc.RoleTag ?? "",
                Magnitude        = 0,
                Note             = $"detected hybrid ({pc.Hybrid.SireClade}/{pc.Hybrid.DamClade})",
                TimestampSeconds = worldClockSeconds,
            };
            rep.Ledger.Append(ev);
            personal?.Apply(ev);
        }

        return result;
    }
}

/// <summary>
/// Phase 6.5 M5 — outcome of one detection roll. The caller (typically the
/// dialogue runner) inspects this to apply the appropriate side effects.
/// </summary>
public enum DetectionResult : byte
{
    /// <summary>PC is not a hybrid; no detection mechanic applies.</summary>
    NotApplicable,

    /// <summary>Hybrid detected on a prior interaction; flag still set.</summary>
    PreviouslyDetected,

    /// <summary>PC is hybrid but not actively passing — no roll, NPC knows immediately.</summary>
    NotPassing,

    /// <summary>NPC lacks scent-reading capability; passing automatic.</summary>
    NoCapability,

    /// <summary>Active scent mask blocked detection without a roll.</summary>
    MaskSuppressed,

    /// <summary>Detection roll succeeded; NPC sees through the cover.</summary>
    Detected,

    /// <summary>Detection roll failed; PC remains hidden in this interaction.</summary>
    Pass,
}
