using NeonSprawl.Server.Game.Items;
using NeonSprawl.Server.Game.Mastery;
using NeonSprawl.Server.Game.Skills;

namespace NeonSprawl.Server.Game.Gathering;

/// <summary>
/// Orchestrates gather side effects: capacity pre-check, inventory grant, skill XP, depletion commit (NEO-62).
/// Wired from <see cref="NeonSprawl.Server.Game.Interaction.InteractionApi"/> on <c>resource_node</c> interact (NEO-63).
/// NEO-64 telemetry hook sites: <see cref="TryGather"/> (<c>resource_gathered</c>, <c>gather_node_depleted</c>).
/// </summary>
public static class GatherOperations
{
    private static readonly GatherGrantApplied[] EmptyGrants = [];

    /// <summary>
    /// Resolves one gather for <paramref name="playerId"/> at <paramref name="interactableId"/>.
    /// Mutations commit in order: inventory add → XP grant → depletion decrement (last).
    /// </summary>
    public static GatherResult TryGather(
        string playerId,
        string interactableId,
        IResourceNodeDefinitionRegistry nodeRegistry,
        IItemDefinitionRegistry itemRegistry,
        IPlayerInventoryStore inventoryStore,
        ISkillDefinitionRegistry skillRegistry,
        IPlayerSkillProgressionStore xpStore,
        ISkillLevelCurve levelCurve,
        PerkUnlockEngine perkUnlockEngine,
        IResourceNodeInstanceStore instanceStore)
    {
        var key = ResourceNodeInstanceIds.Normalize(interactableId);
        if (key.Length == 0 ||
            !nodeRegistry.TryGetDefinition(key, out var definition) ||
            !nodeRegistry.TryGetYield(key, out var yield))
        {
            return Deny(GatherReasonCodes.UnknownNode, null);
        }

        if (!instanceStore.TryEnsureInitialized(key, definition.MaxGathers))
        {
            throw new InvalidOperationException(
                $"Failed to initialize resource node instance '{key}' with maxGathers {definition.MaxGathers}.");
        }

        if (!instanceStore.TryGetRemainingGathers(key, out var capacity) || capacity.RemainingGathers <= 0)
        {
            var remaining = capacity.RemainingGathers;
            return Deny(GatherReasonCodes.NodeDepleted, remaining);
        }

        var inventoryOutcome = PlayerInventoryOperations.TryAddStack(
            playerId,
            yield.ItemId,
            yield.Quantity,
            itemRegistry,
            inventoryStore);

        if (inventoryOutcome.Kind == PlayerInventoryMutationKind.StoreMissing)
        {
            return Deny(GatherReasonCodes.InventoryStoreMissing, capacity.RemainingGathers);
        }

        if (inventoryOutcome.Kind == PlayerInventoryMutationKind.Denied)
        {
            return Deny(inventoryOutcome.ReasonCode ?? GatherReasonCodes.InventoryFull, capacity.RemainingGathers);
        }

        var xpOutcome = SkillProgressionGrantOperations.TryApplyGrant(
            playerId,
            GatherSkillXpConstants.SalvageSkillId,
            GatherSkillXpConstants.ActivityXpPerResourceNodeGather,
            GatherSkillXpConstants.ActivitySourceKind,
            skillRegistry,
            xpStore,
            levelCurve,
            perkUnlockEngine);

        if (xpOutcome.Kind != SkillProgressionGrantApplyKind.Granted)
        {
            var xpReason = xpOutcome.Kind == SkillProgressionGrantApplyKind.ProgressionStoreMissing
                ? GatherReasonCodes.ProgressionStoreMissing
                : xpOutcome.Response?.ReasonCode ?? GatherReasonCodes.ProgressionStoreMissing;

            _ = PlayerInventoryOperations.TryRemoveStack(
                playerId,
                yield.ItemId,
                yield.Quantity,
                itemRegistry,
                inventoryStore);

            return Deny(xpReason, capacity.RemainingGathers);
        }

        var depletionOutcome = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(
            key,
            nodeRegistry,
            instanceStore);

        if (depletionOutcome.Kind != ResourceNodeInstanceMutationKind.Applied)
        {
            _ = PlayerInventoryOperations.TryRemoveStack(
                playerId,
                yield.ItemId,
                yield.Quantity,
                itemRegistry,
                inventoryStore);

            var depletedRemaining = depletionOutcome.Snapshot?.RemainingGathers ?? 0;
            return Deny(
                depletionOutcome.ReasonCode ?? GatherReasonCodes.NodeDepleted,
                depletedRemaining);
        }

        var remainingAfterGather = depletionOutcome.Snapshot!.Value.RemainingGathers;

        // --- Telemetry hook site (NEO-64): future E9.M1 catalog event `resource_gathered` ---
        // TODO(E9.M1): catalog emit — on every successful gather (inventory + XP + depletion committed).
        // Planned payload fields: playerId, interactableId (node key), itemId, quantity (GatherGrantApplied),
        // skillId, xpAmount, sourceKind (GatherXpGrantSummary), remainingGathers after commit.
        // No ingest or ILogger here (comments-only). InteractionApi delegates here (no duplicate hooks).

        if (remainingAfterGather == 0)
        {
            // --- Telemetry hook site (NEO-64): future E9.M1 catalog event `gather_node_depleted` ---
            // TODO(E9.M1): catalog emit — once when this successful gather drives remainingGathers to 0
            // (not on pre-gather node_depleted deny). Planned payload: interactableId, optional playerId
            // (last gatherer), maxGathers from definition when wiring E9.M1.
        }

        return new GatherResult(
            Success: true,
            ReasonCode: null,
            GrantsApplied: [new GatherGrantApplied(yield.ItemId, yield.Quantity)],
            RemainingGathers: remainingAfterGather,
            XpGrantSummary: new GatherXpGrantSummary(
                GatherSkillXpConstants.SalvageSkillId,
                GatherSkillXpConstants.ActivityXpPerResourceNodeGather,
                GatherSkillXpConstants.ActivitySourceKind));
    }

    private static GatherResult Deny(string reasonCode, int? remainingGathers) =>
        new(
            Success: false,
            ReasonCode: reasonCode,
            GrantsApplied: EmptyGrants,
            RemainingGathers: remainingGathers,
            XpGrantSummary: null);
}
