using System.Data;
using PowerEasy.WebFuture.DataMigrations.Foundation.Enum;
using PowerEasy.WebFuture.DataMigrations.Foundation.Log;
using PowerEasy.WebFuture.DataMigrations.Foundation.MigrationProgramme;
using PowerEasy.WebFuture.DataMigrations.Foundation.Model;

namespace PowerEasy.WebFuture.DataMigrations.Foundation.Service
{
    /// <summary>
    /// 节点迁移基础类。
    /// </summary>
    public abstract class BaseNodeMigration : IMigrationItem
    {
        /// <summary>
        /// 构造函数。
        /// </summary>
        public BaseNodeMigration()
        {
            this.MigrationProgrammeIdentifier = this.GetType().Namespace;
        }

        /// <summary>
        /// 迁移方案标识。
        /// </summary>
        /// <remarks>
        /// 迁移项目的标识需要与迁移方案标识相同。
        /// 如果迁移项目跟迁移方案命名空间相同时，可不指定标识，系统会将命名空间作为标识。
        /// </remarks>
        public string MigrationProgrammeIdentifier { get; set; }

        /// <summary>
        /// 获取目标源数据库节点标识符集合。
        /// </summary>
        public List<string> ExistNodeIdentifiers { get; set; } = new List<string>();

        /// <summary>
        /// 指定存放目标节点。
        /// </summary>
        public NodeDataRow TargetNode { get; set; }

        /// <summary>
        /// 迁移项目是否勾选。
        /// </summary>
        public bool Enable { get; set; }

        /// <summary>
        /// 来源数据库表数据。
        /// </summary>
        public DataTable SourceDataTable { get; set; }

        /// <summary>
        /// 可以指定存放目标数据库某个节点id下面。
        /// </summary>
        public abstract int TargetNodeId { get; set; }

        /// <summary>
        /// 来源数据库中节点表的父节点字段的名称。
        /// </summary>
        public abstract string ParentId { get; set; }

        /// <summary>
        /// 迁移项目名称。
        /// </summary>
        public abstract string MigrationItemName { get; set; }

        /// <summary>
        /// 迁移项目的排序，用于执行和工具显示的顺序，从小到大排序。
        /// </summary>
        public abstract int Order { get; set; }

        /// <summary>
        /// 模型名字。
        /// </summary>
        public abstract string ModelName { get; set; }

        /// <summary>
        /// 目标数据库表名。
        /// </summary>
        public abstract string TargetDatabaseTableName { get; set; }

        /// <summary>
        /// 目标站点id。
        /// </summary>
        public abstract int SiteId { get; set; }

        /// <summary>
        /// 来源数据库表的主键名称。
        /// </summary>
        public abstract string SourcePrimaryKeyName { get; set; }

        /// <summary>
        /// 上传目录。
        /// </summary>
        public abstract string UploadPath { get; set; }

        /// <summary>
        /// 指定节点树从哪个数字开始。
        /// </summary>
        public string AppointTreeStart { get; set; } = "0";

        /// <summary>
        /// 获取来源要迁移的数据集合。
        /// </summary>
        /// <param name="newNodeId">最新迁移的数据主键。</param>
        public abstract void LoadSourceDataTable(int newNodeId);

        /// <summary>
        /// 字段映射。（没有赋值的属性都使用了model中的默认值，如果需要，请在本方法重新赋值。）
        /// </summary>
        /// <param name="sourceDataRow">源数据表行数据。</param>
        /// <param name="targetDataRow">目标数据表行数据。</param>
        /// <returns>返回映射后的行数据。</returns>
        public abstract DataRow MapFields(DataRow sourceDataRow, DataRow targetDataRow);

        /// <summary>
        /// 开始迁移方法。
        /// </summary>
        /// <param name="isCheckedIncremental">是否勾选增量迁移。</param>
        /// <returns>返回迁移结果。</returns>
        public bool StartMigration(bool isCheckedIncremental)
        {
            LogHelper.BackgroundWorker.ReportProgress(0, $"{this.MigrationItemName}开始。");
            LogHelper.StartWriteLogAndFormLogByAsyn(this.ModelName);
            var newNodeId = 0;
            var targetDatabaseProvider = DatabaseService.GetTargetDatabaseProvider(this.MigrationProgrammeIdentifier);
            if (isCheckedIncremental)
            {
                // 勾选增量迁移，需要获取最新的SourceNodeId。
                string historyIdSql = targetDatabaseProvider.GetOneDataByTableNameAndOrder("PE_ContentManage_Node", "CAST(SourceNodeId AS INT)");
                var historyIdArticle = targetDatabaseProvider.ExecuteDataTable(historyIdSql);
                if (historyIdArticle.Rows.Count > 0)
                {
                    newNodeId = Convert.ToInt32(historyIdArticle.Rows[0]["SourceNodeId"]);
                }
            }

            var status = true;

            // 获取当前旧站数据。
            this.LoadSourceDataTable(newNodeId);

            this.InitTargetNodeIdData();

            var dataTable = this.GetInsertDataTable();
            this.UpdateLeftAndRight(dataTable);
            var count = dataTable.Rows.Count;

            // 批量插入。
            status = targetDatabaseProvider.BatchInsert(dataTable);
            LogHelper.IngWriteLogAndFormLogByAsyn(this.ModelName, status, count);

            this.SourceDataTable.Clear();
            dataTable.Clear();

            LogHelper.FinishWriteLogAndFormLogByAsyn(this.ModelName, status);
            LogHelper.BackgroundWorker.ReportProgress(100, $"{this.MigrationItemName}结束。");
            return status;
        }

        /// <summary>
        /// 获取批量插入的数据表。
        /// </summary>
        /// <returns>返回数据表。</returns>
        public DataTable GetInsertDataTable()
        {
            // 从源数据库中获取节点数据，并转换为左右节点树的形式。
            var orderDataRows = this.ToNodeTree(this.SourceDataTable, this.AppointTreeStart);
            var targetDatabaseProvider = DatabaseService.GetTargetDatabaseProvider(this.MigrationProgrammeIdentifier);

            // 获取目标表数据。
            var sql = targetDatabaseProvider.GetOneDataByTableName("PE_ContentManage_Node");
            var targetDataTable = targetDatabaseProvider.ExecuteDataTable(sql);

            // 构建新加的表DataTable。
            var addDataTable = new DataTable();
            addDataTable.TableName = "PE_ContentManage_Node";
            for (int i = 0; i < targetDataTable.Columns.Count; i++)
            {
                addDataTable.Columns.Add(targetDataTable.Columns[i].ColumnName, targetDataTable.Columns[i].DataType);
            }

            // 获取本站点节点的标识符集合。
            var newExistNodesSql = targetDatabaseProvider.GetAllDataByTableNameAndSiteId("PE_ContentManage_Node", this.SiteId, "NodeId");
            var newExistNodes = targetDatabaseProvider.ExecuteDataTable(newExistNodesSql);
            foreach (DataRow newnode in newExistNodes.Rows)
            {
                this.ExistNodeIdentifiers.Add(newnode["Identifier"].ToString());
            }

            // 判断当前站点是否存在节点。
            string typeSql = targetDatabaseProvider.GetOneDataByTableNameAndSiteId("PE_ContentManage_Node", this.SiteId, "NodeId");
            var thisNodeList = targetDatabaseProvider.ExecuteDataTable(typeSql);
            int nodeId = 0;
            if (this.SiteId >= 1 && thisNodeList.Rows.Count > 0)
            {
                nodeId = Convert.ToInt32(thisNodeList.Rows[0]["NodeId"]);
            }
            else
            {
                nodeId = (this.SiteId - 1) * 100000;
            }

            foreach (var nodeRow in orderDataRows)
            {
                DataRow newRow = addDataTable.NewRow();

                var node = this.MapFields(nodeRow.DataRow, newRow);

                // 节点的主键Id需要手动控制自增，数据库中没有设置组件自增。
                nodeId = nodeId + 1;
                node["NodeId"] = nodeId;
                node["SiteId"] = this.SiteId;
                node["Left"] = nodeRow.Left;
                node["Right"] = nodeRow.Right;
                node["Depth"] = nodeRow.Depth;
                addDataTable.Rows.Add(node);
            }

            return addDataTable;
        }

        /// <summary>
        /// 初始化指定存放目标节点。
        /// </summary>
        public void InitTargetNodeIdData()
        {
            // 初始化目标节点。
            if (this.TargetNodeId > 0)
            {
                var targetDatabaseProvider = DatabaseService.GetTargetDatabaseProvider(this.MigrationProgrammeIdentifier);
                var targetNodeSql = targetDatabaseProvider.GetAllDataByTableNameAndNodeId("PE_ContentManage_Node", this.TargetNodeId);
                var targetNodeDataTable = targetDatabaseProvider.ExecuteDataTable(targetNodeSql);
                if (targetNodeDataTable.Rows.Count > 0)
                {
                    this.TargetNode = this.MapDataRowToNode(targetNodeDataTable.Rows[0]);
                }
            }
        }

        /// <summary>
        /// 将数据行映射为节点实体。
        /// </summary>
        /// <param name="dataRow">数据行（仅用于WebFuture结构数据行）。</param>
        /// <returns>返回节点实体。</returns>
        public NodeDataRow MapDataRowToNode(DataRow dataRow)
        {
            var node = new NodeDataRow();
            node.Id = Convert.ToInt32(dataRow["NodeId"]);
            node.NodeName = dataRow["NodeName"].ToString();
            node.Left = Convert.ToInt32(dataRow["Left"]);
            node.Right = Convert.ToInt32(dataRow["Right"]);
            node.Depth = Convert.ToInt32(dataRow["Depth"]);
            return node;
        }

        /// <summary>
        /// 构建节点树。
        /// </summary>
        /// <param name="nodeDataTable">节点数据。</param>
        /// <param name="parentId">父节点。</param>
        /// <returns>返回构建好的树结构。</returns>
        public List<NodeDataRow> ToNodeTree(DataTable nodeDataTable, string parentId)
        {
            var orderNodes = this.ToDepthNode(nodeDataTable, parentId);
            LogHelper.WriteLog("ToDepthNode方法：" + orderNodes.Count + "/NodeDataTable:" + nodeDataTable.Rows.Count + "/parentId:" + parentId);
            int id = 1;
            try
            {
                var targetDatabaseType = DatabaseService.GetTargetDatabaseType(this.MigrationProgrammeIdentifier);
                var targetDatabaseProvider = DatabaseService.GetTargetDatabaseProvider(this.MigrationProgrammeIdentifier);
                string sql = targetDatabaseProvider.GetRightBySiteId(this.SiteId);

                var lastNode = targetDatabaseProvider.ExecuteDataTable(sql);

                LogHelper.WriteLog("构建树lastNode：" + lastNode.Rows.Count + "/SQL:" + sql);
                int right = 0;
                if (lastNode.Rows.Count > 0)
                {
                    right = Convert.ToInt32(lastNode.Rows[0]["Right"]);
                }

                int initDepth = 0;
                if (this.TargetNode != null)
                {
                    right = this.TargetNode.Left + 1;
                    initDepth = this.TargetNode.Depth + 1;
                }

                foreach (var node in orderNodes)
                {
                    node.Id = id;
                    node.Depth = node.Depth + initDepth;
                    node.Left = (id * 2) - 1 - node.Depth + right;
                    node.Right = (id * 2) - node.Depth + right;
                    id++;
                }

                foreach (var node in orderNodes)
                {
                    var parentNodes = orderNodes.FindAll(n => n.Id < node.Id && n.Right >= node.Left);
                    foreach (var parentNode in parentNodes)
                    {
                        parentNode.Right = parentNode.Right + 2;
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteExceptionLog(ex, nodeDataTable);
            }

            return orderNodes;
        }

        /// <summary>
        /// 排序带深度节点（父节点）。
        /// </summary>
        /// <param name="nodeDataTable">节点数据。</param>
        /// <param name="parentId">父节点。</param>
        /// <returns>返回排序带深度节点（父节点）。</returns>
        public List<NodeDataRow> ToDepthNode(DataTable nodeDataTable, string parentId)
        {
            var orderDataRowList = new List<NodeDataRow>();
            var testparentId = this.ParentId;
            LogHelper.WriteLog("ToDepthNode方法：testparentId：" + testparentId);
            try
            {
                if (testparentId == null || testparentId == string.Empty)
                {
                    var rootnodesNull = nodeDataTable.Select(this.ParentId = null);
                    LogHelper.WriteLog("ToDepthNode方法：rootnodesNull：" + rootnodesNull);
                    foreach (var rootnode in rootnodesNull)
                    {
                        orderDataRowList.Add(new NodeDataRow { Depth = 0, DataRow = rootnode });
                        orderDataRowList = this.AppendNode(orderDataRowList, nodeDataTable, rootnode, 1);
                    }
                }
                else
                {
                    if (parentId == "NULL")
                    {
                        var rootnodes1 = nodeDataTable.Select(this.ParentId + $" IS NULL ");
                        LogHelper.WriteLog("ToDepthNode方法：rootnodes1：" + rootnodes1);
                        foreach (var rootnode in rootnodes1)
                        {
                            orderDataRowList.Add(new NodeDataRow { Depth = 0, DataRow = rootnode });
                            orderDataRowList = this.AppendNode(orderDataRowList, nodeDataTable, rootnode, 1);
                        }
                    }
                    else
                    {
                        LogHelper.WriteLog("ToDepthNode方法：[" + this.ParentId + "][" + parentId + "]");
                        LogHelper.WriteLog("[" + this.ParentId + "]" + $"='{parentId}'");
                        var rootnodes = nodeDataTable.Select("[" + this.ParentId + "]" + $"='{parentId}'");
                        LogHelper.WriteLog("ToDepthNode方法：rootnodes：" + rootnodes);
                        foreach (var rootnode in rootnodes)
                        {
                            orderDataRowList.Add(new NodeDataRow { Depth = 0, DataRow = rootnode });
                            orderDataRowList = this.AppendNode(orderDataRowList, nodeDataTable, rootnode, 1);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteExceptionLog(ex, nodeDataTable);
            }

            return orderDataRowList;
        }

        /// <summary>
        /// 获取组建节点集合。
        /// </summary>
        /// <param name="orderNodes">要排序的节点。</param>
        /// <param name="nodeDataTable">节点表数据。</param>
        /// <param name="parent">父节点数据。</param>
        /// <param name="depth">深度。</param>
        /// <param name="func">方法。</param>
        /// <returns>返回组建好的节点集合。</returns>
        public List<NodeDataRow> AppendNode(List<NodeDataRow> orderNodes, DataTable nodeDataTable, DataRow parent, int depth, Func<int, DataRow, DataRow[]> func = null)
        {
            DataRow[] childNodes = null;

            // 判断父id是否为空。
            if (!string.IsNullOrEmpty(this.ParentId))
            {
                // 取[ParentId]的数据类型。
                Type type = nodeDataTable.Columns[this.ParentId].DataType;

                if (func != null)
                {
                    childNodes = func(depth, parent);
                }
                else
                {
                    if (type == typeof(string))
                    {
                        childNodes = nodeDataTable.Select($"{this.ParentId}='{parent[this.SourcePrimaryKeyName]}'");
                    }
                    else
                    {
                        childNodes = nodeDataTable.Select($"[{this.ParentId}]={Convert.ToInt32(parent[this.SourcePrimaryKeyName])}");
                    }
                }

                foreach (var childNode in childNodes)
                {
                    orderNodes.Add(new NodeDataRow { Depth = depth, DataRow = childNode });
                    orderNodes = this.AppendNode(orderNodes, nodeDataTable, childNode, depth + 1, func);
                }

                return orderNodes;
            }
            else
            {
                return orderNodes;
            }
        }

        /// <summary>
        /// 更新原节点的Left跟Right值。
        /// </summary>
        /// <param name="dataTable">数据库表。</param>
        public void UpdateLeftAndRight(DataTable dataTable)
        {
            // 当指定添加到目标节点时，需要更新原节点的Left跟Right值，让树的结构保持正确。
            if (this.TargetNodeId > 0)
            {
                var targetDatabaseType = DatabaseService.GetTargetDatabaseType(this.MigrationProgrammeIdentifier);
                var targetDatabaseProvider = DatabaseService.GetTargetDatabaseProvider(this.MigrationProgrammeIdentifier);

                if (this.TargetNode != null)
                {
                    int sourceNodeCount = dataTable.Rows.Count;
                    int right = this.TargetNode.Right;
                    int left = this.TargetNode.Left;
                    var sql = targetDatabaseProvider.UpdateLeftAndRight(this.SiteId, sourceNodeCount, right, left);
                    if (targetDatabaseType == DatabaseType.Oracle)
                    {
                        sql = targetDatabaseProvider.UpdateLeftAndRight(this.SiteId, sourceNodeCount, right, left, 1);
                        targetDatabaseProvider.ExecuteNonQuery(sql);
                        sql = targetDatabaseProvider.UpdateLeftAndRight(this.SiteId, sourceNodeCount, right, left, 2);
                        targetDatabaseProvider.ExecuteNonQuery(sql);
                        sql = targetDatabaseProvider.UpdateLeftAndRight(this.SiteId, sourceNodeCount, right, left, 3);
                        targetDatabaseProvider.ExecuteNonQuery(sql);
                    }

                    if (targetDatabaseType != DatabaseType.Oracle)
                    {
                        targetDatabaseProvider.ExecuteNonQuery(sql);
                    }
                }
            }
        }
    }
}
