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

namespace Predictalytics.Application.Tests.Services;

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

    private static FingerprintSnapshotService CreateService(AppDbContext db)
        => new(db, NullLogger<FingerprintSnapshotService>.Instance);

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

        var master = new Trader { Id = 1, PlatformUserId = "0xM", DisplayName = "Master",
            Analytics = new TraderAnalytics { TraderId = 1, CopytradingScore = 60m, CategoryConcentration = 0.4m, P50PositionSize = 100m } };
        master.Traits.Add(new TraderTrait { TraderId = 1, Trait = "holds_to_resolution", Value = 1m });
        // Below the copy-relevance threshold -> must NOT be snapshotted.
        var weak = new Trader { Id = 2, PlatformUserId = "0xW", DisplayName = "Weak",
            Analytics = new TraderAnalytics { TraderId = 2, CopytradingScore = 20m } };
        db.Traders.AddRange(master, weak);
        await db.SaveChangesAsync();

        var svc = CreateService(db);
        var first = await svc.CaptureDueAsync();

        Assert.Equal(1, first);
        Assert.Single(db.TraderFingerprintSnapshots);
        Assert.Equal(1, db.TraderFingerprintSnapshots.Single().TraderId);
        Assert.Contains("holds_to_resolution", db.TraderFingerprintSnapshots.Single().TraitSetJson);

        // Second run within the throttle window captures nothing.
        var second = await svc.CaptureDueAsync();
        Assert.Equal(0, second);
        Assert.Single(db.TraderFingerprintSnapshots);
    }

    [Fact]
    public async Task GetDriftAsync_NullWithoutBaseline_ThenDetectsScoreDrop()
    {
        using var db = CreateDbContext();
        db.Traders.Add(new Trader { Id = 5, PlatformUserId = "0xD", DisplayName = "Drifter" });
        // Only a recent snapshot -> no baseline yet.
        db.TraderFingerprintSnapshots.Add(new TraderFingerprintSnapshot
        {
            TraderId = 5, CapturedAt = DateTime.UtcNow, CopytradingScore = 45m
        });
        await db.SaveChangesAsync();

        var svc = CreateService(db);
        Assert.Null(await svc.GetDriftAsync(5, baselineDays: 14));

        // Add an older baseline with a much higher score.
        db.TraderFingerprintSnapshots.Add(new TraderFingerprintSnapshot
        {
            TraderId = 5, CapturedAt = DateTime.UtcNow.AddDays(-20), CopytradingScore = 75m
        });
        await db.SaveChangesAsync();

        var drift = await svc.GetDriftAsync(5, baselineDays: 14);
        Assert.NotNull(drift);
        Assert.True(drift!.HasDrifted);
        Assert.Contains(drift.Dimensions, d => d.Dimension == "score");
    }
}
