using ORM;
using SqlSugar;
using System.Linq.Expressions;
using Universal;

namespace SqlSugarORM;
public class SqlSugarCustom : IORM
{
    public SqlSugarCustom()
    {
        this._logQueue = new(LogQueueHandler);
    }

    #region Internal
    private IOrmProvider? _provider;
    private SqlSugarClient? _Client;
    private bool disposedValue;
    private readonly EventQueue<(string, DateTime, LogLevel)> _logQueue;
    private void LogQueueHandler((string log, DateTime time, LogLevel level) logItem)
    {
        _provider?.Log(logItem.log, logItem.time, logItem.level);
    }
    private void Config(SqlSugarClient client)
    {
        client.Aop.OnLogExecuting = SqlLog;
    }
    private void SqlLog(string sql, SugarParameter[] paras)
    {
        string log = UtilMethods.GetNativeSql(sql, paras);
        _logQueue?.Enqueue(new(log, DateTime.Now, LogLevel.Original));
    }
    #endregion

    bool IORM.Initialize(IOrmProvider? notify)
    {
        if (_provider is not null)
            return false;

        _provider = notify;

        return true;
    }

    bool IORM.Open(string connectionString, ORM.DbType dbType, bool isAutoConnection)
    {
        if (this._Client is not null)
            return false;

        ConnectionConfig config = new()
        {
            ConnectionString = connectionString,
            DbType = (SqlSugar.DbType)dbType,
            IsAutoCloseConnection = isAutoConnection
        };
        try
        {
            SqlSugarClient Db = new(config, Config);
            Db.DbMaintenance.CreateDatabase();
            this._Client = Db;
        }
        catch
        {
            return false;
        }
        return true;
    }

    bool IORM.CreateDataBase(string dbName)
    {
        if (this._Client is null)
            return false;

        if (string.IsNullOrEmpty(dbName))
            return false;

        try
        {
            this._Client.DbMaintenance.CreateDatabase(dbName);
        }
        catch
        {
            _logQueue?.Enqueue(new($"Create DB {dbName} Failed", DateTime.Now, LogLevel.Error));
            return false;
        }

        return true;
    }

    bool IORM.CreateTable<T>(string? tableName)
    {
        if (this._Client is null)
            return false;

        try
        {
            if (string.IsNullOrEmpty(tableName))
                this._Client.CodeFirst.InitTables<T>();
            else
                this._Client.CodeFirst.As(typeof(T), tableName).InitTables<T>();
        }
        catch
        {
            _logQueue?.Enqueue(new($"Create Table {tableName} Failed", DateTime.Now, LogLevel.Error));
            return false;
        }


        return true;
    }


    bool IORM.Insert<T>(T data)
    {
        if (this._Client is null)
            return false;
        try
        {
            this._Client.Insertable(data).ExecuteCommand();
        }
        catch
        {
            _logQueue?.Enqueue(new($"Insert {data.ToString} Failed", DateTime.Now, LogLevel.Error));
            return false;
        }


        return true;
    }

    bool IORM.Insert<T>(string tablename, T data)
    {
        if (this._Client is null)
            return false;
        try
        {
            if (string.IsNullOrEmpty(tablename))
                this._Client.Insertable(data).ExecuteCommand();
            else
                this._Client.Insertable(data).AS(tablename).ExecuteCommand();
        }
        catch
        {
            if (string.IsNullOrEmpty(tablename))
                _logQueue?.Enqueue(new($"Insert {data.ToString} Failed", DateTime.Now, LogLevel.Error));
            else
                _logQueue?.Enqueue(new($"Insert {data.ToString} to table {tablename} Failed", DateTime.Now, LogLevel.Error));
            return false;
        }

        return true;
    }


    async Task<bool> IORM.Query<T>(Action<List<T>> results)
    {
        if (this._Client is null)
            return false;

        return await Task<bool>.Factory.StartNew(() =>
        {
            try
            {
                results?.Invoke(this._Client.Queryable<T>().ToList());
            }
            catch
            {
                _logQueue?.Enqueue(new($"Query {typeof(T)} Failed", DateTime.Now, LogLevel.Error));
                return false;
            }
            return true;
        });
    }

    async Task<bool> IORM.Query<T>(string tableName, Action<List<T>> results)
    {
        if (this._Client is null)
            return false;

        return await Task<bool>.Factory.StartNew(() =>
        {
            try
            {
                if (string.IsNullOrEmpty(tableName))
                    results?.Invoke(this._Client.Queryable<T>().ToList());
                else
                    results?.Invoke(this._Client.Queryable<T>().AS(tableName).ToList());
            }
            catch
            {
                _logQueue?.Enqueue(new($"Query {typeof(T)} Failed", DateTime.Now, LogLevel.Error));
                return false;
            }
            return true;
        });
    }


    async Task<bool> IORM.Query<T>(Expression<Func<T, bool>> expression, Action<List<T>> results)
    {
        if (this._Client is null)
            return false;

        return await Task<bool>.Factory.StartNew(() =>
            {
                try
                {
                    results?.Invoke(this._Client.Queryable<T>().Where(expression).ToList());
                }
                catch
                {
                    _logQueue?.Enqueue(new($"Query {typeof(T)} Failed", DateTime.Now, LogLevel.Error));
                    return false;
                }
                return true;
            });
    }

    async Task<bool> IORM.Query<T>(string tableName, Expression<Func<T, bool>> expression, Action<List<T>> results)
    {
        if (this._Client is null)
            return false;

        return await Task<bool>.Factory.StartNew(() =>
          {
              try
              {
                  if (string.IsNullOrEmpty(tableName))
                      results?.Invoke(this._Client.Queryable<T>().Where(expression).ToList());
                  else
                      results?.Invoke(this._Client.Queryable<T>().AS(tableName).Where(expression).ToList());
              }
              catch
              {
                  if (string.IsNullOrEmpty(tableName))
                      _logQueue?.Enqueue(new($"Query {typeof(T)} Failed", DateTime.Now, LogLevel.Error));
                  else
                      _logQueue?.Enqueue(new($"Query {typeof(T)} from Table {tableName} Failed", DateTime.Now, LogLevel.Error));
                  return false;
              }

              return true;
          });
    }

    bool IORM.Delete<T>(string tableName, Expression<Func<T, bool>> expression)
    {
        if (this._Client is null)
            return false;
        try
        {
            this._Client.Deleteable<T>().AS(tableName).Where(expression).ExecuteCommand();
        }
        catch
        {
            return false;
        }
        return true;
    }

    bool IORM.AddOrUpdate<T>(string tableName, T data, Expression<Func<T, bool>> expression)
    {
        if (this._Client is null)
            return false;

        try
        {
            if (string.IsNullOrEmpty(tableName))
            {
                if (this._Client.Updateable(data).Where(expression).ExecuteCommand() == 0)
                    return (this as IORM).Insert(data);
            }
            else
            {
                if (this._Client.Updateable(data).Where(expression).AS(tableName).ExecuteCommand() == 0)
                    return (this as IORM).Insert(tableName,data);
            }
        }
        catch
        {
            if (string.IsNullOrEmpty(tableName))
                _logQueue?.Enqueue(new($"Update {data.ToString} Failed", DateTime.Now, LogLevel.Error));
            else
                _logQueue?.Enqueue(new($"Update {data.ToString} to table {tableName} Failed", DateTime.Now, LogLevel.Error));
            return false;
        }
        return true;
    }

    #region Disopse
    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                while (_logQueue is not null && _logQueue.Count != 0)
                    Thread.Sleep(200);

                this._Client?.Dispose();
                this._Client = null;
                this._logQueue?.Dispose();
                this._provider = null;
            }

            disposedValue = true;
        }
    }

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
    #endregion
}