using Dapper;
using KArtSell.Host.Infrastructure;
using Npgsql;
using Xunit;

namespace KArtSell.Integration.Tests;

[Collection("Database")]
public class RateLimiterServiceTests : IAsyncLifetime
{
    private readonly NpgsqlDataSource _dataSource;
    private readonly RateLimiterService _service;

    public RateLimiterServiceTests(DatabaseFixture fixture)
    {
        _dataSource = fixture.DataSource;
        _service = new RateLimiterService(_dataSource, fixture.Clock(), fixture.Logger<RateLimiterService>());
    }

    public async Task InitializeAsync()
    {
        await using var conn = await _dataSource.OpenConnectionAsync();
        await conn.ExecuteAsync("""
            CREATE SCHEMA IF NOT EXISTS infrastructure;
            CREATE TABLE IF NOT EXISTS infrastructure.rate_limit_quota (
                id BIGSERIAL PRIMARY KEY,
                api_name VARCHAR(50) NOT NULL UNIQUE,
                limit_count INT NOT NULL,
                window_seconds INT NOT NULL,
                current_tokens DECIMAL NOT NULL,
                last_reset_at TIMESTAMP WITH TIME ZONE NOT NULL,
                updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
                published_at TIMESTAMP WITH TIME ZONE NOT NULL
            );
            CREATE TABLE IF NOT EXISTS infrastructure.rate_limit_events (
                id BIGSERIAL PRIMARY KEY,
                api_name VARCHAR(50) NOT NULL,
                action VARCHAR(50) NOT NULL,
                executed_at TIMESTAMP WITH TIME ZONE NOT NULL,
                published_at TIMESTAMP WITH TIME ZONE NOT NULL
            );
            TRUNCATE infrastructure.rate_limit_quota CASCADE;
            """);

        await _service.InitializeAsync();
    }

    public Task DisposeAsync() => Task.CompletedTask;

    [Fact]
    public async Task TryConsumeAsync_ReturnsTrue_WhenTokensAvailable()
    {
        // Act
        var (success, _) = await _service.TryConsumeAsync("krx");

        // Assert
        Assert.True(success);
    }

    [Fact]
    public async Task TryConsumeAsync_ExhaustsQuota_AfterLimitReached()
    {
        // Arrange - KRX limit is 100/min
        // Act - Consume all tokens
        for (int i = 0; i < 100; i++)
        {
            var (success, _) = await _service.TryConsumeAsync("krx");
            Assert.True(success);
        }

        // Act - 101st attempt should fail
        var (finalSuccess, retryAfter) = await _service.TryConsumeAsync("krx");

        // Assert
        Assert.False(finalSuccess);
        Assert.Equal(60, retryAfter);  // Window is 60 seconds
    }

    [Fact]
    public async Task ResetQuotaAsync_Idempotent_RestoresTokens()
    {
        // Arrange - Consume some tokens
        for (int i = 0; i < 50; i++)
            await _service.TryConsumeAsync("opendart");

        // Act - Reset quota
        await _service.ResetQuotaAsync("opendart");

        // Assert - Tokens restored
        var (success, _) = await _service.TryConsumeAsync("opendart");
        Assert.True(success);
    }
}
