using MMoneyCoderSharp.Data;

namespace MBN_STOCK_WEBVIEW.Infrastructure;

public enum MariaDbTlsMode
{
    Disabled,
    Preferred,
    Required,
    VerifyCertificate,
    VerifyIdentity
}

public sealed class DatabaseOptions
{
    public OracleDatabaseOptions Oracle { get; set; } = new();

    public MariaDbDatabaseOptions MariaDb { get; set; } = new();

    public DatabaseResilienceOptions Resilience { get; set; } = new();

    public bool IsConfigured(DataSourceKind source) => GetConfigurationErrors(source).Count == 0;

    public IReadOnlyList<string> GetConfigurationErrors(DataSourceKind source) => source switch
    {
        DataSourceKind.Oracle => Oracle.GetConfigurationErrors(),
        DataSourceKind.MariaDb => MariaDb.GetConfigurationErrors(),
        _ => ["DataSource"]
    };

    internal void Normalize()
    {
        Oracle ??= new OracleDatabaseOptions();
        MariaDb ??= new MariaDbDatabaseOptions();
        Resilience ??= new DatabaseResilienceOptions();
    }

    internal void ValidateRuntimeSettings()
    {
        if (Resilience.OperationTimeoutSeconds is < 1 or > 600)
        {
            throw new DatabaseConfigurationException(
                "OperationTimeoutSeconds는 1초에서 600초 사이여야 합니다.");
        }

        if (Resilience.MaximumRetryCount is < 0 or > 5)
        {
            throw new DatabaseConfigurationException(
                "MaximumRetryCount는 0에서 5 사이여야 합니다.");
        }

        if (Resilience.InitialRetryDelayMilliseconds is < 0 or > 30_000)
        {
            throw new DatabaseConfigurationException(
                "InitialRetryDelayMilliseconds는 0에서 30000 사이여야 합니다.");
        }

        Oracle.ValidateTimeouts();
        MariaDb.ValidateTimeouts();
    }

    public override string ToString() =>
        $"DatabaseOptions {{ Oracle = {ConfigurationState(Oracle.GetConfigurationErrors())}, " +
        $"MariaDb = {ConfigurationState(MariaDb.GetConfigurationErrors())}, " +
        $"Resilience = {Resilience} }}";

    private static string ConfigurationState(IReadOnlyCollection<string> errors) =>
        errors.Count == 0 ? "Configured" : "NotConfigured";
}

public sealed class OracleDatabaseOptions
{
    public string? Host { get; set; }

    public int Port { get; set; } = 1521;

    public string? Sid { get; set; }

    public string? ServiceName { get; set; }

    public string? UserName { get; set; }

    public string? Password { get; set; }

    public int ConnectTimeoutSeconds { get; set; } = 10;

    public int CommandTimeoutSeconds { get; set; } = 30;

    public bool Pooling { get; set; } = true;

    internal IReadOnlyList<string> GetConfigurationErrors()
    {
        var errors = new List<string>();
        AddIfBlank(errors, Host, nameof(Host));
        AddIfBlank(errors, UserName, nameof(UserName));
        AddIfBlank(errors, Password, nameof(Password));

        var hasSid = !string.IsNullOrWhiteSpace(Sid);
        var hasServiceName = !string.IsNullOrWhiteSpace(ServiceName);
        if (hasSid == hasServiceName)
        {
            errors.Add("SidOrServiceName");
        }

        if (Port is < 1 or > 65_535)
        {
            errors.Add(nameof(Port));
        }

        return errors;
    }

    internal void ValidateTimeouts()
    {
        ValidateTimeout(ConnectTimeoutSeconds, nameof(ConnectTimeoutSeconds));
        ValidateTimeout(CommandTimeoutSeconds, nameof(CommandTimeoutSeconds));
    }

    public override string ToString() =>
        $"OracleDatabaseOptions {{ State = {(GetConfigurationErrors().Count == 0 ? "Configured" : "NotConfigured")}, " +
        $"Port = {Port}, ConnectTimeoutSeconds = {ConnectTimeoutSeconds}, " +
        $"CommandTimeoutSeconds = {CommandTimeoutSeconds}, Pooling = {Pooling} }}";

    private static void ValidateTimeout(int value, string settingName)
    {
        if (value is < 1 or > 600)
        {
            throw new DatabaseConfigurationException($"{settingName}는 1초에서 600초 사이여야 합니다.");
        }
    }

    private static void AddIfBlank(ICollection<string> errors, string? value, string name)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            errors.Add(name);
        }
    }
}

public sealed class MariaDbDatabaseOptions
{
    public string? Host { get; set; }

    public int Port { get; set; } = 3306;

    public string? Database { get; set; }

    public string? UserName { get; set; }

    public string? Password { get; set; }

    public MariaDbTlsMode TlsMode { get; set; } = MariaDbTlsMode.Preferred;

    public int ConnectTimeoutSeconds { get; set; } = 10;

    public int CommandTimeoutSeconds { get; set; } = 30;

    public bool Pooling { get; set; } = true;

    internal IReadOnlyList<string> GetConfigurationErrors()
    {
        var errors = new List<string>();
        AddIfBlank(errors, Host, nameof(Host));
        AddIfBlank(errors, Database, nameof(Database));
        AddIfBlank(errors, UserName, nameof(UserName));
        AddIfBlank(errors, Password, nameof(Password));

        if (Port is < 1 or > 65_535)
        {
            errors.Add(nameof(Port));
        }

        return errors;
    }

    internal void ValidateTimeouts()
    {
        ValidateTimeout(ConnectTimeoutSeconds, nameof(ConnectTimeoutSeconds));
        ValidateTimeout(CommandTimeoutSeconds, nameof(CommandTimeoutSeconds));
    }

    public override string ToString() =>
        $"MariaDbDatabaseOptions {{ State = {(GetConfigurationErrors().Count == 0 ? "Configured" : "NotConfigured")}, " +
        $"Port = {Port}, TlsMode = {TlsMode}, ConnectTimeoutSeconds = {ConnectTimeoutSeconds}, " +
        $"CommandTimeoutSeconds = {CommandTimeoutSeconds}, Pooling = {Pooling} }}";

    private static void ValidateTimeout(int value, string settingName)
    {
        if (value is < 1 or > 600)
        {
            throw new DatabaseConfigurationException($"{settingName}는 1초에서 600초 사이여야 합니다.");
        }
    }

    private static void AddIfBlank(ICollection<string> errors, string? value, string name)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            errors.Add(name);
        }
    }
}

public sealed class DatabaseResilienceOptions
{
    public int OperationTimeoutSeconds { get; set; } = 45;

    public int MaximumRetryCount { get; set; } = 2;

    public int InitialRetryDelayMilliseconds { get; set; } = 250;

    public override string ToString() =>
        $"DatabaseResilienceOptions {{ OperationTimeoutSeconds = {OperationTimeoutSeconds}, " +
        $"MaximumRetryCount = {MaximumRetryCount}, " +
        $"InitialRetryDelayMilliseconds = {InitialRetryDelayMilliseconds} }}";
}
