using Bp.Sync.BackEnd.Infrastructure.Repositories;
using Bp.Sync.Domain.Elastics.Inventories.Bills;
using Bp.Sync.Domain.Elastics.Inventories.Orders;
using Bp.Sync.Domain.Elastics.Inventories.Products;
using Bp.Sync.Domain.Entities.Inventories.Orders;
using Bp.Sync.Domain.Entities.Inventories.Reasons;
using Bp.Sync.Dto.Results;
using CommonLibrary;
using Dapper;
using EasyNetQ;
using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Vina.Search.Infrastructure.Mappings;

namespace Bp.Sync.BackEnd.Application.Commands.Inventories.ProductOrders
{
    public class UpdateProductOrderHandler : IRequestHandler<UpdateProductOrderCommand, BaseResult>
    {
        private readonly IDbConnection _dbConnection;
        private readonly IInboundBillRepository _inboundBillRepository;
        private readonly IProductOrderRepository _ProductOrderRepository;
        private readonly IProductBillRepository _productBillRepository;
        private readonly IProductUnitRepository _productUnitRepository;
        private readonly IProductRepository _productRepository;
        private readonly IOrderRepository _orderRepository;
        private readonly IBus _bus;
        private readonly IUnitRepository _unitRepository;
        private readonly ILogger<UpdateProductOrderHandler> _logger;

        public UpdateProductOrderHandler(IConfiguration configuration,
            IProductOrderRepository ProductOrderRepository,
            IOrderRepository orderRepository,
            IBus bus,
            IProductUnitRepository productUnitRepository,
            IProductRepository productRepository,
            ILogger<UpdateProductOrderHandler> logger,
            IInboundBillRepository inboundBillRepository,
            IUnitRepository unitRepository,
            IProductBillRepository productBillRepository)
        {
            var connectionString = configuration.GetConnectionString("InventoryConnection");
            _dbConnection = new System.Data.SqlClient.SqlConnection(connectionString);
            _ProductOrderRepository = ProductOrderRepository;
            _productBillRepository = productBillRepository;
            _productUnitRepository = productUnitRepository;
            _orderRepository = orderRepository;
            _productRepository = productRepository;
            _inboundBillRepository = inboundBillRepository;
            _bus = bus;
            _logger = logger;
            _unitRepository = unitRepository;
        }

        public async Task<BaseResult> Handle(UpdateProductOrderCommand request, CancellationToken cancellationToken)
        {
            _logger.LogInformation("Log_UpdateProductOrderHandler" + "Ids" + request.Ids.Count() + string.Join(",", request.Ids) + "orderids" + request.OrderIds.Count() + string.Join(",", request.Ids) + DateTimeOffset.Now);
            var rs = new BaseResult();

            if (request.Ids.Count() > 1000)
            {
                var length = request.Ids.Count();
                if (length > 0)
                {
                    for (int i = 0; i <= length / 1000; i++)
                    {
                        if (length > i * 1000)
                        {
                            var ids = request.Ids.Skip(i * 1000).Take(1000).ToList();
                            if (ids.Count() > 0)
                            {
                                await _bus.SendAsync($"UpdateToElasticProductOrder", ids.ConvertListLongToString());
                            }
                        }
                    }
                }
                return new BaseResult { Result = Result.Success, Message = $"sent {length} ProductOrders" };
            }

            if (request.OrderIds.Count() > 10)
            {
                var length = request.OrderIds.Count();
                if (length > 0)
                {
                    for (int i = 0; i <= length / 10; i++)
                    {
                        if (length > i * 10)
                        {
                            var ids = request.OrderIds.Skip(i * 10).Take(10).ToList();
                            if (ids.Count() > 0)
                            {
                                await _bus.SendAsync($"UpdateToElasticProductOrderByOrder", ids.ConvertListLongToString());
                            }
                        }
                    }
                }
                return new BaseResult { Result = Result.Success, Message = $"sent {length} ProductOrders" };
            }

            try
            {
                var ProductOrderRs = new List<ProductOrderElastic>();

                if (request.Ids.Any())
                {
                    var p = new Dto.DapperTypes.IdsParam(request.Ids);
                    var ProductOrders = await _dbConnection.QueryAsync<ProductOrder>("Sync_ProductOrder_Get_By_List_Id", p, commandType: CommandType.StoredProcedure);
                    if (ProductOrders.Any())
                    {
                        ProductOrderRs = ProductOrders.ToProductOrders().ToList();
                    }
                    else
                    {
                        rs.Message = $"no data to update";
                        rs.Result = Result.Success;
                        return rs;
                    }
                }

                if (request.OrderIds.Any())
                {
                    var p = new Dto.DapperTypes.IdsParam(request.OrderIds);
                    var ProductOrders = await _dbConnection.QueryAsync<ProductOrder>("Sync_ProductOrder_Get_By_List_OrderId", p, commandType: CommandType.StoredProcedure);
                    if (ProductOrders.Any())
                    {
                        ProductOrderRs = ProductOrders.ToProductOrders().ToList();
                    }
                    else
                    {
                        rs.Message = $"no data to update";
                        rs.Result = Result.Success;
                        return rs;
                    }
                }

                if (!request.Ids.Any() && !request.OrderIds.Any())
                {
                    //lay ra thieu gia von
                    var query = new Nest.BoolQueryDescriptor<ProductOrderElastic>();
                    var notDelete1Q = new Nest.QueryContainerDescriptor<ProductOrderElastic>();
                    notDelete1Q.Terms(t => t.Field(f => f.IsDeleted).Terms(false));
                    var notRevenueQ = new Nest.QueryContainerDescriptor<ProductOrderElastic>();
                    notRevenueQ.Bool(b => b.MustNot(m => m.Exists(t => t.Field(f => f.Revenue))));
                    var notProductNameQ = new Nest.QueryContainerDescriptor<ProductOrderElastic>();
                    notProductNameQ.Bool(b => b.MustNot(m => m.Exists(t => t.Field(f => f.ProductName))));
                    var notAvailableInItockQ = new Nest.QueryContainerDescriptor<ProductOrderElastic>();
                    notAvailableInItockQ.Bool(b => b.MustNot(m => m.Exists(t => t.Field(f => f.AvailableInItock))));
                    var notBaseUnitQ = new Nest.QueryContainerDescriptor<ProductOrderElastic>();
                    notBaseUnitQ.Bool(b => b.MustNot(m => m.Exists(t => t.Field(f => f.BaseUnitId))));

                    query.Filter(f => notDelete1Q && (notRevenueQ || notProductNameQ || notAvailableInItockQ || notBaseUnitQ));
                    var items = await _ProductOrderRepository.Search(query, 1000);

                    if (!items.Items.Any()) return new BaseResult { Result = Result.Failed, Message = "No data update" };
                    var pids = items.Items.Select(x => x.Id).Distinct().ToList();
                    var p = new Dto.DapperTypes.IdsParam(pids);
                    var ProductOrders = await _dbConnection.QueryAsync<ProductOrder>("Sync_ProductOrder_Get_By_List_Id", p, commandType: CommandType.StoredProcedure);
                    if (ProductOrders.Any())
                    {
                        ProductOrderRs = ProductOrders.ToProductOrders().ToList();
                    }
                    else
                    {
                        rs.Message = $"no data to update";
                        rs.Result = Result.Success;
                        return rs;
                    }
                }

                if (ProductOrderRs.Any())
                {
                    //lấy ra trạng thái của đưn hàng
                    var orderids = ProductOrderRs.Select(x => x.OrderId).Distinct().ToList();
                    var p2 = new Dto.DapperTypes.IdsParam(orderids);
                    var Orders = await _dbConnection.QueryAsync<Order>("Sync_Order_Get_By_List_Id", p2, commandType: CommandType.StoredProcedure);
                    var reasonids = ProductOrderRs.Select(x => (long)x.ReasonId).Distinct().ToList();
                    var pReason = new Dto.DapperTypes.IdsParam(reasonids);
                    var reasons = await _dbConnection.QueryAsync<Reason>("Sync_Reason_Get_By_List_Id", pReason, commandType: CommandType.StoredProcedure);

                    var orderelastics = await _orderRepository.GetByIds(orderids);

                    var listChangeUnits = new List<long>();
                    var productIds = ProductOrderRs.Select(x => x.ProductId).Distinct().ToList();
                    var products = await _productRepository.GetByIds(productIds);

                    var productUnits = await _productUnitRepository.GetMinUnitByProductIds(productIds);
                    var productUnitAlls = await _productUnitRepository.GetByProductIds(productIds);
                    var units = await _unitRepository.GetAll();

                    foreach (var item in ProductOrderRs)
                    {
                        if (Orders.Any())
                        {
                            var or = Orders.FirstOrDefault(x => x.Id == item.OrderId);
                            if (or != null)
                            {
                                item.OrderIsDeleted = or.IsDeleted;
                                item.OrderCode = or.Code;
                                item.OrderType = or.OrderType;
                                item.OrderStatus = or.OrderStatus;
                                item.OrderBranchId = or.BranchId;
                                item.OrderDestBranchId = or.DestBranchId;
                                item.OrderCustomerId = or.CustomerId;
                                item.OrderCreatedBy = or.CreatedBy;
                                item.OrderCreatedByUserName = or.CreatedByUserName;
                                item.OrderCreatedTime = or.CreatedTime;
                                item.OrderApprovedDate = or.ApprovedDate;
                                item.OrderDate = or.OrderDate;
                                item.OrderSlaTime = or.SlaTime;
                            }
                        }
                        var pro = products.Items.FirstOrDefault(x => x.Id == item.ProductId);
                        if (pro != null)
                        {
                            item.ProductName = pro.Name;
                            item.ProductCode = pro.Code;
                            item.ProductSupplierId = pro.SupplierId;
                            item.ProductSupplierType = pro.SupplierType;
                        }

                        var orelastic = orderelastics.Items.FirstOrDefault(x => x.Id == item.OrderId);
                        if (orelastic != null)
                        {
                            item.BillCollectMoneyStatus = orelastic.BillCollectMoneyStatus;
                            item.BillCompletedTime = orelastic.BillCompletedTime;
                        }
                        if (item.ReasonId > 0)
                        {
                            var re = reasons.FirstOrDefault(x => x.Id == item.ReasonId);
                            if (re != null)
                            {
                                item.ReasonIsReject = re.IsReject;
                                item.ReasonName = re.Name;
                                item.ReasonType = re.ReasonType;
                            }
                        }

                        //lấy ra productbill mới nhất
                        if (item.CreatedTime.HasValue)
                        {
                            var queryProductBill = new Nest.BoolQueryDescriptor<ProductBillElastic>();
                            var notDeleteProductBillQ = new Nest.QueryContainerDescriptor<ProductBillElastic>();
                            notDeleteProductBillQ.Terms(t => t.Field(f => f.IsDeleted).Terms(false));
                            var productProductBillQ = new Nest.QueryContainerDescriptor<ProductBillElastic>();
                            productProductBillQ.Terms(e => e.Field(f => f.ProductId).Terms(item.ProductId));
                            var bookTypeQ = new Nest.QueryContainerDescriptor<ProductBillElastic>();
                            bookTypeQ.Terms(e => e.Field(f => f.BookType).Terms(2));
                            var vocherDateToLongQ = new Nest.QueryContainerDescriptor<ProductBillElastic>();
                            if (item.CreatedTime.HasValue)
                                vocherDateToLongQ.Range(dr => dr.Field(f => f.VocherDateToLong).LessThanOrEquals(item.CreatedTime.Value.ToUnixTimeMilliseconds()));

                            queryProductBill.Filter(f => notDeleteProductBillQ && productProductBillQ && vocherDateToLongQ && bookTypeQ);

                            var sortDescriptor = new Nest.SortDescriptor<ProductBillElastic>();
                            Expression<Func<ProductBillElastic, object>> sortlamda = f => f.VocherDateToLong;
                            Nest.SortOrder sortOrder = Nest.SortOrder.Descending;
                            sortDescriptor.Field(sortlamda, sortOrder);

                            var r3 = await _productBillRepository.SearchBySort(queryProductBill, sortDescriptor, 1);
                            if (r3.Items.Any())
                            {
                                var prbill = r3.Items.First();
                                if (prbill.UnitId == item.UnitId)
                                {
                                    item.CostPrice = prbill.Price;
                                    item.AvailableInItock = prbill.InventoryQuantity >= item.Quantity;
                                }
                                else
                                {
                                    var unitall = productUnitAlls.Items.Where(x => x.ProductId == item.ProductId).ToList();
                                    if (unitall.Any())
                                    {
                                        var cur = unitall.FirstOrDefault(x => x.UnitId == item.UnitId);
                                        var min = unitall.FirstOrDefault(x => x.UnitId == prbill.UnitId);
                                        if (cur != null && min != null && min.Exchange != 0)
                                        {
                                            item.CostPrice = prbill.Price * cur.Exchange / min.Exchange;
                                            item.AvailableInItock = prbill.InventoryQuantity * min.Exchange >= item.Quantity * cur.Exchange;
                                        }
                                    }
                                }
                            }
                            else
                            {
                                item.CostPrice = 0;
                                item.AvailableInItock = false;
                            }
                        }
                        //lấy ngày nhập kho gần nhất
                        if (item.OrderDate.HasValue)
                        {
                            var queryProductBill = new Nest.BoolQueryDescriptor<InboundBillElastic>();
                            var notDeleteProductBillQ = new Nest.QueryContainerDescriptor<InboundBillElastic>();
                            notDeleteProductBillQ.Terms(t => t.Field(f => f.IsDeleted).Terms(false));
                            var productProductBillQ = new Nest.QueryContainerDescriptor<InboundBillElastic>();
                            productProductBillQ.Terms(e => e.Field(f => f.ProductId).Terms(item.ProductId));
                            var inboundStatusQ = new Nest.QueryContainerDescriptor<InboundBillElastic>();
                            inboundStatusQ.Terms(e => e.Field(f => f.InboundStatus).Terms(3));
                            var billStatusQ = new Nest.QueryContainerDescriptor<InboundBillElastic>();
                            billStatusQ.Terms(e => e.Field(f => f.BillStatus).Terms(3));

                            var completedTimeQ = new Nest.QueryContainerDescriptor<InboundBillElastic>();
                            completedTimeQ.DateRange(dr => dr.Field(f => f.CompletedTime).GreaterThanOrEquals(Nest.DateMath.Anchored(new DateTime(item.OrderDate.Value.Ticks).AddHours(-7))));

                            queryProductBill.Filter(f => notDeleteProductBillQ && productProductBillQ && completedTimeQ && billStatusQ && inboundStatusQ);

                            var sortDescriptor = new Nest.SortDescriptor<InboundBillElastic>();
                            Expression<Func<InboundBillElastic, object>> sortlamda = f => f.CompletedTime;
                            Nest.SortOrder sortOrder = Nest.SortOrder.Ascending;
                            sortDescriptor.Field(sortlamda, sortOrder);

                            var r3 = await _inboundBillRepository.SearchBySort(queryProductBill, sortDescriptor, 1);
                            if (r3.Items.Any())
                            {
                                var prbill = r3.Items.First();
                                if (prbill.UnitId == item.UnitId)
                                {
                                    if (prbill.Quantity >= item.Quantity)
                                        item.InboundDate = prbill.CompletedTime;
                                }
                                else
                                {
                                    var unitall = productUnitAlls.Items.Where(x => x.ProductId == item.ProductId).ToList();
                                    if (unitall.Any())
                                    {
                                        var cur = unitall.FirstOrDefault(x => x.UnitId == item.UnitId);
                                        var min = unitall.FirstOrDefault(x => x.UnitId == prbill.UnitId);
                                        if (cur != null && min != null && min.Exchange != 0)
                                        {
                                            if (prbill.Quantity * min.Exchange >= item.Quantity * cur.Exchange)
                                                item.InboundDate = prbill.CompletedTime;
                                        }
                                    }
                                }
                            }
                        }

                        // lấy đơn vị nhỏ nhất
                        var productunit = productUnits.Items.FirstOrDefault(x => x.ProductId == item.ProductId);
                        if (productunit != null)
                        {
                            item.BaseUnitId = productunit.UnitId;
                            if (item.BaseUnitId > 0)
                            {
                                item.BaseUnitName = units.Items.FirstOrDefault(x => x.Id == item.BaseUnitId)?.Name;
                            }
                        }
                        item.UnitName = units.Items.FirstOrDefault(x => x.Id == item.UnitId)?.Name;
                        var unitexchange = productUnitAlls.Items.FirstOrDefault(x => x.UnitId == item.UnitId && x.ProductId == item.ProductId);
                        if (unitexchange != null)
                        {
                            item.BaseQuantity = unitexchange.Exchange * item.Quantity;
                            item.BaseActualQuantity = unitexchange.Exchange * item.ActualQuantity;
                            item.BasePrice = unitexchange.Exchange != 0 ? item.Price / unitexchange.Exchange : item.Price;
                            item.BaseCostPrice = unitexchange.Exchange != 0 ? item.CostPrice / unitexchange.Exchange : item.CostPrice;
                        }
                    }

                    //ProductOrderRs = ProductOrderRs.Where(x => x.CostPrice.HasValue).ToList();

                    if (ProductOrderRs.Any())
                    {
                        var rs2 = await _ProductOrderRepository.AddManyAsync(ProductOrderRs);
                        if (rs2.Result == Result.Success)
                        {
                            var p = new Dto.DapperTypes.IdsParam(ProductOrderRs.Select(x => x.Id).ToList());
                            await _dbConnection.ExecuteAsync("Synced_ProductOrder_By_List_Id", p, commandType: CommandType.StoredProcedure);
                            rs.Message = $"{ProductOrderRs.Count()} synced";
                            rs.Result = Result.Success;
                        }
                        else
                        {
                            rs.Message = $"can not insert db {string.Join(",", ProductOrderRs.Select(x => x.Id).ToList())}";
                            rs.Result = Result.SystemError;
                        }
                    }
                    else
                    {
                        rs.Message = $"no data to update";
                        rs.Result = Result.Success;
                    }
                }
            }
            catch (Exception ex)
            {
                rs.Message = ex.Message.ToString();
                rs.Result = Result.SystemError;
            }
            return rs;
        }
    }
}