using NeonSprawl.Server.Game.Mastery;

namespace NeonSprawl.Server.Game.Skills;

/// <summary>Shared NEO-38 skill XP grant apply (used by HTTP POST and NEO-41 gather interact hook).</summary>
public static class SkillProgressionGrantOperations
{
    /// <summary>
    /// Applies one grant if validation passes and <see cref="IPlayerSkillProgressionStore.TryApplyXpDelta"/> succeeds.
    /// Caller must ensure <paramref name="playerId"/> is already authorized for progression (e.g. known to position state).
    /// </summary>
    public static SkillProgressionGrantApplyOutcome TryApplyGrant(
        string playerId,
        string skillIdRaw,
        int amount,
        string sourceKindRaw,
        ISkillDefinitionRegistry registry,
        IPlayerSkillProgressionStore xpStore,
        ISkillLevelCurve levelCurve,
        PerkUnlockEngine perkUnlockEngine)
    {
        static SkillProgressionGrantResponse Deny(SkillProgressionSnapshotResponse snapshot, string reason) =>
            new()
            {
                Granted = false,
                ReasonCode = reason,
                Progression = snapshot,
                LevelUps = [],
            };

        var beforeSnapshot = SkillProgressionSnapshotApi.BuildSnapshot(playerId, registry, xpStore, levelCurve);

        if (amount <= 0)
        {
            return new SkillProgressionGrantApplyOutcome(
                SkillProgressionGrantApplyKind.Denied,
                Deny(beforeSnapshot, SkillProgressionSnapshotApi.ReasonInvalidAmount));
        }

        var skillLookup = skillIdRaw.Trim();
        if (skillLookup.Length == 0 || !registry.TryGetDefinition(skillLookup, out var def))
        {
            return new SkillProgressionGrantApplyOutcome(
                SkillProgressionGrantApplyKind.Denied,
                Deny(beforeSnapshot, SkillProgressionSnapshotApi.ReasonUnknownSkill));
        }

        var source = sourceKindRaw.Trim();
        var allowed = def.AllowedXpSourceKinds.Any(k =>
            string.Equals(k, source, StringComparison.OrdinalIgnoreCase));
        if (!allowed)
        {
            return new SkillProgressionGrantApplyOutcome(
                SkillProgressionGrantApplyKind.Denied,
                Deny(beforeSnapshot, SkillProgressionSnapshotApi.ReasonSourceKindNotAllowed));
        }

        if (!xpStore.TryApplyXpDelta(playerId, def.Id, amount, out var prevXp, out var newXp))
        {
            return new SkillProgressionGrantApplyOutcome(SkillProgressionGrantApplyKind.ProgressionStoreMissing, null);
        }

        // --- Telemetry hook site (NEO-40): future E9.M1 catalog event `xp_grant` ---
        // No ingest here until the telemetry catalog exists. Planned payload fields: playerId,
        // skillId, amount, sourceKind; optional correlation/request ids when Slice 3 wires callers.

        var prevLevel = levelCurve.LevelFromTotalXp(prevXp);
        var nextLevel = levelCurve.LevelFromTotalXp(newXp);
        var levelUps = new List<SkillLevelUpJson>();
        if (nextLevel > prevLevel)
        {
            // --- Telemetry hook site (NEO-40): future E9.M1 catalog event `level_up` ---
            // Planned fields: playerId, skillId, previousLevel, newLevel, prevXp, newXp (totals before/after this grant). No logging (comments-only).
            // Time-to-first-level-up: needs first-seen / milestone persistence not present here; wire
            // when catalog + storage exist (likely derived from `level_up` or a dedicated milestone).

            levelUps.Add(
                new SkillLevelUpJson
                {
                    SkillId = def.Id,
                    PreviousLevel = prevLevel,
                    NewLevel = nextLevel,
                });
        }

        if (levelUps.Count > 0)
        {
            foreach (var levelUp in levelUps)
            {
                _ = perkUnlockEngine.ReevaluateAfterLevelUp(playerId, levelUp.SkillId, levelUp.NewLevel);
            }
        }

        var afterSnapshot = SkillProgressionSnapshotApi.BuildSnapshot(playerId, registry, xpStore, levelCurve);
        return new SkillProgressionGrantApplyOutcome(
            SkillProgressionGrantApplyKind.Granted,
            new SkillProgressionGrantResponse
            {
                Granted = true,
                Progression = afterSnapshot,
                LevelUps = levelUps,
            });
    }
}
