using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using PolyTrader.Core.Trading;
using PolyTrader.Modules.ResolutionFarming.Logic;
using PolyTrader.Modules.ResolutionFarming.Models;
using PolyTrader.Modules.ResolutionFarming.Persistence;
using PolyTraderSharp.Services;

namespace PolyTrader.Modules.ResolutionFarming.Services
{
    /// <summary>
    /// Read-only-Scanner (Phase RF-1): holt bald auflösende Favoriten von einer
    /// <see cref="IFarmingMarketSource"/>, bewertet sie mit der puren Filterkette + Risk-Engine und
    /// schreibt JEDEN Kandidaten (akzeptiert wie abgelehnt inkl. Grund) nach rf_candidates – Basis
    /// für die spätere datenbasierte Kalibrierung. Platziert KEINE Orders.
    ///
    /// Die eigentliche Bewertung liegt in <see cref="Evaluate"/> (pur/statisch, voll unit-getestet);
    /// der Service umhüllt sie nur mit Beschaffung, Persistenz und Zeitsteuerung.
    /// </summary>
    public class MarketScannerService : BackgroundService
    {
        private static readonly TimeSpan ScanInterval = TimeSpan.FromMinutes(12);

        private readonly IFarmingMarketSource _source;
        private readonly IRfCandidateRepository _candidateRepo;
        private readonly IRfSettingsRepository _settingsRepo;
        private readonly TerminalLogger _logger;

        public MarketScannerService(
            IFarmingMarketSource source,
            IRfCandidateRepository candidateRepo,
            IRfSettingsRepository settingsRepo,
            TerminalLogger logger)
        {
            _source = source;
            _candidateRepo = candidateRepo;
            _settingsRepo = settingsRepo;
            _logger = logger;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); // nach den Core-Jobs anlaufen
            _logger.Info("ResolutionFarming-Scanner gestartet.");

            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    foreach (var settings in _settingsRepo.GetAll())
                    {
                        if (stoppingToken.IsCancellationRequested) break;
                        var candidates = await ScanAccountAsync(settings.AccountId, settings, stoppingToken);
                        int accepted = candidates.FindAll(c => c.Accepted).Count;
                        _logger.Info($"🔎 [RF-Scanner] Konto {settings.AccountId}: {candidates.Count} Märkte geprüft, {accepted} Kandidaten.");
                    }
                }
                catch (OperationCanceledException) { break; }
                catch (Exception ex)
                {
                    _logger.Error($"RF-Scanner Fehler: {ex.Message}");
                }

                await Task.Delay(ScanInterval, stoppingToken);
            }
        }

        /// <summary>Scannt und persistiert die Kandidaten eines Accounts. Liefert die bewerteten Kandidaten.</summary>
        internal async Task<List<RfCandidate>> ScanAccountAsync(int accountId, RfSettings settings, CancellationToken ct)
        {
            var markets = await _source.GetNearResolutionFavoritesAsync(settings, ct);
            var result = new List<RfCandidate>(markets.Count);
            foreach (var m in markets)
            {
                var candidate = Evaluate(accountId, m, settings);
                _candidateRepo.Insert(candidate);
                result.Add(candidate);
            }
            return result;
        }

        /// <summary>
        /// Reine Bewertung eines Marktes gegen die Settings. Reject-Grund = ERSTER fehlschlagender
        /// Filter (Preisband → Kategorie → Blacklist → Auflösungsfenster → Netto-Edge). Netto-Edge und
        /// Cluster-Key werden immer berechnet (auch bei Ablehnung), für die Kalibrierung.
        /// </summary>
        internal static RfCandidate Evaluate(int accountId, ScannedMarket m, RfSettings settings)
        {
            int feeBps = FeeModel.ResolveBps(m.FeeBps, m.Category);
            decimal netEdgePct = FarmingRiskEngine.NetEdgePct(m.Ask, feeBps);

            var c = new RfCandidate
            {
                ScannedAt = DateTime.UtcNow,
                AccountId = accountId,
                TokenId = m.TokenId,
                MarketSlug = m.MarketSlug,
                EventSlug = m.EventSlug,
                MarketQuestion = m.MarketQuestion,
                Outcome = m.Outcome,
                Category = m.Category,
                ClusterKey = FarmingScanner.ClusterKey(m.EventSlug, m.MarketSlug),
                Ask = m.Ask,
                FeeBps = feeBps,
                NetEdgePct = netEdgePct,
                Score = FarmingScanner.Score(netEdgePct, m.HoursToResolution),
                EndDate = m.EndDate
            };

            if (!FarmingScanner.InPriceBand(m.Ask, settings.MinPrice, settings.MaxPrice))
                return Reject(c, $"Preis {m.Ask:F3} außerhalb Band [{settings.MinPrice:F2}, {settings.MaxPrice:F2}]");
            if (!FarmingScanner.CategoryAllowed(m.Category, settings.CategoryWhitelistCsv))
                return Reject(c, $"Kategorie '{m.Category}' nicht auf Whitelist");
            if (FarmingScanner.IsBlacklisted(m.MarketSlug, m.Tags, settings.BlacklistCsv))
                return Reject(c, "Blacklist");
            if (m.HoursToResolution > settings.MaxHoursToResolution)
                return Reject(c, $"Auflösung erst in {m.HoursToResolution:F0}h > {settings.MaxHoursToResolution}h");
            if (!FarmingRiskEngine.HasEdge(m.Ask, feeBps, settings.MinEdgePct))
                return Reject(c, $"Netto-Edge {netEdgePct:F2}% < Min {settings.MinEdgePct:F2}%");

            c.Accepted = true;
            return c;
        }

        private static RfCandidate Reject(RfCandidate c, string reason)
        {
            c.Accepted = false;
            c.RejectReason = reason;
            return c;
        }
    }

    /// <summary>
    /// Platzhalter-Marktquelle (liefert nichts). Hält den Scanner lauffähig, bis die Live-Anbindung
    /// (Gamma-API endDate-Fenster + CLOB-Book für den Ask) im Zielland implementiert und registriert
    /// wird. So läuft das Modul ohne Live-API, produziert aber (korrekt) keine Kandidaten.
    /// </summary>
    public sealed class NullFarmingMarketSource : IFarmingMarketSource
    {
        public Task<IReadOnlyList<ScannedMarket>> GetNearResolutionFavoritesAsync(RfSettings settings, CancellationToken ct)
            => Task.FromResult<IReadOnlyList<ScannedMarket>>(Array.Empty<ScannedMarket>());
    }
}
