using System.Data.Common;
using MMoneyCoderSharp.Data;
using MySqlConnector;
using Oracle.ManagedDataAccess.Client;

namespace MBN_STOCK_WEBVIEW.Infrastructure;

public sealed class ProviderDatabaseConnectionFactory : IDatabaseConnectionFactory
{
    private readonly DatabaseOptions _options;

    public ProviderDatabaseConnectionFactory(DatabaseOptions options)
    {
        _options = options ?? throw new ArgumentNullException(nameof(options));
        _options.Normalize();
        _options.ValidateRuntimeSettings();
    }

    public DbConnection Create(DataSourceKind source)
    {
        var errors = _options.GetConfigurationErrors(source);
        if (errors.Count > 0)
        {
            throw new DatabaseNotConfiguredException(source, errors);
        }

        try
        {
            return source switch
            {
                DataSourceKind.Oracle => CreateOracleConnection(),
                DataSourceKind.MariaDb => CreateMariaDbConnection(),
                _ => throw new DatabaseConfigurationException("지원하지 않는 데이터 원본입니다.")
            };
        }
        catch (DatabaseInfrastructureException)
        {
            throw;
        }
        catch
        {
            throw new DatabaseConfigurationException(
                $"{source} 데이터베이스 연결 설정을 구성할 수 없습니다.");
        }
    }

    public int GetCommandTimeoutSeconds(DataSourceKind source) => source switch
    {
        DataSourceKind.Oracle => _options.Oracle.CommandTimeoutSeconds,
        DataSourceKind.MariaDb => _options.MariaDb.CommandTimeoutSeconds,
        _ => throw new DatabaseConfigurationException("지원하지 않는 데이터 원본입니다.")
    };

    private OracleConnection CreateOracleConnection()
    {
        var options = _options.Oracle;
        var host = RequireOracleDescriptorToken(options.Host, nameof(options.Host));
        var identifierName = string.IsNullOrWhiteSpace(options.ServiceName) ? "SID" : "SERVICE_NAME";
        var identifierValue = string.IsNullOrWhiteSpace(options.ServiceName)
            ? RequireOracleDescriptorToken(options.Sid, nameof(options.Sid))
            : RequireOracleDescriptorToken(options.ServiceName, nameof(options.ServiceName));

        var dataSource =
            $"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={host})(PORT={options.Port}))" +
            $"(CONNECT_DATA=(SERVER=DEDICATED)({identifierName}={identifierValue})))";

        var builder = new OracleConnectionStringBuilder
        {
            DataSource = dataSource,
            UserID = options.UserName,
            Password = options.Password,
            Pooling = options.Pooling,
            PersistSecurityInfo = false,
            ConnectionTimeout = options.ConnectTimeoutSeconds
        };

        return new OracleConnection(builder.ConnectionString);
    }

    private MySqlConnection CreateMariaDbConnection()
    {
        var options = _options.MariaDb;
        var builder = new MySqlConnectionStringBuilder
        {
            Server = options.Host,
            Port = checked((uint)options.Port),
            Database = options.Database,
            UserID = options.UserName,
            Password = options.Password,
            Pooling = options.Pooling,
            PersistSecurityInfo = false,
            ConnectionTimeout = checked((uint)options.ConnectTimeoutSeconds),
            DefaultCommandTimeout = checked((uint)options.CommandTimeoutSeconds),
            CancellationTimeout = 2,
            SslMode = options.TlsMode switch
            {
                MariaDbTlsMode.Disabled => MySqlSslMode.None,
                MariaDbTlsMode.Preferred => MySqlSslMode.Preferred,
                MariaDbTlsMode.Required => MySqlSslMode.Required,
                MariaDbTlsMode.VerifyCertificate => MySqlSslMode.VerifyCA,
                MariaDbTlsMode.VerifyIdentity => MySqlSslMode.VerifyFull,
                _ => throw new DatabaseConfigurationException("MariaDB TLS 설정이 올바르지 않습니다.")
            }
        };

        return new MySqlConnection(builder.ConnectionString);
    }

    private static string RequireOracleDescriptorToken(string? value, string settingName)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new DatabaseConfigurationException($"Oracle {settingName} 설정이 필요합니다.");
        }

        if (value.IndexOfAny(['(', ')', '=', ';', '\'', '"', '\r', '\n']) >= 0)
        {
            throw new DatabaseConfigurationException($"Oracle {settingName} 설정 형식이 올바르지 않습니다.");
        }

        return value.Trim();
    }
}
