using System;
using System.Linq;
using System.Threading;
using PolyTrader.Core.Trading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using PolyTrader.Core.Notifications;
using PolyTrader.Core.Persistence;
using PolyTrader.Modules.CopyTrading.Logic;
using PolyTraderSharp.Models;

namespace PolyTraderSharp.Services
{
    /// <summary>
    /// Phase 0.1 – SELL-Eskalationsleiter. Statt SELLs als Market-Order ins (oft leergeräumte)
    /// Orderbuch zu werfen (April-Verlustquelle), platziert die Engine ein GTC-Limit nahe am
    /// Master-Exit und markiert die Position <see cref="Position.ExitPending"/>. Dieser Service
    /// senkt das Limit stufenweise (relative Schrittweite) bis zum Floor, wenn kein Fill kommt:
    /// canceln → eine Stufe tiefer neu platzieren. Am Floor ohne Fill: Position halten +
    /// Benachrichtigung über <see cref="INotificationSink"/>. Fills erkennt der bestehende Sync (TraderMonitorService), der die
    /// Position entfernt → die Leiter endet dann von selbst.
    ///
    /// Bewusst separat vom CLOB-Order-Code gehalten; die Preislogik ist in <see cref="SellLogic"/>
    /// pur und unit-getestet. Umfangreiches Logging, da (noch) kein Live-Test möglich ist.
    /// </summary>
    public class SellLadderService : BackgroundService
    {
        private static readonly TimeSpan Tick = TimeSpan.FromSeconds(5);

        private readonly CopyTradingState _copyState;
        private readonly TradingState _state;
        private readonly IClobClient _clob;
        private readonly TerminalLogger _logger;
        private readonly INotificationSink _notify;
        private readonly IPositionRepository _positionRepo;
        private readonly IOrderEventLog _orderEvents;

        public SellLadderService(
            CopyTradingState copyState,
            TradingState state,
            IClobClient clob,
            TerminalLogger logger,
            INotificationSink notify,
            IPositionRepository positionRepo,
            IOrderEventLog orderEvents)
        {
            _copyState = copyState;
            _state = state;
            _clob = clob;
            _logger = logger;
            _notify = notify;
            _positionRepo = positionRepo;
            _orderEvents = orderEvents;
        }

        /// <summary>S-0: Order-Lifecycle-Event der Leiter strukturiert festhalten (core_order_events).</summary>
        private void OrderEvent(ExitLadderState ladder, OrderEventType type, decimal price, decimal amountUsd, string response, string detailsJson = "")
        {
            _orderEvents.Write(new OrderEventRecord
            {
                SignalId = ladder.SignalId,
                ModuleName = "CopyTrading",
                AccountId = ladder.AccountId,
                TokenId = ladder.TokenId,
                EventType = type,
                Side = "SELL",
                Price = price,
                AmountUsd = amountUsd,
                OrderType = "GTC",
                Response = response,
                DetailsJson = detailsJson
            });
        }

        /// <summary>
        /// Startet eine SELL-Eskalationsleiter für eine Live-Position (Phase 0.1). Platziert das erste
        /// GTC-Limit nahe am Referenzpreis, stellt die Position auf <see cref="Position.ExitPending"/>
        /// (kein Doppel-SELL, Limits rechnen weiter) und registriert die Leiter; der Loop preist nach.
        /// Gemeinsame Quelle für Master-SELLs (Engine) UND eigene Exits wie Profit-Target (Sync).
        /// </summary>
        public async Task<bool> StartLadderAsync(
            AccountState account, Position pos, decimal referencePrice, int sourceTraderId,
            bool isHf, decimal maxPriceDifferencePct, decimal sellFloorPct, bool isNegRisk, string reasonTag,
            string signalId = "")
        {
            string key = $"{account.AccountId}_{pos.TokenId}";

            decimal firstLimit = SellLogic.FirstLimit(referencePrice, isHf, maxPriceDifferencePct);
            decimal floor = SellLogic.Floor(referencePrice, sellFloorPct);
            firstLimit = Math.Clamp(firstLimit, 0.01m, 0.99m);
            floor = Math.Clamp(floor, 0.01m, 0.99m);
            if (floor > firstLimit) floor = firstLimit; // Floor nie über dem Startlimit
            // H4: Preise auf Tick runden, damit usdc = size × Preis exakt aufgeht (kein Dust-Rest).
            firstLimit = SellLogic.RoundToTick(firstLimit);
            floor = SellLogic.RoundToTick(floor);

            var exact = PolymarketClobClient.CalculateExactOrderAmounts(pos.Size * firstLimit, firstLimit, firstLimit, "SELL");
            if (exact.shares <= 0)
            {
                _logger.TradeReasoning($"❌ [SELL-LEITER {reasonTag}] {account.Name} | {pos.MarketQuestion}: mathematisch keine Order möglich (Dust). Position wird gehalten.");
                pos.ExitPending = false;
                account.OpenPositions[pos.TokenId] = pos;
                return false;
            }

            // H1 – Atomarer Claim ZUERST: nur EINE Leiter je Position. Master-SELL (Engine) und
            // Profit-Target (Sync) laufen ohne gemeinsames Lock; ohne diesen Claim könnten beide
            // ExitPending==false lesen und zwei GTC-SELLs / Fehlerkaskaden auslösen.
            var ladder = new ExitLadderState
            {
                AccountId = account.AccountId,
                TokenId = pos.TokenId,
                SourceTraderId = sourceTraderId,
                SignalId = signalId,
                MarketQuestion = pos.MarketQuestion,
                ReferencePrice = referencePrice,
                CurrentLimit = firstLimit,
                Floor = floor,
                IsHf = isHf,
                Attempt = 1,
                LastActionAt = DateTime.UtcNow
            };
            if (!_copyState.ExitLadders.TryAdd(key, ladder))
            {
                // Es läuft bereits eine Leiter für diese Position (anderer Aufrufer hat den Claim).
                // Position der bestehenden Leiter überlassen, aber sicherstellen, dass sie – falls der
                // Aufrufer (Engine) sie zuvor entfernt hat – als ExitPending präsent bleibt (kein Waise).
                pos.ExitPending = true;
                account.OpenPositions[pos.TokenId] = pos;
                if (!account.IsDemo) _positionRepo.UpsertLive(account.AccountId, pos);
                _logger.Info($"🪜 [SELL-LEITER {reasonTag}] {account.Name} | {pos.MarketQuestion}: Leiter läuft bereits – kein Doppelstart.");
                return false;
            }

            // Ab hier besitzt DIESER Aufruf die Leiter. Position tracken + ExitPending.
            pos.ExitPending = true;
            account.OpenPositions[pos.TokenId] = pos;
            if (!account.IsDemo) _positionRepo.UpsertLive(account.AccountId, pos);

            _logger.Trade($"🪜 [SELL-LEITER Start · {reasonTag}]\n" +
                          $"   Konto:    {account.Name}\n" +
                          $"   Markt:    {pos.MarketQuestion}\n" +
                          $"   Referenz: {referencePrice:F3} | Startlimit: {firstLimit:F3} | Floor: {floor:F3}\n" +
                          $"   Stufen:   {(isHf ? "HF ~20s" : "~120s")}/Schritt, {SellLogic.LadderStepPct}% relativ");

            // M6: realen Taker-Satz (bzw. Kategorie-Fallback) in die signierte Order schreiben.
            int feeBps = _state.MarketCache.TryGetValue(pos.TokenId, out var startMd)
                ? FeeModel.ResolveBps(startMd.TakerFeeBps, startMd.Category)
                : FeeModel.FallbackBps(null);
            var result = await _clob.PlaceOrderAsync(account, pos.TokenId, "SELL", pos.Size * firstLimit, firstLimit, "GTC", _state.DebugOrderPayloadLog, isNegRisk, feeBps);
            OrderEvent(ladder, result == "OK" ? OrderEventType.LadderStart : OrderEventType.PlaceFailed,
                firstLimit, pos.Size * firstLimit, result,
                $"{{\"reasonTag\":\"{reasonTag}\",\"floor\":{floor.ToString(System.Globalization.CultureInfo.InvariantCulture)}}}");

            if (result == "OK")
            {
                _copyState.PendingOrderTimestamps[key] = (DateTime.UtcNow, sourceTraderId, "SELL");
                _logger.Trade($"✅ [SELL-LEITER platziert · {reasonTag}] {account.Name} | GTC-Limit {firstLimit:F3} für {pos.Size:F2} Shares.");
                return true;
            }

            // Startorder fehlgeschlagen: Claim + ExitPending zurücknehmen, Position bleibt handelbar.
            _copyState.ExitLadders.TryRemove(key, out _);
            pos.ExitPending = false;
            _copyState.PendingOrderTimestamps[key] = (DateTime.UtcNow.AddSeconds(-15), sourceTraderId, "SELL");
            _logger.TradeReasoning($"❌ [SELL-LEITER · {reasonTag}] Startorder fehlgeschlagen: {result}. Position bleibt; neuer Versuch beim nächsten Signal/Sync.");
            return false;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.Info("SELL-Eskalationsleiter-Service gestartet.");

            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    await Task.Delay(Tick, stoppingToken);
                    foreach (var kvp in _copyState.ExitLadders.ToArray())
                    {
                        await ProcessLadderAsync(kvp.Key, kvp.Value);
                    }
                }
                catch (OperationCanceledException) { break; }
                catch (Exception ex)
                {
                    _logger.Error($"SellLadderService Fehler: {ex.Message}");
                }
            }
        }

        internal async Task ProcessLadderAsync(string key, ExitLadderState ladder)
        {
            // Account weg? -> Leiter verwerfen.
            if (!_state.Accounts.TryGetValue(ladder.AccountId, out var account))
            {
                _copyState.ExitLadders.TryRemove(key, out _);
                return;
            }

            // Position weg -> Sync hat den Fill erkannt und geschlossen. Leiter fertig.
            if (!account.OpenPositions.TryGetValue(ladder.TokenId, out var pos))
            {
                _copyState.ExitLadders.TryRemove(key, out _);
                _logger.Trade($"✅ [SELL-LEITER fertig] {account.Name} | {ladder.MarketQuestion} | Position gefüllt/geschlossen (Sync).");
                return;
            }

            // H4: Dust-Rest unter dem Polymarket-Minimum lässt sich nie als Order platzieren. Leiter
            // beenden statt endlos Sub-Minimum-Orders zu schicken (ewige API-Rejects; ExitPending bliebe
            // sonst für immer). Der Mikro-Rest wird bewusst gehalten und geloggt.
            if (pos.Size < CopyTradingRisk.MinShares)
            {
                _copyState.ExitLadders.TryRemove(key, out _);
                pos.ExitPending = false;
                if (!account.IsDemo) _positionRepo.UpsertLive(account.AccountId, pos);
                _logger.Warning($"🧹 [SELL-LEITER Dust] {account.Name} | {ladder.MarketQuestion}: Restgröße {pos.Size:F2} < Minimum {CopyTradingRisk.MinShares} Shares – Leiter beendet, Rest gehalten.");
                OrderEvent(ladder, OrderEventType.DustAbort, ladder.CurrentLimit, pos.Size * ladder.CurrentLimit,
                    "Dust", $"{{\"restShares\":{pos.Size.ToString(System.Globalization.CultureInfo.InvariantCulture)}}}");
                return;
            }

            // Noch nicht Zeit für die nächste Stufe?
            double waited = (DateTime.UtcNow - ladder.LastActionAt).TotalSeconds;
            if (waited < SellLogic.LadderIntervalSeconds(ladder.IsHf)) return;

            // Bereits am Floor: Order ruht auf dem Floor und soll dort auf einen Fill warten.
            if (SellLogic.IsAtFloor(ladder.CurrentLimit, ladder.Floor))
            {
                // K1 – Floor-Robustheit: prüfen, ob die Verkaufsorder überhaupt noch auf dem Markt ruht.
                // Falls nicht (Cleanup-Timeout, externer Cancel, Teil-Fill hat die Order entfernt), würde die
                // Position sonst bis zum Neustart unverkäuflich liegen bleiben → am Floor neu platzieren.
                bool orderResting;
                try
                {
                    var open = await _clob.GetOpenOrdersAsync(account, ladder.TokenId);
                    orderResting = open.Any(o => (o.Side ?? string.Empty).ToUpperInvariant().Contains("SELL"));
                }
                catch (Exception ex)
                {
                    _logger.Error($"[SELL-LEITER Floor] Order-Existenzprüfung fehlgeschlagen: {ex.Message}");
                    ladder.LastActionAt = DateTime.UtcNow;
                    return;
                }

                if (!orderResting)
                {
                    bool isNegFloor = _state.MarketCache.TryGetValue(ladder.TokenId, out var mdFloor) && mdFloor.NegRisk;
                    int floorFeeBps = mdFloor != null ? FeeModel.ResolveBps(mdFloor.TakerFeeBps, mdFloor.Category) : FeeModel.FallbackBps(null);
                    _logger.Warning($"🔁 [SELL-LEITER Floor] {account.Name} | {ladder.MarketQuestion}: keine ruhende SELL-Order am Floor {ladder.Floor:F3} – platziere neu.");
                    try
                    {
                        var r = await _clob.PlaceOrderAsync(account, ladder.TokenId, "SELL", pos.Size * ladder.Floor, ladder.Floor, "GTC", _state.DebugOrderPayloadLog, isNegFloor, floorFeeBps);
                        OrderEvent(ladder, OrderEventType.FloorReplaced, ladder.Floor, pos.Size * ladder.Floor, r);
                        if (r == "OK")
                            _copyState.PendingOrderTimestamps[key] = (DateTime.UtcNow, ladder.SourceTraderId, "SELL");
                        else
                            _logger.Warning($"⚠️ [SELL-LEITER Floor] Neuplatzierung am Floor fehlgeschlagen ({r}) – nächster Versuch beim nächsten Intervall.");
                    }
                    catch (Exception ex) { _logger.Error($"[SELL-LEITER Floor] Neuplatzierung-Fehler: {ex.Message}"); }
                    ladder.LastActionAt = DateTime.UtcNow;
                    return;
                }

                // Order ruht am Floor: einmalig benachrichtigen, dann halten.
                if (!ladder.FloorNotified)
                {
                    ladder.FloorNotified = true;
                    string msg = $"⚠️ SELL-Floor ohne Fill erreicht – Position wird gehalten.\n" +
                                 $"Konto: {account.Name}\nMarkt: {ladder.MarketQuestion}\n" +
                                 $"Floor: {ladder.Floor:F3} (Master-Exit war {ladder.ReferencePrice:F3})";
                    _logger.Warning($"🛑 [SELL-LEITER Floor] {account.Name} | {ladder.MarketQuestion} | Floor {ladder.Floor:F3} ohne Fill – halte Position.");
                    // Kein try/catch nötig: INotificationSink wirft laut Vertrag nie.
                    await _notify.SendAsync(msg, NotificationSeverity.Critical);
                }
                ladder.LastActionAt = DateTime.UtcNow; // Re-Notify-Spam vermeiden
                return;
            }

            // Eine Stufe tiefer: alte Order canceln, tiefer neu platzieren.
            decimal next = SellLogic.RoundToTick(SellLogic.NextPrice(ladder.CurrentLimit, SellLogic.LadderStepPct, ladder.Floor));
            bool isNegRisk = _state.MarketCache.TryGetValue(ladder.TokenId, out var md) && md.NegRisk;
            int stepFeeBps = md != null ? FeeModel.ResolveBps(md.TakerFeeBps, md.Category) : FeeModel.FallbackBps(null);

            _logger.Trade($"🪜 [SELL-LEITER Stufe {ladder.Attempt + 1}] {account.Name} | {ladder.MarketQuestion}\n" +
                          $"   Limit {ladder.CurrentLimit:F3} → {next:F3} (Floor {ladder.Floor:F3}, {(ladder.IsHf ? "HF" : "Standard")})");

            try
            {
                await _clob.CancelConflictingOrdersAsync(account, ladder.TokenId, next, "SELL");

                decimal usdc = pos.Size * next;
                var result = await _clob.PlaceOrderAsync(account, ladder.TokenId, "SELL", usdc, next, "GTC", _state.DebugOrderPayloadLog, isNegRisk, stepFeeBps);
                OrderEvent(ladder, result == "OK" ? OrderEventType.LadderStep : OrderEventType.PlaceFailed,
                    next, usdc, result,
                    $"{{\"attempt\":{ladder.Attempt + 1},\"floor\":{ladder.Floor.ToString(System.Globalization.CultureInfo.InvariantCulture)}}}");

                if (result == "OK")
                {
                    ladder.CurrentLimit = next;
                    ladder.Attempt++;
                    ladder.LastActionAt = DateTime.UtcNow;
                    _copyState.PendingOrderTimestamps[key] = (DateTime.UtcNow, ladder.SourceTraderId, "SELL");
                    _logger.Trade($"✅ [SELL-LEITER Stufe {ladder.Attempt}] {account.Name} | neues GTC-Limit {next:F3} für {pos.Size:F2} Shares.");
                }
                else
                {
                    ladder.LastActionAt = DateTime.UtcNow; // beim nächsten Tick erneut versuchen
                    _logger.Warning($"⚠️ [SELL-LEITER] Nachpreisen fehlgeschlagen ({result}) – nächster Versuch in {SellLogic.LadderIntervalSeconds(ladder.IsHf)}s.");
                }
            }
            catch (Exception ex)
            {
                ladder.LastActionAt = DateTime.UtcNow;
                _logger.Error($"[SELL-LEITER] Fehler beim Nachpreisen: {ex.Message}");
            }
        }
    }
}
