using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ScadaLink.Commons.Entities.InboundApi;
using ScadaLink.ConfigurationDatabase;
using ScadaLink.ConfigurationDatabase.Repositories;

namespace ScadaLink.ConfigurationDatabase.Tests;

public class InboundApiRepositoryTests : IDisposable
{
    private readonly ScadaLinkDbContext _context;
    private readonly CapturingLogger<InboundApiRepository> _logger = new();
    private readonly InboundApiRepository _repository;

    public InboundApiRepositoryTests()
    {
        _context = SqliteTestHelper.CreateInMemoryContext();
        _repository = new InboundApiRepository(_context, _logger);
    }

    public void Dispose()
    {
        _context.Database.CloseConnection();
        _context.Dispose();
    }

    [Fact]
    public async Task AddApiKey_AndGetById_RoundTrips()
    {
        var key = new ApiKey("Key1", "secret-value-1") { IsEnabled = true };
        await _repository.AddApiKeyAsync(key);
        await _repository.SaveChangesAsync();

        var loaded = await _repository.GetApiKeyByIdAsync(key.Id);
        Assert.NotNull(loaded);
        Assert.Equal("Key1", loaded!.Name);

        var byValue = await _repository.GetApiKeyByValueAsync("secret-value-1");
        Assert.NotNull(byValue);
        Assert.Equal(key.Id, byValue!.Id);
    }

    [Fact]
    public async Task AddApiMethod_AndGetByName_RoundTrips()
    {
        var method = new ApiMethod("DoThing", "return 1;");
        await _repository.AddApiMethodAsync(method);
        await _repository.SaveChangesAsync();

        var loaded = await _repository.GetMethodByNameAsync("DoThing");
        Assert.NotNull(loaded);
        Assert.Equal(method.Id, loaded!.Id);
    }

    [Fact]
    public async Task GetApprovedKeysForMethod_WithValidCsv_ReturnsAllKeys()
    {
        var k1 = new ApiKey("K1", "v1");
        var k2 = new ApiKey("K2", "v2");
        await _repository.AddApiKeyAsync(k1);
        await _repository.AddApiKeyAsync(k2);
        await _repository.SaveChangesAsync();

        var method = new ApiMethod("M", "return 1;") { ApprovedApiKeyIds = $"{k1.Id}, {k2.Id}" };
        await _repository.AddApiMethodAsync(method);
        await _repository.SaveChangesAsync();

        var keys = await _repository.GetApprovedKeysForMethodAsync(method.Id);

        Assert.Equal(2, keys.Count);
        Assert.Empty(_logger.Warnings);
    }

    [Fact]
    public async Task GetApprovedKeysForMethod_WithMalformedCsvToken_LogsWarningAndDropsToken()
    {
        // Regression guard for ConfigurationDatabase-008: a corrupt token (a name where an
        // integer id is expected) must not be dropped silently — the corruption must be
        // observable via a logged warning, while the valid ids still resolve.
        var k1 = new ApiKey("K1", "v1");
        await _repository.AddApiKeyAsync(k1);
        await _repository.SaveChangesAsync();

        var method = new ApiMethod("M", "return 1;") { ApprovedApiKeyIds = $"{k1.Id},not-an-id" };
        await _repository.AddApiMethodAsync(method);
        await _repository.SaveChangesAsync();

        var keys = await _repository.GetApprovedKeysForMethodAsync(method.Id);

        Assert.Single(keys);
        Assert.Equal(k1.Id, keys[0].Id);
        Assert.Single(_logger.Warnings);
        Assert.Contains("not-an-id", _logger.Warnings[0]);
    }

    [Fact]
    public async Task GetApprovedKeysForMethod_WithNullOrEmptyCsv_ReturnsEmptyWithoutWarning()
    {
        var method = new ApiMethod("M", "return 1;");
        await _repository.AddApiMethodAsync(method);
        await _repository.SaveChangesAsync();

        var keys = await _repository.GetApprovedKeysForMethodAsync(method.Id);

        Assert.Empty(keys);
        Assert.Empty(_logger.Warnings);
    }

    [Fact]
    public async Task DeleteApiMethod_RemovesEntity()
    {
        var method = new ApiMethod("ToDelete", "return 1;");
        await _repository.AddApiMethodAsync(method);
        await _repository.SaveChangesAsync();

        await _repository.DeleteApiMethodAsync(method.Id);
        await _repository.SaveChangesAsync();

        Assert.Null(await _repository.GetApiMethodByIdAsync(method.Id));
    }

    [Fact]
    public void Constructor_NullContext_Throws()
    {
        Assert.Throws<ArgumentNullException>(() => new InboundApiRepository(null!));
    }
}

/// <summary>Minimal ILogger that captures warning-level messages for assertions.</summary>
internal sealed class CapturingLogger<T> : ILogger<T>
{
    public List<string> Warnings { get; } = new();

    public IDisposable BeginScope<TState>(TState state) where TState : notnull => NullScope.Instance;

    public bool IsEnabled(LogLevel logLevel) => true;

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        if (logLevel == LogLevel.Warning)
        {
            Warnings.Add(formatter(state, exception));
        }
    }

    private sealed class NullScope : IDisposable
    {
        public static readonly NullScope Instance = new();
        public void Dispose() { }
    }
}
