using System;
using System.Collections.Generic;
using System.Text;

using static Apewer.NumberUtility;

namespace Apewer.Source
{

    /// <summary>ORM 帮助程序。</summary>
    public static class OrmHelper
    {

        #region As

        /// <summary>转换模型类型。</summary>
        public static TDst[] As<TSrc, TDst>(this TSrc[] input) where TDst : class
        {
            if (input == null) return null;
            var count = input.Length;
            var output = new TDst[count];
            for (var i = 0; i < count; i++)
            {
                var item = input[i];
                if (item == null) continue;
                output[i] = item as TDst; // 此处可能抛出异常。
            }
            return output;
        }

        /// <summary>转换模型类型。</summary>
        public static Result<TDst> As<TSrc, TDst>(this Result<TSrc> input) where TDst : class
        {
            if (input == null) return null;
            if (!input.HasValue) return new Result<TDst>(input.Code, input.Message);
            var value = input.Value as TDst;
            if (value == null)
            {
                var src = input.Value.GetType().FullName;
                var dst = typeof(TDst).FullName;
                return new Result<TDst>($"无法将记录从转换 {src} 到 {dst}。");
            }
            return new Result<TDst>(value);
        }

        /// <summary>转换模型类型。</summary>
        public static Result<TDst[]> As<TSrc, TDst>(this Result<TSrc[]> input) where TDst : class
        {
            if (input == null) return null;
            if (!input.HasValue) return new Result<TDst[]>(input.Code, input.Message);
            var count = input.Value.Length;
            var output = new TDst[count];
            for (var i = 0; i < count; i++) output[i] = input.Value[i] as TDst;
            return new Result<TDst[]>(output);
        }

        #endregion

        #region IQuery -> IRecord

        /// <summary>读取所有行,生成列表。</summary>
        public static T[] Fill<T>(this IQuery query) where T : class, new() => As<object, T>(Fill(query, typeof(T)));

        /// <summary>读取所有行填充到 T,组成 T[]。</summary>
        public static object[] Fill(this IQuery query, Type model)
        {
            if (query == null) return new object[0];
            if (model == null) return new object[0];

            var ts = TableStructure.Parse(model);
            if (ts == null) return new object[0];

            var output = new object[query.Rows];
            for (int r = 0; r < query.Rows; r++) output[r] = Row(query, r, model, ts);
            return output;
        }

        /// <summary>获取指定列的所有值,无效值不加入结果。</summary>
        public static T[] Column<T>(this IQuery query, Func<int, T> filler)
        {
            if (query == null || filler == null) return new T[0];

            var rows = query.Rows;
            var output = new T[rows];
            var added = 0;
            for (int r = 0; r < rows; r++)
            {
                var value = filler(r);
                if (value == null) continue;
                if (value is string str)
                {
                    if (str == "") continue;
                }
                output[added] = value;
                added++;
            }

            if (added < 1) return new T[0];
            if (added == rows) return output;
            var output2 = new T[added];
            Array.Copy(output, output2, added);
            return output2;
        }

        /// <summary>将 Query 的行,填充到模型实体。</summary>
        /// <remarks>填充失败时返回 NULL 值。</remarks>
        /// <exception cref="Exception"></exception>
        public static object Row(IQuery query, int rowIndex, Type model, TableStructure structure)
        {
            // 检查参数。
            if (query == null || model == null || structure == null) return null;
            if (rowIndex < 0 || rowIndex >= query.Rows) return null;
            if (!RuntimeUtility.CanNew(model)) return null;

            // 变量别名。
            var ts = structure;
            var r = rowIndex;
            var columns = ts.Columns;

            // 检查模型的属性,按属性从表中取相应的列。
            var record = Activator.CreateInstance(model);
            var properties = model.GetProperties();
            foreach (var property in properties)
            {
                // 必须有 setter 访问器。
                var setter = property.GetSetMethod();
                if (setter == null) continue;

                // 在表结构中检查,是否包含此属性,并获取 ColumnAttribute。
                var attribute = null as ColumnAttribute;
                for (var j = 0; j < columns.Length; j++)
                {
                    if (columns[j].PropertyName == property.Name)
                    {
                        attribute = columns[j];
                        break;
                    }
                }
                if (attribute == null) continue;

                // 读取值。
                var value = query.Value(r, attribute.Field);
                if (value == null) continue;
                if (value.Equals(DBNull.Value)) continue;

                // 根据属性类型设置值。
                var pt = property.PropertyType;
                if (pt.Equals(typeof(object))) setter.Invoke(record, new object[] { value });
                else if (pt.Equals(typeof(byte[]))) setter.Invoke(record, new object[] { (byte[])value });
                else if (pt.Equals(typeof(string))) setter.Invoke(record, new object[] { value.ToString() });

                else if (pt.Equals(typeof(DateTime))) setter.Invoke(record, new object[] { value });
                else if (pt.Equals(typeof(byte))) setter.Invoke(record, new object[] { Byte(value) });
                else if (pt.Equals(typeof(sbyte))) setter.Invoke(record, new object[] { SByte(value) });
                else if (pt.Equals(typeof(short))) setter.Invoke(record, new object[] { Int16(value) });
                else if (pt.Equals(typeof(ushort))) setter.Invoke(record, new object[] { UInt16(value) });
                else if (pt.Equals(typeof(int))) setter.Invoke(record, new object[] { Int32(value) });
                else if (pt.Equals(typeof(uint))) setter.Invoke(record, new object[] { UInt32(value) });
                else if (pt.Equals(typeof(long))) setter.Invoke(record, new object[] { Int64(value) });
                else if (pt.Equals(typeof(ulong))) setter.Invoke(record, new object[] { UInt64(value) });
                else if (pt.Equals(typeof(float))) setter.Invoke(record, new object[] { Single(value) });
                else if (pt.Equals(typeof(double))) setter.Invoke(record, new object[] { Double(value) });
                else if (pt.Equals(typeof(decimal))) setter.Invoke(record, new object[] { Decimal(value) });

                else if (pt.Equals(typeof(Nullable<DateTime>))) setter.Invoke(record, new object[] { new Nullable<DateTime>((DateTime)value) });
                else if (pt.Equals(typeof(Nullable<byte>))) setter.Invoke(record, new object[] { new Nullable<byte>(Byte(value) ) });
                else if (pt.Equals(typeof(Nullable<sbyte>))) setter.Invoke(record, new object[] { new Nullable<sbyte>(SByte(value) ) });
                else if (pt.Equals(typeof(Nullable<short>))) setter.Invoke(record, new object[] { new Nullable<short>(Int16(value) ) });
                else if (pt.Equals(typeof(Nullable<ushort>))) setter.Invoke(record, new object[] { new Nullable<int>(UInt16(value) ) });
                else if (pt.Equals(typeof(Nullable<int>))) setter.Invoke(record, new object[] { new Nullable<int>(Int32(value) ) });
                else if (pt.Equals(typeof(Nullable<uint>))) setter.Invoke(record, new object[] { new Nullable<uint>(UInt32(value) ) });
                else if (pt.Equals(typeof(Nullable<long>))) setter.Invoke(record, new object[] { new Nullable<long>(Int64(value) ) });
                else if (pt.Equals(typeof(Nullable<ulong>))) setter.Invoke(record, new object[] { new Nullable<ulong>(UInt64(value) ) });
                else if (pt.Equals(typeof(Nullable<float>))) setter.Invoke(record, new object[] { new Nullable<float>(Single(value) ) });
                else if (pt.Equals(typeof(Nullable<double>))) setter.Invoke(record, new object[] { new Nullable<double>(Double(value) ) });
                else if (pt.Equals(typeof(Nullable<decimal>))) setter.Invoke(record, new object[] { new Nullable<decimal>(Decimal(value) ) });

                else
                {
                    try
                    {
                        setter.Invoke(record, new object[] { query.Value(r, attribute.Field) });
                    }
                    catch { }
                }
            }
            return record;
        }

        #endregion

        #region IOrm

        /// <summary>查询记录。</summary>
        /// <param name="database">数据库对象。</param>
        /// <param name="model">记录模型。</param>
        /// <param name="sql">SQL 语句。</param>
        public static Result<object[]> Query(IDbClientAdo database, Type model, string sql)
        {
            if (database == null) return new Result<object[]>("数据库无效。");
            if (model == null) return new Result<object[]>("模型类型无效。");
            if (string.IsNullOrEmpty(sql)) return new Result<object[]>("SQL 语句无效。");
            using (var query = database.Query(sql))
            {
                if (query == null) return new Result<object[]>("查询实例无效。");
                if (query.Table == null)
                {
                    if (!string.IsNullOrEmpty(query.Message)) return new Result<object[]>(query.Message);
                    return new Result<object[]>("查询实例不包含数据表。");
                }
                try
                {
                    var array = Fill(query, model);
                    return new Result<object[]>(array);
                }
                catch (Exception ex)
                {
                    return new Result<object[]>(ex);
                }
            }
        }

        // /// <summary>查询记录。</summary>
        // /// <typeparam name="T">记录模型。</typeparam>
        // /// <param name="database">数据库对象。</param>
        // /// <param name="sql">SQL 语句。</param>
        // public static Result<T[]> Query<T>(IDbClientAdo database, string sql) where T : class, new() => As<object, T>(Query(database, typeof(T), sql));

        /// <summary>查询记录。</summary>
        /// <param name="database">数据库对象。</param>
        /// <param name="model">记录模型。</param>
        /// <param name="sqlGetter">生成 SQL 语句的函数,传入参数为表名。</param>
        public static Result<object[]> Query(IDbClientAdo database, Type model, Func<string, string> sqlGetter)
        {
            if (sqlGetter == null) return new Result<object[]>("SQL 语句获取函数无效。");
            try
            {
                var tableName = TableStructure.Parse(model).Name;
                if (string.IsNullOrEmpty(tableName)) return new Result<object[]>("表名无效。");
                return Query(database, model, sqlGetter(tableName));
            }
            catch (Exception ex)
            {
                return new Result<object[]>(ex);
            }
        }

        /// <summary>查询记录。</summary>
        /// <typeparam name="T">记录模型。</typeparam>
        /// <param name="database">数据库对象。</param>
        /// <param name="sqlGetter">生成 SQL 语句的函数,传入参数为表名。</param>
        public static Result<T[]> Query<T>(IDbClientAdo database, Func<string, string> sqlGetter) where T : class, new() => As<object, T>(Query(database, typeof(T), sqlGetter));

        /// <summary>获取具有指定主键的记录。</summary>
        /// <param name="database">数据库对象。</param>
        /// <param name="model">记录模型。</param>
        /// <param name="key">主键。</param>
        /// <param name="sqlGetter">生成 SQL 语句的函数,传入参数为表名和主键值。</param>
        public static Result<object> Get(IDbClientAdo database, Type model, string key, Func<string, string, string> sqlGetter)
        {
            if (sqlGetter == null) return new Result<object>("SQL 语句获取函数无效。");

            var safetyKey = TextUtility.SafeKey(key);
            if (string.IsNullOrEmpty(safetyKey)) return new Result<object>("主键无效。");

            var query = null as IQuery;
            var record = null as object;
            try
            {
                record = Activator.CreateInstance(model);
                var ts = TableStructure.Parse(model);
                var tableName = ts.Name;
                if (string.IsNullOrEmpty(tableName)) return new Result<object>("表名无效。");
                var sql = sqlGetter(tableName, safetyKey);

                query = database.Query(sql);
                if (query.Table == null) return new Result<object>("没有获取到记录。");
                record = Row(query, 0, model, ts);
            }
            catch (Exception ex)
            {
                RuntimeUtility.Dispose(query);
                return new Result<object>(ex);
            }
            RuntimeUtility.Dispose(query);
            if (record == null) return new Result<object>("没有获取到记录。");
            return new Result<object>(record);
        }

        /// <summary>获取具有指定主键的记录。</summary>
        /// <typeparam name="T">记录模型。</typeparam>
        /// <param name="database">数据库对象。</param>
        /// <param name="key">主键。</param>
        /// <param name="sqlGetter">生成 SQL 语句的函数,传入参数为表名和主键值。</param>
        public static Result<T> Get<T>(IDbClientAdo database, string key, Func<string, string, string> sqlGetter) where T : class, IRecord, new() => As<object, T>(Get(database, typeof(T), key, sqlGetter));

        /// <summary>获取主键。</summary>
        /// <param name="database">数据库对象。</param>
        /// <param name="model">记录模型。</param>
        /// <param name="sqlGetter">生成 SQL 语句的函数,传入参数为表名。</param>
        public static Result<string[]> Keys(IDbClientAdo database, Type model, Func<string, string> sqlGetter)
        {
            if (database == null) return new Result<string[]>("数据库无效。");
            if (model == null) return new Result<string[]>("模型类型无效。");
            if (sqlGetter == null) return new Result<string[]>("SQL 语句获取函数无效。");

            var tableStructure = null as TableStructure;
            try
            {
                tableStructure = TableStructure.Parse(model);
            }
            catch (Exception ex)
            {
                return new Result<string[]>(ex);
            }

            var tableName = tableStructure.Name;
            if (string.IsNullOrEmpty(tableName)) return new Result<string[]>("表名无效。");

            // var keyName = null as string;
            // foreach (var column in tableStructure.Columns)
            // {
            //     if (column.Key == "Key")
            //     {
            //         keyName = column.Value.Field;
            //         break;
            //     }
            // }
            // if (string.IsNullOrEmpty(keyName)) return Result<List<string>>.Error("主键字段无效。");

            // var sql = sqlGetter(tableName, keyName);
            var sql = sqlGetter(tableName);
            var query = null as IQuery;
            try
            {
                query = database.Query(sql);
                if (query == null) return new Result<string[]>("查询实例无效。");

                var list = new List<string>(query.Rows);
                for (var r = 0; r < query.Rows; r++)
                {
                    var key = TextUtility.SafeKey(query.Text(r));
                    if (string.IsNullOrEmpty(key)) continue;
                    list.Add(key);
                }
                query.Dispose();
                list.Capacity = list.Count;
                var array = list.ToArray();
                return new Result<string[]>(array);
            }
            catch (Exception ex)
            {
                RuntimeUtility.Dispose(query);
                return new Result<string[]>(ex);
            }
        }

        /// <summary>获取主键。</summary>
        /// <typeparam name="T">记录模型。</typeparam>
        /// <param name="database">数据库对象。</param>
        /// <param name="sqlGetter">生成 SQL 语句的函数,传入参数为表名。</param>
        public static Result<string[]> Keys<T>(IDbClientAdo database, Func<string, string> sqlGetter) where T : IRecord
        {
            return Keys(database, typeof(T), sqlGetter);
        }

        #endregion

        #region Record

        /// <summary>修复记录属性。</summary>
        public static void FixProperties(object record)
        {
            if (record == null) return;

            if (record is IRecord key)
            {
                if (string.IsNullOrEmpty(key.Key)) key.ResetKey();
            }

            var now = DateTime.Now;
            if (record is IRecordMoment moment)
            {
                if (string.IsNullOrEmpty(moment.Created)) moment.Created = now.Lucid();
                if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now.Lucid();
            }
            if (record is IRecordStamp stamp)
            {
                var utc = ClockUtility.UtcStamp;
                if (stamp.Created == 0L) stamp.Created = now.Stamp();
                if (stamp.Updated == 0L) stamp.Updated = now.Stamp();
            }
        }

        /// <summary>设置 Updated 属性。</summary>
        /// <returns>TRUE:设置成功;FALSE:设置失败。</returns>
        public static bool SetUpdated(object record)
        {
            if (record == null) return false;
            var now = DateTime.Now;
            var setted = false;
            if (record is IRecordMoment moment)
            {
                moment.Updated = now.Lucid();
                setted = true;
            }
            if (record is IRecordStamp stamp)
            {
                stamp.Updated = now.Stamp();
                setted = true;
            }
            return setted;
        }

        #endregion

    }

}