using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Predictalytics.Application.Services;
using Predictalytics.Domain.Entities;
using Predictalytics.Domain.Enums;
using Predictalytics.Infrastructure.Data;
using Predictalytics.Infrastructure.Data.Repositories;
using Predictalytics.Infrastructure.Services;
using System;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace Predictalytics.Application.Tests.Services;

public class AlertServiceTests
{
    private static AppDbContext CreateDbContext()
        => new(new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options);

    private static AlertService CreateService(AppDbContext db) => new(
        new AlertRepository(db),
        new TradeRepository(db, NullLogger<TradeRepository>.Instance),
        new TraderRepository(db),
        new InsiderWatchRepository(db),
        new FingerprintSnapshotService(db, NullLogger<FingerprintSnapshotService>.Instance),
        NullLogger<AlertService>.Instance);

    [Fact]
    public async Task InsiderWatch_RegistersInsider_WithoutAlertingOnHistory()
    {
        using var db = CreateDbContext();

        var trader = new Trader { Id = 1, PlatformUserId = "0xI", DisplayName = "QuietWhale" };
        trader.Traits.Add(new TraderTrait { TraderId = 1, Trait = "possible_insider", Value = 4.2m });
        db.Traders.Add(trader);
        // A historical trade (predates detection) must NOT produce an alert.
        db.Trades.Add(new Trade
        {
            Id = 10, TraderId = 1, DbMarketId = 100, Platform = PlatformType.Polymarket,
            Side = TradeSide.Buy, Outcome = "Yes", Price = 0.08m, Amount = 900m,
            ExecutedAt = DateTime.UtcNow.AddDays(-3)
        });
        await db.SaveChangesAsync();

        var svc = CreateService(db);
        await svc.EvaluateInsiderWatchAsync();

        // Registered in the system-level insider registry, NOT the user watchlist.
        Assert.Single(db.InsiderWatches.Where(i => i.TraderId == 1));
        Assert.Empty(db.WatchlistEntries);
        Assert.Empty(db.Alerts.Where(a => a.Type == AlertType.InsiderActivity));
    }

    [Fact]
    public async Task InsiderWatch_AlertsOnNewTrade_AndDedupsOnRerun()
    {
        using var db = CreateDbContext();

        var trader = new Trader { Id = 2, PlatformUserId = "0xJ", DisplayName = "Insider2" };
        trader.Traits.Add(new TraderTrait { TraderId = 2, Trait = "possible_insider", Value = 5m });
        db.Traders.Add(trader);
        // Already registered an hour ago; high-water mark predates the new trade.
        db.InsiderWatches.Add(new InsiderWatch
        {
            Id = 3, TraderId = 2,
            FirstDetectedAt = DateTime.UtcNow.AddHours(-1),
            LastAlertedTradeAt = DateTime.UtcNow.AddHours(-1)
        });
        // A trade placed AFTER the high-water mark -> should alert exactly once.
        db.Trades.Add(new Trade
        {
            Id = 20, TraderId = 2, DbMarketId = 200, Platform = PlatformType.Polymarket,
            Side = TradeSide.Buy, Outcome = "No", Price = 0.12m, Amount = 1500m,
            ExecutedAt = DateTime.UtcNow.AddMinutes(-20)
        });
        await db.SaveChangesAsync();

        var svc = CreateService(db);
        await svc.EvaluateInsiderWatchAsync();

        var alerts = db.Alerts.Where(a => a.Type == AlertType.InsiderActivity).ToList();
        Assert.Single(alerts);
        Assert.Equal(4, alerts[0].Severity);
        Assert.Equal(2, alerts[0].TraderId);

        // High-water mark advanced; a second run must not re-alert.
        var watch = db.InsiderWatches.First(i => i.Id == 3);
        Assert.True(watch.LastAlertedTradeAt > DateTime.UtcNow.AddMinutes(-25));

        await svc.EvaluateInsiderWatchAsync();
        Assert.Single(db.Alerts.Where(a => a.Type == AlertType.InsiderActivity));
    }

    [Fact]
    public async Task StrategyDrift_AlertsOnce_ThenDedupsWithinCooldown()
    {
        using var db = CreateDbContext();
        db.Traders.Add(new Trader { Id = 9, PlatformUserId = "0xD", DisplayName = "DriftMaster" });
        // Baseline 20 days ago (score 78) vs latest now (score 40) -> clear score-drop drift.
        db.TraderFingerprintSnapshots.Add(new TraderFingerprintSnapshot
        {
            TraderId = 9, CapturedAt = DateTime.UtcNow.AddDays(-20), CopytradingScore = 78m
        });
        db.TraderFingerprintSnapshots.Add(new TraderFingerprintSnapshot
        {
            TraderId = 9, CapturedAt = DateTime.UtcNow, CopytradingScore = 40m
        });
        await db.SaveChangesAsync();

        var svc = CreateService(db);
        await svc.EvaluateStrategyDriftAsync();

        var alerts = db.Alerts.Where(a => a.Type == AlertType.StrategyDrift).ToList();
        Assert.Single(alerts);
        Assert.Equal(9, alerts[0].TraderId);
        Assert.Equal(3, alerts[0].Severity);

        // Within the cooldown, a second evaluation must not re-alert.
        await svc.EvaluateStrategyDriftAsync();
        Assert.Single(db.Alerts.Where(a => a.Type == AlertType.StrategyDrift));
    }
}
