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

namespace MBN_STOCK_WEBVIEW.Infrastructure;

public interface ITransientDatabaseErrorDetector
{
    bool IsTransient(DataSourceKind source, Exception exception);

    bool IsTimeout(DataSourceKind source, Exception exception);

    int? GetProviderErrorCode(DataSourceKind source, Exception exception);
}

public sealed class TransientDatabaseErrorDetector : ITransientDatabaseErrorDetector
{
    private static readonly HashSet<int> OracleTransientErrors =
    [
        28,
        1033,
        1034,
        1089,
        1090,
        3113,
        3114,
        12153,
        12154,
        12170,
        12514,
        12516,
        12518,
        12520,
        12528,
        12537,
        12541,
        12543,
        12545,
        12547,
        12560,
        12571
    ];

    private static readonly HashSet<int> MariaDbTransientErrors =
    [
        1040,
        1042,
        1043,
        1047,
        1081,
        1129,
        1130,
        1152,
        1153,
        1154,
        1155,
        1156,
        1157,
        1158,
        1159,
        1160,
        1161,
        1205,
        1213,
        1927,
        2002,
        2003,
        2004,
        2005,
        2006,
        2012,
        2013,
        2014,
        2047,
        2055
    ];

    public bool IsTransient(DataSourceKind source, Exception exception)
    {
        if (exception is SocketException or IOException)
        {
            return true;
        }

        return source switch
        {
            DataSourceKind.Oracle when exception is OracleException oracleException =>
                OracleTransientErrors.Contains(oracleException.Number),
            DataSourceKind.MariaDb when exception is MySqlException mySqlException =>
                mySqlException.IsTransient
                || MariaDbTransientErrors.Contains(mySqlException.Number),
            _ when exception is DbException dbException => dbException.IsTransient,
            _ => false
        };
    }

    public bool IsTimeout(DataSourceKind source, Exception exception)
    {
        if (exception is TimeoutException)
        {
            return true;
        }

        if (source == DataSourceKind.Oracle
            && exception is OracleException oracleException
            && oracleException.Number == 1013)
        {
            return true;
        }

        if (source == DataSourceKind.MariaDb
            && exception is MySqlException mySqlException
            && mySqlException.ErrorCode == MySqlErrorCode.CommandTimeoutExpired)
        {
            return true;
        }

        for (var inner = exception.InnerException; inner is not null; inner = inner.InnerException)
        {
            if (inner is TimeoutException)
            {
                return true;
            }
        }

        return false;
    }

    public int? GetProviderErrorCode(DataSourceKind source, Exception exception) => source switch
    {
        DataSourceKind.Oracle when exception is OracleException oracleException => oracleException.Number,
        DataSourceKind.MariaDb when exception is MySqlException mySqlException => mySqlException.Number,
        _ when exception is DbException dbException => dbException.ErrorCode,
        _ => null
    };
}
