using GB5Shared.Connection;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.DTO.ReportOrchestration;
using GB5Shared.Enums.ReportOrchestration;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Logging;
using Npgsql;
using System.Data;
namespace FrameworkBLL.ReportOrchestration
{
///
/// Resolves a closed IDbConnection for the requested DataSourceType,
/// using MSERVERCONFIG data from IServerConfigCache.
/// Caches resolved ReportServerConfigDTO instances within the request scope.
///
public sealed class ReportConnectionResolver : IReportConnectionResolver
{
private readonly IServerConfigCache _serverConfigCache;
private readonly IApplicationConnection _appConnection;
private readonly ILogger _logger;
private readonly Dictionary _resolved = new();
public ReportConnectionResolver(
IServerConfigCache serverConfigCache,
IApplicationConnection appConnection,
ILogger logger)
{
_serverConfigCache = serverConfigCache;
_appConnection = appConnection;
_logger = logger;
}
public async Task ResolveConnectionAsync(
LoginDTO loginDTO,
DataSourceType dataSource,
CancellationToken cancellationToken = default)
{
var config = await ResolveConfigAsync(loginDTO, dataSource, cancellationToken)
.ConfigureAwait(false);
return config.ProviderType switch
{
DatabaseProviderType.SqlServer =>
new SqlConnection(config.ConnectionString),
DatabaseProviderType.PostgreSql =>
new NpgsqlConnection(config.ConnectionString),
DatabaseProviderType.Oracle =>
throw new NotSupportedException("Oracle connections are not supported by the report orchestrator."),
DatabaseProviderType.MySql =>
throw new NotSupportedException("MySQL connections are not supported by the report orchestrator."),
_ => throw new NotSupportedException(
$"Unknown DatabaseProviderType: {config.ProviderType}")
};
}
public async Task ResolveConfigAsync(
LoginDTO loginDTO,
DataSourceType dataSource,
CancellationToken cancellationToken = default)
{
if (dataSource == DataSourceType.Auto)
throw new ArgumentException(
"DataSourceType.Auto must be resolved to a concrete source before calling ResolveConfigAsync.",
nameof(dataSource));
if (_resolved.TryGetValue(dataSource, out var cached))
return cached;
var config = await BuildConfigAsync(loginDTO, dataSource, cancellationToken)
.ConfigureAwait(false);
_resolved[dataSource] = config;
return config;
}
public bool IsDedicatedDbConfigured(LoginDTO loginDTO, DataSourceType dataSource)
{
// We can answer this synchronously from cache if already loaded,
// otherwise it requires an async call — return false (unknown = fallback safe).
if (_resolved.TryGetValue(dataSource, out var config))
return config.IsDedicatedAnalyticsDb;
return false;
}
// -----------------------------------------------------------------------
private async Task BuildConfigAsync(
LoginDTO loginDTO,
DataSourceType dataSource,
CancellationToken cancellationToken)
{
// Always load the OLTP config first — it carries the ReportDB/ArchiveDB IDs
var oltpConfig = await _serverConfigCache
.GetAsync(loginDTO.ServerConfigId, cancellationToken).ConfigureAwait(false);
int targetConfigId = dataSource switch
{
DataSourceType.Oltp => loginDTO.ServerConfigId,
DataSourceType.ReportDb => oltpConfig.ReportServerConfigId,
DataSourceType.ArchiveDb => oltpConfig.ArchiveServerConfigId,
_ => loginDTO.ServerConfigId
};
bool dedicated = dataSource != DataSourceType.Oltp && targetConfigId != -1;
if (!dedicated)
{
// Fall back to OLTP
if (dataSource != DataSourceType.Oltp)
_logger.LogWarning(
"No dedicated {DataSource} DB configured for ServerConfigId={Id}; falling back to OLTP.",
dataSource, loginDTO.ServerConfigId);
return BuildDto(oltpConfig, DataSourceType.Oltp, isDedicated: false);
}
var targetConfig = await _serverConfigCache
.GetAsync(targetConfigId, cancellationToken).ConfigureAwait(false);
return BuildDto(targetConfig, dataSource, isDedicated: true);
}
private static ReportServerConfigDTO BuildDto(
CachedServerConfig cfg,
DataSourceType resolvedSource,
bool isDedicated)
{
string connStr = cfg.ProviderType switch
{
DatabaseProviderType.SqlServer =>
$"Data Source={cfg.ServerIp};" +
$"Initial Catalog={cfg.DatabaseName};" +
$"User Id={cfg.DatabaseUserName};" +
$"Password={cfg.DatabasePassword};" +
"Pooling=true;Connection Timeout=120;Max Pool Size=250;Connection Lifetime=20;" +
"Encrypt=False;TrustServerCertificate=True",
DatabaseProviderType.PostgreSql =>
$"Host={cfg.ServerIp};Port=5432;" +
$"Database={cfg.DatabaseName};" +
$"Username={cfg.DatabaseUserName.ToLower()};" +
$"Password={cfg.DatabasePassword};" +
"Pooling=true;Timeout=120;Maximum Pool Size=250",
_ => throw new NotSupportedException(
$"Provider {cfg.ProviderType} is not supported for report connections.")
};
return new ReportServerConfigDTO
{
ServerConfigId = cfg.ServerConfigId,
ProviderType = cfg.ProviderType,
ConnectionString = connStr,
DatabaseName = cfg.DatabaseName,
IsDedicatedAnalyticsDb = isDedicated,
ResolvedDataSource = resolvedSource
};
}
}
}