using System.Diagnostics;
using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.Infrastructure;

public enum DatabaseHealthState
{
    NotConfigured,
    Healthy,
    Unhealthy,
    TimedOut
}

public sealed record DatabaseHealthStatus(
    DataSourceKind Source,
    DatabaseHealthState State,
    DateTimeOffset CheckedAt,
    TimeSpan Elapsed,
    string UserMessage,
    bool? IsTransient = null,
    int? ProviderErrorCode = null);

public interface IDatabaseHealthService
{
    Task<DatabaseHealthStatus> CheckAsync(
        DataSourceKind source,
        CancellationToken cancellationToken = default);

    Task<IReadOnlyList<DatabaseHealthStatus>> CheckAllAsync(
        CancellationToken cancellationToken = default);
}

public sealed class DatabaseHealthService : IDatabaseHealthService
{
    private readonly IDataQueryExecutor _executor;
    private readonly DatabaseOptions _options;

    public DatabaseHealthService(IDataQueryExecutor executor, DatabaseOptions options)
    {
        _executor = executor ?? throw new ArgumentNullException(nameof(executor));
        _options = options ?? throw new ArgumentNullException(nameof(options));
    }

    public async Task<DatabaseHealthStatus> CheckAsync(
        DataSourceKind source,
        CancellationToken cancellationToken = default)
    {
        var checkedAt = DateTimeOffset.Now;
        if (!_options.IsConfigured(source))
        {
            return new DatabaseHealthStatus(
                source,
                DatabaseHealthState.NotConfigured,
                checkedAt,
                TimeSpan.Zero,
                "연결 정보가 설정되지 않았습니다.",
                IsTransient: null,
                ProviderErrorCode: null);
        }

        var stopwatch = Stopwatch.StartNew();
        try
        {
            var query = source switch
            {
                DataSourceKind.Oracle => "SELECT 1 FROM DUAL",
                DataSourceKind.MariaDb => "SELECT 1",
                _ => throw new ArgumentOutOfRangeException(nameof(source), source, null)
            };

            await _executor
                .ExecuteAsync(source, $"{source}Health", query, cancellationToken)
                .ConfigureAwait(false);

            return new DatabaseHealthStatus(
                source,
                DatabaseHealthState.Healthy,
                checkedAt,
                stopwatch.Elapsed,
                "연결 정상",
                IsTransient: null,
                ProviderErrorCode: null);
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            throw;
        }
        catch (DatabaseQueryTimeoutException exception)
        {
            return new DatabaseHealthStatus(
                source,
                DatabaseHealthState.TimedOut,
                checkedAt,
                stopwatch.Elapsed,
                exception.Message,
                exception.IsTransient,
                exception.ProviderErrorCode);
        }
        catch (DatabaseOperationException exception)
        {
            return new DatabaseHealthStatus(
                source,
                DatabaseHealthState.Unhealthy,
                checkedAt,
                stopwatch.Elapsed,
                exception.Message,
                exception.IsTransient,
                exception.ProviderErrorCode);
        }
        catch (DatabaseInfrastructureException exception)
        {
            return new DatabaseHealthStatus(
                source,
                DatabaseHealthState.Unhealthy,
                checkedAt,
                stopwatch.Elapsed,
                exception.Message,
                IsTransient: null,
                ProviderErrorCode: null);
        }
        catch
        {
            return new DatabaseHealthStatus(
                source,
                DatabaseHealthState.Unhealthy,
                checkedAt,
                stopwatch.Elapsed,
                "데이터베이스 연결 상태를 확인할 수 없습니다.",
                IsTransient: null,
                ProviderErrorCode: null);
        }
    }

    public async Task<IReadOnlyList<DatabaseHealthStatus>> CheckAllAsync(
        CancellationToken cancellationToken = default)
    {
        var checks = Enum
            .GetValues<DataSourceKind>()
            .Select(source => CheckAsync(source, cancellationToken));
        return await Task.WhenAll(checks).ConfigureAwait(false);
    }
}
