using Predictalytics.Application.DTOs;
using Predictalytics.Application.Interfaces;
using Predictalytics.Domain.Entities;
using Predictalytics.Domain.Enums;
using Predictalytics.Domain.Interfaces;
using Microsoft.Extensions.Logging;

namespace Predictalytics.Application.Services;

/// <summary>
/// Alert evaluation and management service.
/// </summary>
public class AlertService : IAlertService
{
    private readonly IAlertRepository _alertRepo;
    private readonly ITradeRepository _tradeRepo;
    private readonly ITraderRepository _traderRepo;
    private readonly IInsiderWatchRepository _insiderRepo;
    private readonly IFingerprintSnapshotService _fingerprints;
    private readonly ILogger<AlertService> _logger;

    // Alert thresholds (configurable in future)
    private const decimal LargePositionThresholdUsd = 5000m;

    /// <summary>Trait computed by <c>TraderTraitCalculator</c> for statistically improbable longshot winners.</summary>
    private const string PossibleInsiderTrait = "possible_insider";

    /// <summary>A drifting master stays drifted for a while; only re-alert after this quiet period.</summary>
    private static readonly TimeSpan DriftAlertCooldown = TimeSpan.FromDays(7);

    public AlertService(
        IAlertRepository alertRepo,
        ITradeRepository tradeRepo,
        ITraderRepository traderRepo,
        IInsiderWatchRepository insiderRepo,
        IFingerprintSnapshotService fingerprints,
        ILogger<AlertService> logger)
    {
        _alertRepo = alertRepo;
        _tradeRepo = tradeRepo;
        _traderRepo = traderRepo;
        _insiderRepo = insiderRepo;
        _fingerprints = fingerprints;
        _logger = logger;
    }

    public async Task EvaluateAlertsAsync(CancellationToken ct = default)
    {
        _logger.LogDebug("Evaluating alert rules...");

        // Check for large recent trades
        var recentTrades = await _tradeRepo.GetRecentAsync(100, ct: ct);
        var thirtyMinAgo = DateTime.UtcNow.AddMinutes(-30);

        foreach (var trade in recentTrades.Where(t => t.ExecutedAt > thirtyMinAgo))
        {
            if (trade.Amount >= LargePositionThresholdUsd)
            {
                var trader = await _traderRepo.GetByIdAsync(trade.TraderId, ct);
                var marketRef = trade.DbMarketId.HasValue
                    ? $"Market #{trade.DbMarketId}"
                    : (!string.IsNullOrEmpty(trade.MarketId) ? $"Market {trade.MarketId[..Math.Min(12, trade.MarketId.Length)]}..." : "Unknown Market");
                await CreateAlertAsync(new Alert
                {
                    Type = AlertType.LargePosition,
                    Platform = trade.Platform,
                    TraderId = trade.TraderId,
                    Title = $"Large {trade.Side} detected",
                    Message = $"{trader?.DisplayName ?? "Unknown"} {trade.Side} ${trade.Amount:N0} on {marketRef} ({trade.Outcome} @ {trade.Price:P0})",
                    Severity = trade.Amount >= 25000 ? 4 : trade.Amount >= 10000 ? 3 : 2
                }, ct);
            }
        }

        await EvaluateInsiderWatchAsync(ct);
        await EvaluateStrategyDriftAsync(ct);
    }

    /// <summary>
    /// Strategy-drift alarm (#3): fires when a master's fingerprint has drifted from its baseline,
    /// protecting copiers from a trader who has quietly changed strategy. Drift is a slow-moving
    /// signal, so alerts are de-duplicated per trader over <see cref="DriftAlertCooldown"/>.
    /// </summary>
    public async Task EvaluateStrategyDriftAsync(CancellationToken ct = default)
    {
        var drifted = await _fingerprints.GetDriftedTradersAsync(ct: ct);
        if (drifted.Count == 0) return;

        var cooldownSince = DateTime.UtcNow - DriftAlertCooldown;

        foreach (var (traderId, drift) in drifted)
        {
            if (await _alertRepo.ExistsRecentAsync(AlertType.StrategyDrift, traderId, cooldownSince, ct))
                continue;

            var trader = await _traderRepo.GetByIdAsync(traderId, ct);
            var dims = string.Join("; ", drift.Dimensions.Select(d => d.Detail));
            await CreateAlertAsync(new Alert
            {
                Type = AlertType.StrategyDrift,
                Platform = trader?.Platform ?? PlatformType.Polymarket,
                TraderId = traderId,
                Title = $"Strategie-Drift: {trader?.DisplayName ?? traderId.ToString()}",
                Message = $"Fingerprint driftet ggü. Baseline ({drift.BaselineAt:d}). {dims}",
                Severity = 3
            }, ct);
        }
    }

    /// <summary>
    /// Insider-Follow feed: maintains the system-level <see cref="InsiderWatch"/> registry for every
    /// <c>possible_insider</c> wallet and fires a high-severity alert for each new trade one of them
    /// places. These wallets trade rarely, so a single new trade is the strongest copy signal we have.
    /// This is deliberately decoupled from (per-user) watchlists. Dedup is via
    /// <see cref="InsiderWatch.LastAlertedTradeAt"/>; a freshly detected wallet is seeded at detection
    /// time so historical (backfilled) trades never trigger a backlog of alerts.
    /// </summary>
    public async Task EvaluateInsiderWatchAsync(CancellationToken ct = default)
    {
        var insiders = await _traderRepo.GetByTraitAsync(PossibleInsiderTrait, ct);

        foreach (var trader in insiders)
        {
            var watch = await _insiderRepo.GetByTraderIdAsync(trader.Id, ct);

            // Register newly detected insiders; seed the high-water mark at "now" so we don't alert
            // on their history. No alerts on the detection cycle itself.
            if (watch == null)
            {
                await _insiderRepo.AddAsync(new InsiderWatch
                {
                    TraderId = trader.Id,
                    FirstDetectedAt = DateTime.UtcNow,
                    LastAlertedTradeAt = DateTime.UtcNow
                }, ct);
                _logger.LogInformation("👁 Insider-Watch: detected new possible-insider {Trader}", trader.DisplayName);
                continue;
            }

            var recent = await _tradeRepo.GetByTraderIdAsync(trader.Id, 0, 50, ct);
            var newTrades = recent.Where(t => t.ExecutedAt > watch.LastAlertedTradeAt).OrderBy(t => t.ExecutedAt).ToList();
            if (newTrades.Count == 0) continue;

            foreach (var trade in newTrades)
            {
                var marketRef = trade.DbMarketId.HasValue
                    ? $"Market #{trade.DbMarketId}"
                    : (!string.IsNullOrEmpty(trade.MarketId) ? $"Market {trade.MarketId[..Math.Min(12, trade.MarketId.Length)]}..." : "Unknown Market");
                await CreateAlertAsync(new Alert
                {
                    Type = AlertType.InsiderActivity,
                    Platform = trade.Platform,
                    TraderId = trader.Id,
                    Title = $"Insider-Wallet aktiv: {trade.Side}",
                    Message = $"{trader.DisplayName} {trade.Side} ${trade.Amount:N0} auf {marketRef} ({trade.Outcome} @ {trade.Price:P0})",
                    Severity = 4
                }, ct);
            }

            watch.LastAlertedTradeAt = newTrades.Max(t => t.ExecutedAt);
            await _insiderRepo.UpdateAsync(watch, ct);
        }
    }

    public async Task CreateAlertAsync(Alert alert, CancellationToken ct = default)
    {
        await _alertRepo.AddAsync(alert, ct);
        _logger.LogInformation("🔔 Alert [{Type}]: {Title}", alert.Type, alert.Title);
    }

    public async Task<IReadOnlyList<AlertDto>> GetRecentAlertsAsync(int count = 50, bool unreadOnly = false, CancellationToken ct = default)
    {
        var alerts = await _alertRepo.GetRecentAsync(count, unreadOnly, ct);
        var result = new List<AlertDto>();

        foreach (var a in alerts)
        {
            string? traderName = null;
            if (a.TraderId.HasValue)
            {
                var trader = await _traderRepo.GetByIdAsync(a.TraderId.Value, ct);
                traderName = trader?.DisplayName;
            }

            result.Add(new AlertDto(
                a.Id, a.Type.ToString(), a.Platform.ToString(),
                a.Title, a.Message, a.Severity, a.IsRead, a.CreatedAt, traderName));
        }

        return result;
    }

    public async Task MarkAsReadAsync(int alertId, CancellationToken ct = default)
    {
        await _alertRepo.MarkAsReadAsync(alertId, ct);
    }
}
