using System;
using PolyTraderSharp.Models;

namespace PolyTrader.Modules.CopyTrading.Logic
{
    /// <summary>
    /// Reine, seiteneffektfreie Entscheidungs-/Rechenlogik des Copytrading-BUY-Pfads.
    /// Bewusst aus <c>CopyTradingEngine</c> herausgezogen, damit die risikorelevanten
    /// Berechnungen (Limit-Preis, Zeitfenster, Markt-Budget) vollständig unit-getestet
    /// werden können — CLOB-Eingriffe sind hochkritisch (siehe .agents/rules/clob.md).
    /// Verhalten ist 1:1 aus der Engine übernommen; Änderungen hier immer mit Tests.
    /// </summary>
    public static class CopyTradingRisk
    {
        /// <summary>Harte Preisobergrenze für jede BUY-Order (nie über 0.99 kaufen).</summary>
        public const decimal MaxOrderPriceCap = 0.99m;

        /// <summary>Fester Limit-Aufschlag für HF-Trader (0,5 ¢).</summary>
        public const decimal HfLimitOffset = 0.005m;

        /// <summary>Polymarket-Mindestgröße: wir filtern schon knapp über 5 Shares, um API-Fehler
        /// ("Size lower than the minimum: 5") zu vermeiden.</summary>
        public const decimal MinShares = 5.5m;

        /// <summary>Absolute USDC-Untergrenze pro Order.</summary>
        public const decimal MinUsdc = 0.10m;

        /// <summary>
        /// Liegt eine berechnete Order unter dem Polymarket-Minimum (Shares ODER USDC)?
        /// Solche Mikro-Orders werden vor dem API-Call verworfen.
        /// </summary>
        public static bool IsBelowPolymarketMinimum(decimal shares, decimal usdc) =>
            shares < MinShares || usdc < MinUsdc;

        /// <summary>
        /// Darf ein SELL diese Position schließen? Erlaubt, wenn das Signal vom besitzenden Master
        /// stammt (<paramref name="positionSourceTraderId"/> == <paramref name="signalTraderId"/>)
        /// ODER es ein System-Signal ist (<paramref name="signalTraderId"/> == 0, z.B. Demo-Auto-Close
        /// bei Marktauflösung). K3: ohne die System-Ausnahme würden Copy-Positionen (SourceTraderId &gt; 0)
        /// System-Closes IMMER ablehnen → Demo-Positionen schließen bei Resolution nie.
        /// </summary>
        public static bool IsAuthorizedSell(int signalTraderId, int positionSourceTraderId) =>
            signalTraderId == 0 || positionSourceTraderId == signalTraderId;

        /// <summary>
        /// Limit-Preis einer BUY-Order: HF-Trader bekommen einen festen Aufschlag von 0.005,
        /// sonst einen prozentualen Aufschlag (<paramref name="maxPriceDifferencePct"/>) über dem
        /// Signalpreis. Gedeckelt durch <paramref name="maxBuyPrice"/> und die harte Grenze 0.99.
        /// </summary>
        public static decimal CalculateBuyOrderPrice(decimal signalPrice, bool isHfTrader, decimal maxPriceDifferencePct, decimal maxBuyPrice)
        {
            decimal desired = isHfTrader
                ? signalPrice + HfLimitOffset
                : signalPrice * (1.0m + maxPriceDifferencePct / 100.0m);

            decimal price = Math.Min(desired, maxBuyPrice);
            if (price > MaxOrderPriceCap) price = MaxOrderPriceCap;
            return price;
        }

        /// <summary>Restlaufzeit-Klasse eines Marktes (Zeit bis Marktschluss).</summary>
        public enum TimeBucket { Under6h, Under24h, Under72h, Over72h }

        public static TimeBucket ResolveTimeBucket(double hoursLeft) =>
            hoursLeft < 6 ? TimeBucket.Under6h :
            hoursLeft < 24 ? TimeBucket.Under24h :
            hoursLeft < 72 ? TimeBucket.Under72h :
            TimeBucket.Over72h;

        /// <summary>Menschenlesbares Label passend zum <see cref="ResolveTimeBucket"/>-Ergebnis.</summary>
        public static string TimeBucketLabel(TimeBucket bucket) => bucket switch
        {
            TimeBucket.Under6h => "< 6h",
            TimeBucket.Under24h => "< 24h",
            TimeBucket.Under72h => "< 72h",
            _ => "> 72h"
        };

        /// <summary>Das für die Zeitklasse geltende Exposure-Limit (% des Guthabens) aus den Settings.</summary>
        public static decimal TimeLimitPct(TimeBucket bucket, CopyTradingAccountSettings settings) => bucket switch
        {
            TimeBucket.Under6h => settings.perMaxTime6h,
            TimeBucket.Under24h => settings.perMaxTime24h,
            TimeBucket.Under72h => settings.perMaxTime72h,
            _ => settings.perMaxTimeNone
        };

        /// <summary>
        /// Zählt eine offene Position anhand ihres Enddatums zur angegebenen Zeitklasse?
        /// (Dedupliziert die vier Filter im Time-Limit-Check der Engine, 1:1-Semantik.)
        /// </summary>
        public static bool IsPositionInBucket(DateTime? expiryDate, TimeBucket bucket, DateTime nowUtc)
        {
            if (bucket == TimeBucket.Over72h)
                return !expiryDate.HasValue || (expiryDate.Value - nowUtc).TotalHours >= 72;

            if (!expiryDate.HasValue) return false;
            double h = (expiryDate.Value - nowUtc).TotalHours;
            return bucket switch
            {
                TimeBucket.Under6h => h < 6,
                TimeBucket.Under24h => h >= 6 && h < 24,
                TimeBucket.Under72h => h >= 24 && h < 72,
                _ => false
            };
        }

        /// <summary>
        /// Maximal erlaubter Einsatz pro Markt: normalerweise <paramref name="perMarketLimitPct"/> %
        /// des Guthabens. Für kleine Konten greift ein Low-Balance-Bypass (Stufen), der den
        /// verfügbaren Betrag begrenzt, damit überhaupt eine Mindest-Order möglich ist.
        /// </summary>
        public static decimal MaxPerMarket(decimal totalBalance, decimal availableBalance, decimal perMarketLimitPct)
        {
            decimal maxAllowed = totalBalance * (perMarketLimitPct / 100.0m);
            if (totalBalance < 150m) maxAllowed = Math.Min(1.20m, Math.Max(availableBalance, 0m));
            else if (totalBalance < 500m) maxAllowed = Math.Min(3.0m, Math.Max(availableBalance, 0m));
            return maxAllowed;
        }
    }
}
