using Microsoft.Data.SqlClient;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace XLAB2.Infrastructure;

internal sealed class SqlServerConnectionFactory : IDatabaseConnectionFactory
{
    private static readonly Lazy<IDatabaseConnectionFactory> CurrentFactory = new(() => new SqlServerConnectionFactory(DatabaseConfiguration.Current), LazyThreadSafetyMode.ExecutionAndPublication);

    private readonly DatabaseOptions _options;

    public SqlServerConnectionFactory(DatabaseOptions options)
    {
        _options = options ?? throw new ArgumentNullException(nameof(options));
        ConnectionString = BuildConnectionString(options);
    }

    public static IDatabaseConnectionFactory Current => CurrentFactory.Value;

    public string ConnectionString { get; }

    public DatabaseOptions Options => _options;

    public SqlConnection CreateConnection()
    {
        return new SqlConnection(ConnectionString);
    }

    public async Task<SqlConnection> OpenConnectionAsync(CancellationToken cancellationToken = default)
    {
        var connection = CreateConnection();

        try
        {
            await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
            return connection;
        }
        catch
        {
            await connection.DisposeAsync().ConfigureAwait(false);
            throw;
        }
    }

    internal static string BuildConnectionString(DatabaseOptions options)
    {
        var builder = new SqlConnectionStringBuilder
        {
            ApplicationName = string.IsNullOrWhiteSpace(options.ApplicationName) ? "XLAB2" : options.ApplicationName.Trim(),
            ConnectRetryCount = Math.Max(0, options.ConnectRetryCount),
            ConnectRetryInterval = Math.Max(1, options.ConnectRetryIntervalSeconds),
            ConnectTimeout = Math.Max(1, options.ConnectTimeoutSeconds),
            DataSource = options.Server.Trim(),
            Encrypt = options.Encrypt,
            InitialCatalog = options.Database.Trim(),
            IntegratedSecurity = options.IntegratedSecurity,
            MaxPoolSize = Math.Max(1, options.MaxPoolSize),
            MinPoolSize = Math.Max(0, options.MinPoolSize),
            MultipleActiveResultSets = options.MultipleActiveResultSets,
            Pooling = options.Pooling,
            TrustServerCertificate = options.TrustServerCertificate
        };

        return builder.ConnectionString;
    }
}
