using System.Data;
using System.Linq;
using System;
using System.Collections.Generic;
using System.Collections;
using System.Reflection;
using System.Collections.ObjectModel;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using MrWu.Debug;

/// <summary>
/// ADOExtensions
/// </summary>
public static partial class ADOExtensions {


    static MethodInfo GetColumeValueMethod { get; }


    #region DataTable Extensions

    /// <summary>
    /// TryGetValue from DataTable
    /// </summary>
    /// <typeparam name="TValue"></typeparam>
    /// <param name="table"></param>
    /// <param name="columnName"></param>
    /// <param name="value"></param>
    /// <param name="rowNumber"></param>
    /// <returns></returns>
    public static bool TryGetValue<TValue>(this DataTable table, string columnName, out TValue value, int rowNumber = 0) {
        if (table.Rows.Count > rowNumber) {
            value = table.Rows[rowNumber].GetValue<TValue>(columnName);
            return true;
        }
        value = default(TValue);
        return false;
    }

    /// <summary>
    /// GetValue from DataTable
    /// </summary>
    /// <typeparam name="TValue"></typeparam>
    /// <param name="table"></param>
    /// <param name="columnName"></param>
    /// <param name="rowNumber"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static TValue GetValue<TValue>(this DataTable table, string columnName, int rowNumber = 0, TValue defaultValue = default(TValue)) {
        return table.TryGetValue<TValue>(columnName, out var outValue, rowNumber) ? outValue : defaultValue;
    }

    /// <summary>
    /// Select
    /// </summary>
    /// <typeparam name="TObject"></typeparam>
    /// <param name="table"></param>
    /// <param name="selector"></param>
    /// <returns></returns>
    public static IEnumerable<TObject> Select<TObject>(this DataTable table, Func<DataRow, TObject> selector) {
        foreach (DataRow row in table.Rows) {
            yield return selector.Invoke(row);
        }
    }

    /// <summary>
    /// ToObjects
    /// </summary>
    /// <typeparam name="TObject"></typeparam>
    /// <param name="table"></param>
    /// <returns></returns>
    public static IEnumerable<TObject> ToObjects<TObject>(this DataTable table) where TObject : new() {
        foreach (DataRow row in table.Rows) {
            yield return row.ToObject<TObject>();
        }
    }

    #endregion

    #region DataRow Extensions

    #region 泛型方法

    #region 单列解析方法

    /// <summary>
    /// GetValue
    /// </summary>
    /// <typeparam name="TValue"></typeparam>
    /// <param name="row">DataRow</param>
    /// <param name="column">DataColumn</param>
    /// <returns></returns>
    public static TValue GetColumeValue<TValue>(this DataRow row, DataColumn column) {
        var columnValue = row[column];
        if (columnValue is TValue value) {
            return value;
        }
        if (columnValue is DBNull && column.AllowDBNull) {
            return default(TValue);
        }
        throw new ArgumentOutOfRangeException($"无效的泛型类型，列名：{column.ColumnName}，泛型类型为：{typeof(TValue)}，数据列类型为：{GetColumnDataType(column)}");
    }

    /// <summary>
    /// GetValue from DataRow
    /// </summary>
    /// <typeparam name="TValue"></typeparam>
    /// <param name="row"></param>
    /// <param name="columnName"></param>
    /// <returns></returns>
    public static TValue GetValue<TValue>(this DataRow row, string columnName) {
        if (row.TryGetColumn(columnName, out var column)) {
            return row.GetColumeValue<TValue>(column);
        }
        throw new ArgumentOutOfRangeException("未找到字段：" + columnName);
    }

    #endregion

    #region 整行解析方法

    static ConcurrentDictionary<Type, ICollection<MemberInfo>> memberInfoOfType = new ConcurrentDictionary<Type, ICollection<MemberInfo>>();

    /// <summary>
    /// ToObject from DataRow
    /// </summary>
    /// <typeparam name="TObject"></typeparam>
    /// <param name="row"></param>
    /// <returns></returns>
    public static TObject ToObject<TObject>(this DataRow row) where TObject : new() {
        var type = typeof(TObject);
        var objectMembers = memberInfoOfType.GetOrAdd(type, t => {
            var collection = new Collection<MemberInfo>();
            foreach (var item in type.GetProperties().Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0)) {
                collection.Add(item);
            }
            foreach (var item in type.GetFields().Where(p => !p.IsInitOnly && p.IsPublic)) {
                collection.Add(item);
            }
            return collection;
        });
        var obj = new TObject();
        foreach (var item in objectMembers) {
            if (item is FieldInfo fieldInfo) {
                row.TrySetMemberValue(obj, fieldInfo.Name, fieldInfo.FieldType, fieldInfo.SetValue);
            } else if (item is PropertyInfo propertyInfo) {
                row.TrySetMemberValue(obj, propertyInfo.Name, propertyInfo.PropertyType, propertyInfo.SetValue);
            }
        }
        return obj;
    }

    static void TrySetMemberValue(this DataRow row, object parent, string memberName, Type memberType, Action<object, object> valueSetMethod) {
        var column = row.Table.Columns[memberName];
        if (column != null) {
            var value = row[column];
            try {
                if (value != null && !(value is DBNull)) {
                    var sourceType = value.GetType();
                    if (memberType.IsEnum) {
                        value = Enum.ToObject(memberType, value);
                    } else if (memberType.IsGenericType && memberType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
                        //目标类型为 Nullable<>，无需转换，可直接赋值
                    } else if (!sourceType.IsAssignableFrom(memberType) && value is IConvertible convert) {
                        value = convert.ToType(memberType, null);
                    }
                    valueSetMethod.Invoke(parent, value);
                }
            } catch (Exception ex) {
                throw new ArgumentException($"TrySetMemberValue 数据类型定义错误, 定义类型与数据库类型不一致, 成员名：{memberName}, 当前泛型参数类型为: {memberType}, 实际数据类型：{ GetColumnDataType(row.Table.Columns[memberName])}, data: {value}", ex);
            }
        }
    }

    /// <summary>
    /// ToDictionary from DataRow
    /// </summary>
    /// <param name="row"></param>
    /// <returns></returns>
    public static IDictionary<string, object> ToDictionary(this DataRow row) {
        var columns = row.Table.Columns;
        var dict = new Dictionary<string, object>();
        foreach (DataColumn column in columns) {
            var value = row.GetValue(column);
            dict.Add(column.ColumnName, value);
        }
        return dict;
    }

    #endregion

    #endregion

    #region 非泛型方法

    /// <summary>
    /// TryGetValue
    /// </summary>
    /// <param name="row"></param>
    /// <param name="columnName"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public static bool TryGetValue(this DataRow row, string columnName, out object value) {
        if (row.TryGetColumn(columnName, out var column)) {
            value = row.GetValue(column);
            return true;
        }
        value = null;
        return false;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="row"></param>
    /// <param name="column"></param>
    /// <returns></returns>
    public static object GetValue(this DataRow row, DataColumn column) {
        var columnType = GetColumnDataType(column);
        var value = row[column];
        return value is DBNull ? null : value;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="column"></param>
    /// <returns></returns>
    public static Type GetColumnDataType(this DataColumn column) => column.AllowDBNull && column.DataType.IsValueType ? typeof(Nullable<>).MakeGenericType(column.DataType) : column.DataType;

    /// <summary>
    /// TryGetColumn from DataRow
    /// </summary>
    /// <param name="row"></param>
    /// <param name="columnName"></param>
    /// <param name="column"></param>
    /// <returns></returns>
    public static bool TryGetColumn(this DataRow row, string columnName, out DataColumn column) {
        column = row.Table.Columns[columnName];
        return column != null;
    }

    internal static int SQLValue(this bool value) => value ? 1 : 0;

    #endregion

    #endregion

    #region SQL命令扩展

    #region 构造与内部成员

    private static string connectionString { get; }


    #endregion

    #region 单结果集查询

    public static DataTable ExecuteTable(this dbbase db, string sqlText, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => cmd.FillTable(), parameters);
    }

    public static DataTable ExecuteTable(this dbbase db, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => cmd.FillTable(), type, parameters);
    }

    public static Task<DataTable> ExecuteTableAsync(this dbbase db, string sqlText, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, async cmd => await cmd.FillTableAsync(), parameters);
    }

    public static Task<DataTable> ExecuteTableAsync(this dbbase db, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, async cmd => await cmd.FillTableAsync(), type, parameters);
    }

    #endregion

    #region 多结果集查询

    public static DataTable[] ExecuteTables(this dbbase db, string sqlText, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => cmd.FillTables(), parameters);
    }

    public static DataTable[] ExecuteTables(this dbbase db, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => cmd.FillTables(), type, parameters);
    }

    public static Task<DataTable[]> ExecuteTablesAsync(this dbbase db, string sqlText, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, async cmd => await cmd.FillTablesAsync(), parameters);
    }

    public static Task<DataTable[]> ExecuteTablesAsync(this dbbase db, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, async cmd => await cmd.FillTablesAsync(), type, parameters);
    }

    #endregion


    #region 执行无结果集SQL

    public static int ExecuteNonQuery(this dbbase db, string sqlText, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => cmd.ExecuteNonQuery(), parameters);
    }

    public static int ExecuteNonQuery(this dbbase db, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => cmd.ExecuteNonQuery(), type, parameters);
    }

    public static Task<int> ExecuteNonQueryAsync(this dbbase db, string sqlText, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, cmd => cmd.ExecuteNonQueryAsync(), parameters);
    }

    public static Task<int> ExecuteNonQueryAsync(this dbbase db, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, cmd => cmd.ExecuteNonQueryAsync(), type, parameters);
    }

    #endregion

    #region 执行首行首列查询

    public static object ExecuteScalar(this dbbase db, string sqlText, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => cmd.ExecuteScalar(), parameters);
    }

    public static object ExecuteScalar(this dbbase db, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => cmd.ExecuteScalar(), type, parameters);
    }

    public static Task<object> ExecuteScalarAsync(this dbbase db, string sqlText, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, cmd => cmd.ExecuteScalarAsync(), parameters);
    }

    public static Task<object> ExecuteScalarAsync(this dbbase db, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, cmd => cmd.ExecuteScalarAsync(), type, parameters);
    }

    #endregion

    #region 泛型方法

    #region 单表实体集查询

    public static IEnumerable<TResult> ExecuteEntities<TResult>(this dbbase db, string sqlText, Func<DataRow, TResult> resultSelector, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => cmd.FillTable().Select(p => resultSelector.Invoke(p)), parameters);
    }

    public static IEnumerable<TResult> ExecuteEntities<TResult>(this dbbase db, string sqlText, Func<DataRow, TResult> resultSelector, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => cmd.FillTable().Select(p => resultSelector.Invoke(p)), type, parameters);
    }

    public static Task<IEnumerable<TResult>> ExecuteEntitiesAsync<TResult>(this dbbase db, string sqlText, Func<DataRow, TResult> resultSelector, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, async cmd => (await cmd.FillTableAsync()).Select(p => resultSelector.Invoke(p)), parameters);
    }

    public static Task<IEnumerable<TResult>> ExecuteEntitiesAsync<TResult>(this dbbase db, string sqlText, Func<DataRow, TResult> resultSelector, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, async cmd => (await cmd.FillTableAsync()).Select(p => resultSelector.Invoke(p)), type, parameters);
    }

    #endregion

    #region 多表实体集查询

    public static TResult ExecuteEntities2<TResult>(this dbbase db, string sqlText, Func<DataTable[], TResult> resultSelector, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => resultSelector.Invoke(cmd.FillTables()), parameters);
    }

    public static TResult ExecuteEntities2<TResult>(this dbbase db, string sqlText, Func<DataTable[], TResult> resultSelector, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, cmd => resultSelector.Invoke(cmd.FillTables()), type, parameters);
    }

    public static Task<TResult> ExecuteEntities2Async<TResult>(this dbbase db, string sqlText, Func<DataTable[], TResult> resultSelector, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, async cmd => resultSelector.Invoke(await cmd.FillTablesAsync()), parameters);
    }

    public static Task<TResult> ExecuteEntities2Async<TResult>(this dbbase db, string sqlText, Func<DataTable[], TResult> resultSelector, CommandType type, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, async cmd => resultSelector.Invoke(await cmd.FillTablesAsync()), type, parameters);
    }

    #endregion

    #region 

    public static TResult ExecuteCommand<TResult>(this dbbase db, string sqlText, Func<SqlCommand, TResult> resultSelector, params SqlParameter[] parameters) {
        return db.ExecuteCommand(sqlText, resultSelector, CommandType.Text, parameters);
    }

    public static TResult ExecuteCommand<TResult>(this dbbase db, string sqlText, Func<SqlCommand, TResult> resultSelector, CommandType type, params SqlParameter[] parameters) {
        using (SqlConnection conn = new SqlConnection(connectionString)) {
            conn.Open();
            conn.ChangeDatabase(GetDataBaseName(db));
            using (var cmd = conn.CreateCommand()) {
                cmd.CommandText = sqlText;
                cmd.CommandType = type;
                if (parameters != null && parameters.Length > 0) {
                    cmd.Parameters.AddRange(parameters);
                }
                return resultSelector.Invoke(cmd);
            }
        }
    }

    public static Task<TResult> ExecuteCommandAsync<TResult>(this dbbase db, string sqlText, Func<SqlCommand, Task<TResult>> resultSelector, params SqlParameter[] parameters) {
        return db.ExecuteCommandAsync(sqlText, resultSelector, CommandType.Text, parameters);
    }

    public static async Task<TResult> ExecuteCommandAsync<TResult>(this dbbase db, string sqlText, Func<SqlCommand, Task<TResult>> resultSelector, CommandType type, params SqlParameter[] parameters) {
        using (SqlConnection conn = new SqlConnection(connectionString)) {
            await conn.OpenAsync();
            conn.ChangeDatabase(GetDataBaseName(db));
            using (var cmd = conn.CreateCommand()) {
                cmd.CommandText = sqlText;
                cmd.CommandType = type;
                if (parameters != null && parameters.Length > 0) {
                    cmd.Parameters.AddRange(parameters);
                }
                return await resultSelector.Invoke(cmd);
            }
        }
    }

    #endregion

    #endregion

    #region 事务

    public static TResult ExecuteTransaction<TResult>(this dbbase db, Func<SqlTransaction, TResult> transactionAction) {
        return db.ExecuteTransaction(true, transactionAction);
    }

    public static TResult ExecuteTransaction<TResult>(this dbbase db, bool throwIfException, Func<SqlTransaction, TResult> transactionAction) {
        var result = default(TResult);
        using (var conn = new SqlConnection(connectionString)) {
            conn.Open();
            conn.ChangeDatabase(GetDataBaseName(db));
            using (var transaction = conn.BeginTransaction()) {
                try {
                    result = transactionAction.Invoke(transaction);
                    transaction.Commit();
                } catch (Exception ex) {
                    Debug.Error("ExecuteTransaction " + ex);
                    transaction.SafeRollback();
                    if (throwIfException) {
                        throw ex;
                    }
                }
            }
        }
        return result;
    }

    public static Task<TResult> ExecuteTransactionAsync<TResult>(this dbbase db, Func<SqlTransaction, Task<TResult>> transactionAction) {
        return db.ExecuteTransactionAsync(true, transactionAction);
    }

    public static async Task<TResult> ExecuteTransactionAsync<TResult>(this dbbase db, bool throwIfException, Func<SqlTransaction, Task<TResult>> transactionAction) {
        var result = default(TResult);
        using (var conn = new SqlConnection(connectionString)) {
            await conn.OpenAsync();
            conn.ChangeDatabase(GetDataBaseName(db));
            using (var transaction = conn.BeginTransaction()) {
                try {
                    result = await transactionAction.Invoke(transaction);
                    transaction.Commit();
                } catch (Exception ex) {
                    Debug.Error("ExecuteTransaction " + ex);
                    transaction.SafeRollback();
                    if (throwIfException) {
                        throw ex;
                    }
                }
            }
        }
        return result;
    }


    /// <summary>
    /// 
    /// </summary>
    /// <param name="transaction"></param>
    /// <param name="sqlText"></param>
    /// <param name="parameters"></param>
    /// <returns></returns>
    public static TResult ExecuteQuery<TResult>(this SqlTransaction transaction, string sqlText, Func<SqlCommand, TResult> resultSelector, params SqlParameter[] parameters) {
        return transaction.ExecuteQuery(sqlText, resultSelector, CommandType.Text, parameters);
    }

    public static TResult ExecuteQuery<TResult>(this SqlTransaction transaction, string sqlText, Func<SqlCommand, TResult> resultSelector, CommandType type, params SqlParameter[] parameters) {
        using (var cmd = new SqlCommand(sqlText, transaction.Connection, transaction) { CommandType = type }) {
            if (parameters != null && parameters.Length > 0) {
                cmd.Parameters.AddRange(parameters);
            }
            var result = resultSelector.Invoke(cmd);
            return result;
        }
    }

    public static int ExecuteNonQuery(this SqlTransaction transaction, string sqlText, params SqlParameter[] parameters) {
        return transaction.ExecuteNonQuery(sqlText, CommandType.Text, parameters);
    }

    public static int ExecuteNonQuery(this SqlTransaction transaction, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return transaction.ExecuteQuery(sqlText, cmd => cmd.ExecuteNonQuery(), type, parameters);
    }

    public static Task<int> ExecuteNonQueryAsync(this SqlTransaction transaction, string sqlText, params SqlParameter[] parameters) {
        return transaction.ExecuteNonQueryAsync(sqlText, CommandType.Text, parameters);
    }

    public static Task<int> ExecuteNonQueryAsync(this SqlTransaction transaction, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return transaction.ExecuteQuery(sqlText, cmd => cmd.ExecuteNonQueryAsync(), type, parameters);
    }

    public static object ExecuteScalar(this SqlTransaction transaction, string sqlText, params SqlParameter[] parameters) {
        return transaction.ExecuteScalar(sqlText, CommandType.Text, parameters);
    }

    public static object ExecuteScalar(this SqlTransaction transaction, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return transaction.ExecuteQuery(sqlText, cmd => cmd.ExecuteScalar(), type, parameters);
    }

    public static Task<object> ExecuteScalarAsync(this SqlTransaction transaction, string sqlText, params SqlParameter[] parameters) {
        return transaction.ExecuteScalarAsync(sqlText, CommandType.Text, parameters);
    }

    public static Task<object> ExecuteScalarAsync(this SqlTransaction transaction, string sqlText, CommandType type, params SqlParameter[] parameters) {
        return transaction.ExecuteQuery(sqlText, cmd => cmd.ExecuteScalarAsync(), type, parameters);
    }

    public static void SafeRollback(this SqlTransaction transaction) {
        try {
            transaction.Rollback();
        } catch (Exception ex) {
            Debug.Error("SafeRollback:" + ex);
        }
    }

    #endregion

    #region Common

    private static string GetDataBaseName(dbbase db) {
        switch (db) {
            case dbbase.gamedb:
                return "jnxdgame";
            case dbbase.game2018:
                return "game2018";
            default:
                throw new NotSupportedException(nameof(db));
        }
    }

    public static DataTable FillTable(this SqlCommand cmd) {
        return cmd.FillTables().FirstOrDefault() ?? new DataTable();
    }

    public static DataTable[] FillTables(this SqlCommand cmd) {
        using (var adapter = new SqlDataAdapter(cmd)) {
            using (var dataSet = new DataSet()) {
                adapter.Fill(dataSet);
                var tabs = new DataTable[dataSet.Tables.Count];
                for (int i = 0; i < dataSet.Tables.Count; i++) {
                    tabs[i] = dataSet.Tables[i];
                }
                return tabs;
            }
        }
    }

    public static async Task<DataTable> FillTableAsync(this SqlCommand cmd) {
        using (var reader = await cmd.ExecuteReaderAsync()) {
            return await reader.FillTableInternal();
        }
    }

    public static async Task<DataTable[]> FillTablesAsync(this SqlCommand cmd) {
        using (var reader = await cmd.ExecuteReaderAsync()) {
            var tables = new Collection<DataTable>();
            do {
                var datatable = await reader.FillTableInternal();
                if (datatable != null) {
                    tables.Add(datatable);
                }
            } while (await reader.NextResultAsync());
            return tables.ToArray();
        }
    }

    static async Task<DataTable> FillTableInternal(this SqlDataReader reader) {
        var datatable = new DataTable();
        if (reader.FieldCount > 0) {
            datatable.Columns.AddRange(Enumerable.Range(0, reader.FieldCount)
                .Select(i => new DataColumn(reader.GetName(i), reader.GetFieldType(i)))
                .ToArray());

            ///添加行数据  
            while (await reader.ReadAsync()) {
                var myDataRow = datatable.NewRow();
                for (int i = 0; i < reader.FieldCount; i++) {
                    myDataRow[i] = reader[i];
                }
                datatable.Rows.Add(myDataRow);
            }
        }
        return datatable;
    }

    #endregion 

    #endregion
}
