using KArtSell.BuildingBlocks.Time;
using Microsoft.Extensions.Logging;

namespace KArtSell.Modules.ModelOperations.ShadowRun;

/// <summary>
/// Replays model over historical data window to generate signals, orders, and fills.
/// Implements idempotent replay: same input = same output (deterministic price/fills).
/// </summary>
public sealed class ReplayEngine(
    ILogger<ReplayEngine> logger)
{
    public record Signal(
        Guid SignalId,
        DateOnly Date,
        string Ticker,
        SignalAction Action,
        decimal Confidence,
        string Rationale);

    public record Order(
        Guid OrderId,
        DateOnly PlacedDate,
        DateOnly? FilledDate,
        string Ticker,
        SignalAction Action,
        long Quantity,
        decimal InitialPrice,
        decimal? FilledPrice);

    public record Portfolio(
        DateOnly AsOfDate,
        Dictionary<string, long> Positions,  // ticker -> shares
        decimal CashBalance,
        decimal TotalValue);

    public enum SignalAction
    {
        Buy = 0,
        Sell = 1,
        Hold = 2,
        Exit = 3
    }

    /// <summary>
    /// Replay model across historical window.
    /// Returns daily portfolio snapshots and order fills.
    /// </summary>
    public async Task<ReplayResult> ReplayAsync(
        Guid modelId,
        IReadOnlyList<DataBackfiller.OhlcvBar> ohlcvBars,
        IReadOnlyList<DataBackfiller.FeeScheduleEntry> feeSchedule,
        decimal initialCashBalance,
        IReadOnlyList<DateOnly> tradingSessions,
        CancellationToken cancellationToken)
    {
        logger.LogInformation(
            "Replaying model {ModelId} across {TradingDays} sessions, initial cash: {CashBalance:C}",
            modelId, tradingSessions.Count, initialCashBalance);

        var portfolioHistory = new List<Portfolio>();
        var signals = new List<Signal>();
        var orders = new List<Order>();
        var dailyReturns = new List<(DateOnly Date, decimal Return)>();

        var currentPortfolio = new Portfolio(
            tradingSessions[0],
            new Dictionary<string, long>(),
            initialCashBalance,
            initialCashBalance);

        decimal previousPortfolioValue = initialCashBalance;

        foreach (var session in tradingSessions)
        {
            cancellationToken.ThrowIfCancellationRequested();

            // Simulate signals at market open (simplified: use model.predict logic)
            var daySignals = await GenerateSignalsAsync(modelId, session, ohlcvBars, cancellationToken);
            signals.AddRange(daySignals);

            // Convert signals to orders
            var dayOrders = daySignals
                .Select(s => new Order(
                    OrderId: Guid.NewGuid(),
                    PlacedDate: session,
                    FilledDate: session,  // Market order filled same day
                    Ticker: s.Ticker,
                    Action: s.Action,
                    Quantity: 100,  // Simplified: fixed quantity
                    InitialPrice: GetClosePrice(session, s.Ticker, ohlcvBars),
                    FilledPrice: GetClosePrice(session, s.Ticker, ohlcvBars)))
                .ToList();

            orders.AddRange(dayOrders);

            // Update portfolio
            foreach (var order in dayOrders)
            {
                if (order.FilledPrice.HasValue)
                {
                    var cost = order.Quantity * order.FilledPrice.Value;
                    switch (order.Action)
                    {
                        case SignalAction.Buy:
                            currentPortfolio.Positions.TryGetValue(order.Ticker, out var existing);
                            currentPortfolio.Positions[order.Ticker] = existing + order.Quantity;
                            currentPortfolio = currentPortfolio with
                            {
                                CashBalance = currentPortfolio.CashBalance - cost
                            };
                            break;
                        case SignalAction.Sell:
                        case SignalAction.Exit:
                            currentPortfolio.Positions.TryGetValue(order.Ticker, out var current);
                            currentPortfolio.Positions[order.Ticker] = Math.Max(0, current - order.Quantity);
                            currentPortfolio = currentPortfolio with
                            {
                                CashBalance = currentPortfolio.CashBalance + cost
                            };
                            break;
                    }
                }
            }

            // Calculate portfolio value
            var holdingValue = currentPortfolio.Positions
                .Sum(pos => pos.Value * GetClosePrice(session, pos.Key, ohlcvBars));
            var totalValue = currentPortfolio.CashBalance + holdingValue;

            currentPortfolio = currentPortfolio with
            {
                AsOfDate = session,
                TotalValue = totalValue
            };

            portfolioHistory.Add(currentPortfolio);

            // Daily return
            var dailyReturn = (totalValue - previousPortfolioValue) / previousPortfolioValue;
            dailyReturns.Add((session, dailyReturn));
            previousPortfolioValue = totalValue;
        }

        logger.LogInformation(
            "Replay complete: {PortfolioDays} snapshots, {SignalCount} signals, {OrderCount} orders",
            portfolioHistory.Count, signals.Count, orders.Count);

        return new ReplayResult(
            ModelId: modelId,
            PortfolioHistory: portfolioHistory.AsReadOnly(),
            Signals: signals.AsReadOnly(),
            Orders: orders.AsReadOnly(),
            DailyReturns: dailyReturns.AsReadOnly());
    }

    private async Task<List<Signal>> GenerateSignalsAsync(
        Guid modelId,
        DateOnly date,
        IReadOnlyList<DataBackfiller.OhlcvBar> bars,
        CancellationToken cancellationToken)
    {
        // Simplified: stub model prediction
        // In production: call model.predict() with features
        await Task.Delay(10, cancellationToken);
        return new List<Signal>();
    }

    private static decimal GetClosePrice(
        DateOnly date,
        string ticker,
        IReadOnlyList<DataBackfiller.OhlcvBar> bars)
    {
        var bar = bars.FirstOrDefault(b => b.Date == date && b.Ticker == ticker);
        return bar?.Close ?? 0m;
    }
}

public sealed record ReplayResult(
    Guid ModelId,
    IReadOnlyList<ReplayEngine.Portfolio> PortfolioHistory,
    IReadOnlyList<ReplayEngine.Signal> Signals,
    IReadOnlyList<ReplayEngine.Order> Orders,
    IReadOnlyList<(DateOnly Date, decimal Return)> DailyReturns);
