using Predictalytics.Application.Interfaces;
using Predictalytics.Application.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;

namespace Predictalytics.Api.Endpoints;

public static class TraderEndpoints
{
    public static void MapTraderReadEndpoints(this IEndpointRouteBuilder routes)
    {
        var group = routes.MapGroup("/api/traders").WithTags("Traders");

        group.MapGet("/", async (IAnalyticsService svc, int? skip, int? take, string? platform, bool? highlyCopyable, string? trait, string? sort, decimal? minWinRate, decimal? minCopyability, CancellationToken ct) =>
            Results.Ok(await svc.GetTradersAsync(skip ?? 0, take ?? 50, platform, highlyCopyable ?? false, trait, sort, minWinRate, minCopyability, ct)));

        // Curated dashboard showcases (leaderboards). Read-only, persisted data only.
        group.MapGet("/showcases", async (IAnalyticsService svc, CancellationToken ct) =>
            Results.Ok(await svc.GetShowcasesAsync(ct)));

        // Dedicated Insider view: system-level list of possible-insider wallets (NOT a user watchlist).
        group.MapGet("/insiders", async (Predictalytics.Infrastructure.Data.AppDbContext db, CancellationToken ct) =>
        {
            var traders = await db.Traders
                .Include(t => t.Traits)
                .Include(t => t.Analytics)
                .Include(t => t.CurrentScore)
                .Where(t => t.Traits.Any(tr => tr.Trait == "possible_insider"))
                .ToListAsync(ct);

            var detectedAt = (await db.InsiderWatches.ToListAsync(ct))
                .ToDictionary(i => i.TraderId, i => i.FirstDetectedAt);

            var result = traders
                .Select(t => new Predictalytics.Application.DTOs.InsiderDto(
                    t.Id,
                    t.Platform.ToString(),
                    t.DisplayName,
                    t.PlatformUserId,
                    t.CurrentScore?.CombinedScore ?? 0,
                    t.Analytics?.CopytradingCopyabilityScore ?? 0,
                    t.WinRate,
                    t.TotalPnl,
                    t.TotalTrades,
                    t.Traits.FirstOrDefault(tr => tr.Trait == "possible_insider")?.Value ?? 0,
                    detectedAt.TryGetValue(t.Id, out var d) ? d : (DateTime?)null,
                    t.LastPolledAt))
                .OrderByDescending(i => i.SurpriseValue)
                .ToList();

            return Results.Ok(result);
        });

        group.MapGet("/traits", async (Predictalytics.Infrastructure.Data.AppDbContext db, CancellationToken ct) =>
            Results.Ok(await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync(db.TraderTraits.Select(t => t.Trait).Distinct(), ct)));

        group.MapGet("/{id:int}", async (int id, IAnalyticsService svc, CancellationToken ct) =>
        {
            var detail = await svc.GetTraderDetailAsync(id, ct);
            return detail is not null ? Results.Ok(detail) : Results.NotFound();
        });

        group.MapGet("/{id:int}/positions", async (int id, IAnalyticsService svc, CancellationToken ct) =>
        {
            var positions = await svc.GetTraderPositionsAsync(id, ct);
            return Results.Ok(positions);
        });

        group.MapGet("/{id:int}/profile", async (int id, Predictalytics.Infrastructure.Data.AppDbContext db, CancellationToken ct) =>
        {
            var trader = await db.Traders.Include(t => t.Analytics).FirstOrDefaultAsync(t => t.Id == id, ct);
            if (trader == null || trader.Analytics == null) return Results.NotFound();

            var windowMetrics = await db.TraderWindowMetrics
                .Where(w => w.TraderId == id)
                .OrderByDescending(w => w.WindowStart)
                .Select(w => new Predictalytics.Application.DTOs.TraderWindowMetricsDto(
                    w.WindowStart, w.WindowEnd, w.ClosedMarkets, w.WinRate, w.AvgReturnPct,
                    w.MedianWinReturnPct, w.MedianLossReturnPct, w.ProfitFactor))
                .ToListAsync(ct);

            var profile = new Predictalytics.Application.DTOs.TraderProfileDto(
                trader.Id,
                trader.DisplayName ?? "",
                trader.MasterStatus,
                trader.Analytics.MedianHoldDurationHours,
                trader.Analytics.P50PositionSize,
                trader.Analytics.P90PositionSize,
                trader.Analytics.TradesPerWeek,
                trader.Analytics.MedianMarketVolumeUsd,
                trader.Analytics.MedianPostFillDriftPct,
                trader.Analytics.NetEdgeAfterFeesPct,
                trader.Analytics.PriceBandProfileJson,
                windowMetrics
            );
            return Results.Ok(profile);
        });

        // Strategy-drift: compare a master's latest fingerprint to a baseline ~N days ago (#3).
        group.MapGet("/{id:int}/fingerprint-drift", async (int id, int? baselineDays, IFingerprintSnapshotService svc, CancellationToken ct) =>
        {
            var drift = await svc.GetDriftAsync(id, baselineDays ?? 14, ct);
            return drift is not null ? Results.Ok(drift) : Results.NoContent();
        });

        // Edge-freshness: is the recent out-of-sample window still as strong as the older one? (#5)
        group.MapGet("/{id:int}/edge-freshness", async (int id, Predictalytics.Infrastructure.Data.AppDbContext db, CancellationToken ct) =>
        {
            var windows = await db.TraderWindowMetrics
                .Where(w => w.TraderId == id)
                .OrderByDescending(w => w.WindowEnd)
                .ToListAsync(ct);
            if (windows.Count < 2) return Results.NoContent();

            var recent = windows[0];
            var older = windows[1];
            return Results.Ok(Predictalytics.Application.Services.EdgeFreshnessCalculator.Compute(older, recent));
        });

        // Smart-money co-movement (#1): wallets that repeatedly enter the same outcomes as this seed,
        // around the same time (positive lead = they tend to move first).
        group.MapGet("/{id:int}/co-movement", async (int id, int? windowHours, int? minShared, Predictalytics.Infrastructure.Data.AppDbContext db, CancellationToken ct) =>
        {
            var buy = Predictalytics.Domain.Enums.TradeSide.Buy;
            var seed = await db.Trades
                .Where(t => t.TraderId == id && t.Side == buy && t.MarketOutcomeId != null)
                .OrderByDescending(t => t.ExecutedAt)
                .Select(t => new { OutcomeId = t.MarketOutcomeId!.Value, t.ExecutedAt })
                .Take(500)
                .ToListAsync(ct);
            if (seed.Count == 0) return Results.Ok(Array.Empty<Predictalytics.Application.DTOs.CoMovingWalletDto>());

            var outcomeIds = seed.Select(s => s.OutcomeId).Distinct().ToList();
            var cands = await db.Trades
                .Where(t => t.TraderId != id && t.Side == buy && t.MarketOutcomeId != null && outcomeIds.Contains(t.MarketOutcomeId!.Value))
                .Select(t => new { t.TraderId, OutcomeId = t.MarketOutcomeId!.Value, t.ExecutedAt })
                .ToListAsync(ct);

            var seedEntries = seed.Select(s => new Predictalytics.Application.Services.CoEntry(s.OutcomeId, s.ExecutedAt)).ToList();
            var candEntries = cands.Select(c => new Predictalytics.Application.Services.CandidateEntry(c.TraderId, c.OutcomeId, c.ExecutedAt)).ToList();

            var ranked = Predictalytics.Application.Services.CoMovementCalculator
                .Rank(seedEntries, candEntries, windowHours ?? 48, minShared ?? 3)
                .Take(20).ToList();

            var ids = ranked.Select(r => r.TraderId).ToList();
            var traders = (await db.Traders.Where(t => ids.Contains(t.Id))
                .Select(t => new { t.Id, t.DisplayName, t.Platform }).ToListAsync(ct))
                .ToDictionary(t => t.Id);

            var result = ranked.Select(r =>
            {
                traders.TryGetValue(r.TraderId, out var t);
                return new Predictalytics.Application.DTOs.CoMovingWalletDto(
                    r.TraderId, t?.DisplayName ?? "?", t?.Platform.ToString() ?? "?",
                    r.SharedMarkets, r.AvgLeadHours, r.AvgLeadHours > 0);
            }).ToList();

            return Results.Ok(result);
        });

        group.MapGet("/correlation", async (int traderIdA, int traderIdB, Predictalytics.Infrastructure.Data.AppDbContext db, CancellationToken ct) =>
        {
            var positionsA = await db.TraderPositions
                .Where(p => p.TraderId == traderIdA && p.MarketOutcome != null)
                .Select(p => new { p.MarketOutcome!.MarketId, Direction = p.SharesHeld > 0 ? p.MarketOutcomeId : -p.MarketOutcomeId })
                .ToListAsync(ct);

            var positionsB = await db.TraderPositions
                .Where(p => p.TraderId == traderIdB && p.MarketOutcome != null)
                .Select(p => new { p.MarketOutcome!.MarketId, Direction = p.SharesHeld > 0 ? p.MarketOutcomeId : -p.MarketOutcomeId })
                .ToListAsync(ct);

            var marketsA = positionsA.Select(p => p.MarketId).Distinct().ToList();
            var marketsB = positionsB.Select(p => p.MarketId).Distinct().ToList();

            var commonMarkets = marketsA.Intersect(marketsB).ToList();
            var sameDirectionCount = 0;

            foreach (var m in commonMarkets)
            {
                var dirA = positionsA.Where(p => p.MarketId == m).Select(p => p.Direction).FirstOrDefault();
                var dirB = positionsB.Where(p => p.MarketId == m).Select(p => p.Direction).FirstOrDefault();
                if (dirA == dirB && dirA != 0) sameDirectionCount++;
            }

            decimal intersectionA = marketsA.Count > 0 ? (decimal)commonMarkets.Count / marketsA.Count : 0;
            decimal intersectionB = marketsB.Count > 0 ? (decimal)commonMarkets.Count / marketsB.Count : 0;
            decimal agreement = commonMarkets.Count > 0 ? (decimal)sameDirectionCount / commonMarkets.Count : 0;

            return Results.Ok(new Predictalytics.Application.DTOs.TraderCorrelationDto(
                traderIdA, traderIdB, commonMarkets.Count, sameDirectionCount, intersectionA, intersectionB, agreement
            ));
        });
    }

    public static void MapTraderControlEndpoints(this IEndpointRouteBuilder routes)
    {
        var group = routes.MapGroup("/api/traders").WithTags("Traders");

        group.MapGet("/{id:int}/deep-dive", async (int id, IAnalyticsService svc, CancellationToken ct) =>
        {
            var dd = await svc.GetTraderDeepDiveAsync(id, ct);
            return dd is not null ? Results.Ok(dd) : Results.NotFound();
        });

        group.MapPost("/{id:int}/priority", async (int id, int? score, IScoringService svc, CancellationToken ct) =>
        {
            await svc.SetManualOverrideAsync(id, score, ct);
            return Results.Ok();
        });

        group.MapPost("/{id:int}/watchlist", async (int id, WatchlistService svc, CancellationToken ct) =>
        {
            await svc.AddAsync(id, "Watched via UI", null, ct);
            return Results.Ok();
        });

        group.MapDelete("/{id:int}/watchlist", async (int id, WatchlistService svc, CancellationToken ct) =>
        {
            await svc.RemoveByTraderIdAsync(id, ct);
            return Results.Ok();
        });

        group.MapPost("/{id:int}/ai-analysis", async (int id, bool manual, IAiStrategyAnalysisService aiSvc, CancellationToken ct) =>
        {
            var summary = await aiSvc.AnalyzeTraderStrategyAsync(id, manual, ct);
            return Results.Ok(new { summary });
        });

        group.MapPost("/", async (string platform, string wallet, IAnalyticsService svc, CancellationToken ct) =>
        {
            var id = await svc.AddTraderAsync(platform, wallet, ct);
            return Results.Ok(new { id });
        });
    }
}
