using System.Data;
using MBN_STOCK_WEBVIEW.Infrastructure;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.Infrastructure.Tests;

public sealed class DatabaseHealthServiceTests
{
    [Fact]
    public async Task CheckAsync_Unconfigured_ReturnsNotConfiguredWithoutQuery()
    {
        var executor = new StubDataQueryExecutor();
        var service = new DatabaseHealthService(executor, new DatabaseOptions());

        var status = await service.CheckAsync(DataSourceKind.Oracle);

        Assert.Equal(DatabaseHealthState.NotConfigured, status.State);
        Assert.Equal(DataSourceKind.Oracle, status.Source);
        Assert.Equal(TimeSpan.Zero, status.Elapsed);
        Assert.Null(status.IsTransient);
        Assert.Null(status.ProviderErrorCode);
        Assert.Equal(0, executor.CallCount);
    }

    [Theory]
    [InlineData(DataSourceKind.Oracle, "SELECT 1 FROM DUAL")]
    [InlineData(DataSourceKind.MariaDb, "SELECT 1")]
    public async Task CheckAsync_ConfiguredAndQuerySucceeds_ReturnsHealthy(
        DataSourceKind source,
        string expectedQuery)
    {
        var executor = new StubDataQueryExecutor
        {
            ExecuteAsyncHandler = (_, _, _, _) => Task.FromResult(new DataTable())
        };
        var service = new DatabaseHealthService(executor, TestOptions.CreateConfigured());

        var status = await service.CheckAsync(source);

        Assert.Equal(DatabaseHealthState.Healthy, status.State);
        Assert.Equal(source, status.Source);
        Assert.Null(status.IsTransient);
        Assert.Null(status.ProviderErrorCode);
        Assert.Equal(1, executor.CallCount);
        Assert.Equal(expectedQuery, executor.LastQuery);
    }

    [Fact]
    public async Task CheckAsync_ConfiguredAndQueryFails_ReturnsUnhealthyWithoutThrowing()
    {
        const string host = "health-host-sensitive-sentinel";
        var executor = new StubDataQueryExecutor
        {
            ExecuteAsyncHandler = (_, _, _, _) => Task.FromException<DataTable>(
                new DatabaseQueryException(
                    DataSourceKind.MariaDb,
                    isTransient: true,
                    providerErrorCode: 1042))
        };
        var service = new DatabaseHealthService(executor, TestOptions.CreateConfigured());

        var status = await service.CheckAsync(DataSourceKind.MariaDb);

        Assert.Equal(DatabaseHealthState.Unhealthy, status.State);
        Assert.Equal(DataSourceKind.MariaDb, status.Source);
        Assert.True(status.IsTransient);
        Assert.Equal(1042, status.ProviderErrorCode);
        Assert.DoesNotContain(host, status.UserMessage, StringComparison.Ordinal);
    }

    [Fact]
    public async Task CheckAsync_ConfiguredAndQueryTimesOut_ReturnsTimedOut()
    {
        var executor = new StubDataQueryExecutor
        {
            ExecuteAsyncHandler = (_, _, _, _) => Task.FromException<DataTable>(
                new DatabaseQueryTimeoutException(DataSourceKind.Oracle))
        };
        var service = new DatabaseHealthService(executor, TestOptions.CreateConfigured());

        var status = await service.CheckAsync(DataSourceKind.Oracle);

        Assert.Equal(DatabaseHealthState.TimedOut, status.State);
        Assert.Equal(DataSourceKind.Oracle, status.Source);
        Assert.True(status.IsTransient);
        Assert.Null(status.ProviderErrorCode);
    }

    [Fact]
    public async Task CheckAsync_NonOperationInfrastructureFailure_DoesNotInventDiagnostics()
    {
        var executor = new StubDataQueryExecutor
        {
            ExecuteAsyncHandler = (_, _, _, _) => Task.FromException<DataTable>(
                new DatabaseConfigurationException("safe configuration failure"))
        };
        var service = new DatabaseHealthService(executor, TestOptions.CreateConfigured());

        var status = await service.CheckAsync(DataSourceKind.Oracle);

        Assert.Equal(DatabaseHealthState.Unhealthy, status.State);
        Assert.Null(status.IsTransient);
        Assert.Null(status.ProviderErrorCode);
    }
}

internal sealed class StubDataQueryExecutor : IDataQueryExecutor
{
    public Func<DataSourceKind, string, string, CancellationToken, Task<DataTable>>? ExecuteAsyncHandler
    {
        get;
        init;
    }

    public int CallCount { get; private set; }

    public string? LastQuery { get; private set; }

    public DataTable Execute(DataSourceKind source, string tableName, string query) =>
        ExecuteAsync(source, tableName, query).GetAwaiter().GetResult();

    public Task<DataTable> ExecuteAsync(
        DataSourceKind source,
        string tableName,
        string query,
        CancellationToken cancellationToken = default)
    {
        CallCount++;
        LastQuery = query;
        return ExecuteAsyncHandler?.Invoke(source, tableName, query, cancellationToken)
            ?? Task.FromResult(new DataTable());
    }
}
