using Xunit;
using KArtSell.BuildingBlocks.Time;
using KArtSell.Modules.ModelOperations.ShadowRun;
using Microsoft.Extensions.Logging;

namespace KArtSell.Integration.Tests;

/// <summary>
/// Pure unit tests for Shadow Run components.
/// NO external API calls, NO database, NO I/O.
/// Uses strict mocks/stubs.
/// </summary>
public sealed class ShadowRunUnitTests
{
    private readonly ILogger<DataBackfiller> _backfillerLogger = new NoOpLogger<DataBackfiller>();
    private readonly ILogger<ReplayEngine> _replayLogger = new NoOpLogger<ReplayEngine>();
    private readonly ILogger<MetricsCalculator> _calculatorLogger = new NoOpLogger<MetricsCalculator>();

    /// <summary>
    /// DataBackfiller should generate OHLCV bars for all trading days.
    /// </summary>
    [Fact]
    public async Task DataBackfiller_GeneratesOhlcvBars_ForAllTradingDays()
    {
        // Arrange
        var calendar = new StubMarketCalendar();
        var krxData = new StubKrxData();
        var backfiller = new DataBackfiller(calendar, krxData, _backfillerLogger);

        // Act: 30-day window
        var bars = await backfiller.BackfillOhlcvAsync(
            new DateOnly(2026, 4, 1),
            new DateOnly(2026, 4, 30),
            new[] { "KOSPI", "KOSDAQ" }.ToList(),
            CancellationToken.None);

        // Assert
        Assert.NotEmpty(bars);
        Assert.True(bars.Count >= 20, $"Expected 20+ bars (trading days), got {bars.Count}");

        // Verify both tickers present
        var tickers = bars.Select(b => b.Ticker).Distinct().ToList();
        Assert.Contains("KOSPI", tickers);
        Assert.Contains("KOSDAQ", tickers);
    }

    /// <summary>
    /// ReplayEngine should handle zero-order scenario gracefully.
    /// </summary>
    [Fact]
    public async Task ReplayEngine_HandlesZeroOrders_WithoutCrash()
    {
        // Arrange
        var replay = new ReplayEngine(_replayLogger);
        var bars = new List<DataBackfiller.OhlcvBar>
        {
            new(new DateOnly(2026, 4, 1), "KOSPI", 2500, 2510, 2490, 2505, 1_000_000),
            new(new DateOnly(2026, 4, 2), "KOSPI", 2505, 2515, 2495, 2510, 1_000_000),
        };
        var fees = new List<DataBackfiller.FeeScheduleEntry>
        {
            new(new DateOnly(2026, 4, 1), 0.001m, 0.0005m),
        };
        var sessions = new[] { new DateOnly(2026, 4, 1), new DateOnly(2026, 4, 2) }.ToList();

        // Act
        var result = await replay.ReplayAsync(
            Guid.NewGuid(), bars, fees, 10_000_000m, sessions, CancellationToken.None);

        // Assert
        Assert.NotNull(result);
        Assert.NotEmpty(result.PortfolioHistory);
        Assert.Equal(sessions.Count, result.PortfolioHistory.Count);
    }

    /// <summary>
    /// DataBackfiller should validate completeness.
    /// </summary>
    [Fact]
    public async Task DataBackfiller_ValidatesCompleteness()
    {
        // Arrange
        var calendar = new StubMarketCalendar();
        var krxData = new StubKrxData();
        var backfiller = new DataBackfiller(calendar, krxData, _backfillerLogger);

        var bars = new List<DataBackfiller.OhlcvBar>
        {
            new(new DateOnly(2026, 4, 1), "KOSPI", 2500, 2510, 2490, 2505, 1_000_000),
            // Missing KOSDAQ on 2026-04-01
        };

        var fees = new List<DataBackfiller.FeeScheduleEntry>
        {
            new(new DateOnly(2026, 4, 1), 0.001m, 0.0005m),
        };

        // Act
        var result = await backfiller.ValidateAsync(
            bars, fees,
            new[] { "KOSPI", "KOSDAQ" }.ToList(),
            new DateOnly(2026, 4, 1),
            new DateOnly(2026, 4, 2),
            CancellationToken.None);

        // Assert
        Assert.True(result.HasIssues);
        Assert.NotEmpty(result.MissingTickers ?? new List<string>());
    }

    // Stub implementations (no real I/O)
    private sealed class StubMarketCalendar : IMarketCalendarService
    {
        public Task<IReadOnlyList<DateOnly>> GetTradingSessionsAsync(
            DateOnly start, DateOnly end, CancellationToken ct)
        {
            var sessions = new List<DateOnly>();
            for (var d = start; d <= end; d = d.AddDays(1))
            {
                if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
                    sessions.Add(d);
            }
            return Task.FromResult<IReadOnlyList<DateOnly>>(sessions.AsReadOnly());
        }
    }

    private sealed class StubKrxData : IKrxDataService
    {
        public Task<IReadOnlyList<DataBackfiller.OhlcvBar>> GetDailyOhlcvAsync(
            string ticker, DateOnly start, DateOnly endDate, CancellationToken ct)
        {
            var bars = new List<DataBackfiller.OhlcvBar>();
            for (var d = start; d <= endDate; d = d.AddDays(1))
            {
                if (d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Sunday)
                    bars.Add(new DataBackfiller.OhlcvBar(
                        d, ticker, 2500, 2510, 2490, 2505, 1_000_000));
            }
            return Task.FromResult<IReadOnlyList<DataBackfiller.OhlcvBar>>(bars.AsReadOnly());
        }

        public Task<IReadOnlyList<DataBackfiller.FeeScheduleEntry>> GetFeeScheduleAsync(
            DateOnly start, DateOnly endDate, CancellationToken ct)
        {
            return Task.FromResult<IReadOnlyList<DataBackfiller.FeeScheduleEntry>>(
                new[] { new DataBackfiller.FeeScheduleEntry(start, 0.001m, 0.0005m) }
                    .ToList().AsReadOnly());
        }
    }

    private sealed class NoOpLogger<T> : ILogger<T>
    {
        public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
        public bool IsEnabled(LogLevel logLevel) => false;
        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
            Func<TState, Exception?, string> formatter) { }
    }
}
