using System.Collections.Generic;
using System.Linq;
using PolyTrader.Modules.ResolutionFarming.Models;

namespace PolyTrader.Modules.ResolutionFarming.Logic
{
    /// <summary>
    /// Reine Entry-Planung: entscheidet aus akzeptierten Kandidaten, offenen Positionen, Settings und
    /// Bankroll-/Tageszustand, WELCHE Positionen in WELCHER USDC-Größe eröffnet werden – priorisiert
    /// nach Score, unter allen Risiko-Limits (Markt/Cluster/Gesamt), Kill-Switch und Tages-Drossel.
    /// Der laufende Exposure-Zustand wird innerhalb eines Laufs fortgeschrieben, damit auch mehrere
    /// gleichzeitige Öffnungen zusammen die Limits einhalten. Seiteneffektfrei → voll unit-getestet.
    /// </summary>
    public static class FarmingExecutionPlanner
    {
        /// <summary>Kleinste sinnvolle Ordergröße (USDC); darunter wird nicht eröffnet.</summary>
        public const decimal MinOrderUsd = 1.0m;

        public static List<(RfCandidate candidate, decimal sizeUsd)> Plan(
            IReadOnlyList<RfCandidate> acceptedCandidates,
            IReadOnlyList<RfPosition> openPositions,
            RfSettings settings,
            decimal bankrollUsd,
            int newPositionsToday,
            decimal realizedDailyPnlUsd)
        {
            var plan = new List<(RfCandidate, decimal)>();

            // Kill-Switch: bei erreichtem Tagesverlust nichts Neues eröffnen.
            if (FarmingRiskEngine.ShouldKill(realizedDailyPnlUsd, settings.DailyLossKillSwitchUsd))
                return plan;

            decimal totalExposure = openPositions.Sum(p => p.AmountUsd);
            var clusterExposure = openPositions
                .GroupBy(p => p.ClusterKey)
                .ToDictionary(g => g.Key, g => g.Sum(p => p.AmountUsd));
            var heldTokens = new HashSet<string>(openPositions.Select(p => p.TokenId));
            int opened = 0;

            foreach (var c in acceptedCandidates.Where(c => c.Accepted).OrderByDescending(c => c.Score))
            {
                if (FarmingRiskEngine.DailyLimitReached(newPositionsToday + opened, settings.MaxNewPositionsPerDay))
                    break;
                if (heldTokens.Contains(c.TokenId)) continue;   // Markt bereits gehalten (oder schon geplant)

                clusterExposure.TryGetValue(c.ClusterKey, out var clusterExp);
                decimal allowed = FarmingRiskEngine.AllowedPositionUsd(
                    settings.MaxPerMarketUsd, settings.MaxPerClusterPct, settings.MaxTotalExposurePct,
                    bankrollUsd, totalExposure, clusterExp, existingMarketExposureUsd: 0m);

                if (allowed < MinOrderUsd) continue;

                plan.Add((c, allowed));
                totalExposure += allowed;
                clusterExposure[c.ClusterKey] = clusterExp + allowed;
                heldTokens.Add(c.TokenId);
                opened++;
            }

            return plan;
        }
    }
}
