using Xunit;
using KArtSell.Modules.ModelOperations.ShadowRun.Services;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;

namespace KArtSell.Integration.Tests;

/// <summary>
/// Tests for KRX data service: caching, retry logic, PIT-safe lookups.
/// </summary>
public sealed class KrxDataServiceTests : IAsyncLifetime
{
    private IMemoryCache _cache = null!;
    private ILogger<KrxDataService> _logger = null!;
    private HttpClient _httpClient = null!;

    public Task InitializeAsync()
    {
        _cache = new MemoryCache(new MemoryCacheOptions());
        _logger = new NoOpLogger<KrxDataService>();

        // Use HttpClient without network to prevent real KRX API calls (AGENTS.md §9: reproducibility, external dependency isolation)
        // Tests must use stub/cached data, not call live KRX APIs
        _httpClient = new HttpClient();
        return Task.CompletedTask;
    }

    public Task DisposeAsync()
    {
        _cache?.Dispose();
        _httpClient?.Dispose();
        return Task.CompletedTask;
    }

    [Fact]
    public async Task GetDailyOhlcvAsync_ReturnsBarsForTickerAndDateRange()
    {
        // Arrange
        var service = new KrxDataService(_httpClient, _cache, _logger);
        var ticker = "005930";  // Samsung
        var startDate = new DateOnly(2024, 1, 2);
        var endDate = new DateOnly(2024, 1, 5);

        // Act
        var bars = await service.GetDailyOhlcvAsync(ticker, startDate, endDate, CancellationToken.None);

        // Assert
        Assert.NotEmpty(bars);
        Assert.All(bars, bar =>
        {
            Assert.Equal(ticker, bar.Ticker);
            Assert.True(bar.Date >= startDate && bar.Date <= endDate);
            Assert.True(bar.Close > 0);
            Assert.True(bar.High >= bar.Close);
            Assert.True(bar.Low <= bar.Close);
        });
    }

    [Fact]
    public async Task GetDailyOhlcvAsync_CacheHit_ReturnsCachedData()
    {
        // Arrange
        var service = new KrxDataService(_httpClient, _cache, _logger);
        var ticker = "005930";
        var startDate = new DateOnly(2024, 1, 2);
        var endDate = new DateOnly(2024, 1, 5);

        // Act: First call
        var bars1 = await service.GetDailyOhlcvAsync(ticker, startDate, endDate, CancellationToken.None);

        // Act: Second call (should hit cache)
        var bars2 = await service.GetDailyOhlcvAsync(ticker, startDate, endDate, CancellationToken.None);

        // Assert: Same data structure (values equal, not necessarily same reference)
        Assert.Equal(bars1.Count, bars2.Count);
        Assert.All(Enumerable.Range(0, bars1.Count), i =>
        {
            Assert.Equal(bars1[i].Ticker, bars2[i].Ticker);
            Assert.Equal(bars1[i].Date, bars2[i].Date);
            Assert.Equal(bars1[i].Close, bars2[i].Close);
        });
    }

    [Fact]
    public async Task GetFeeScheduleAsync_ReturnsFeeEntries()
    {
        // Arrange
        var service = new KrxDataService(_httpClient, _cache, _logger);
        var startDate = new DateOnly(2024, 1, 2);
        var endDate = new DateOnly(2024, 1, 31);

        // Act
        var fees = await service.GetFeeScheduleAsync(startDate, endDate, CancellationToken.None);

        // Assert
        Assert.NotEmpty(fees);
        Assert.All(fees, fee =>
        {
            Assert.True(fee.TransactionFeePercent > 0);
            Assert.True(fee.SlippagePercent > 0);
        });
    }

    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) { }
    }
}
