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

namespace Bp.Sync.BackEnd.Application.Commands.Inventories.Bills.OutboundBills
{
    public class UpdateOutboundBillHandler : IRequestHandler<UpdateOutboundBillCommand, BaseResult>
    {
        private readonly IOutboundBillRepository _OutboundBillRepository;
        private readonly IProductRepository _productRepository;
        private readonly IUnitRepository _unitRepository;
        private readonly IProductUnitRepository _productUnitRepository;
        private readonly IDbConnection _dbConnection;
        private readonly IProductInventoryRepository _productInventoryRepository;
        private readonly IBus _bus;
        private readonly ICustomerRepository _customerRepository;
        private readonly IContractDiscountLevelRepository _contractDiscountLevelRepository;
        private readonly IInboundBillRepository _inboundBillRepository;

        public UpdateOutboundBillHandler(IOutboundBillRepository OutboundBillRepository,
            IProductUnitRepository productUnitRepository,
            IConfiguration configuration,
            IProductInventoryRepository productInventoryRepository,
            IBus bus,
            IProductRepository productRepository,
            ICustomerRepository customerRepository,
            IContractDiscountLevelRepository contractDiscountLevelRepository,
            IInboundBillRepository inboundBillRepository,
            IUnitRepository unitRepository)
        {
            var connectionString = configuration.GetConnectionString("InventoryConnection");
            _dbConnection = new SqlConnection(connectionString);
            _OutboundBillRepository = OutboundBillRepository;
            _productRepository = productRepository;
            _unitRepository = unitRepository;
            _productUnitRepository = productUnitRepository;
            _productInventoryRepository = productInventoryRepository;
            _bus = bus;
            _customerRepository = customerRepository;
            _contractDiscountLevelRepository = contractDiscountLevelRepository;
            _inboundBillRepository = inboundBillRepository;
        }

        public async Task<BaseResult> Handle(UpdateOutboundBillCommand request, CancellationToken cancellationToken)
        {
            var rs = new BaseResult();

            var listUpdated = new List<OutboundBillElastic>();

            var contractProductids = new List<long>();
            if (true)
            {
                var queryPI = new BoolQueryDescriptor<ContractDiscountLevelElastic>();
                var aggsPI = new AggregationContainerDescriptor<ContractDiscountLevelElastic>();
                var isDeletedPIQ = new QueryContainerDescriptor<ContractDiscountLevelElastic>();
                isDeletedPIQ.Terms(t => t.Field(f => f.IsDeleted).Terms(false));
                aggsPI.Terms("product", t => t.Field(f => f.ProductId).Size(10000));
                queryPI.Filter(f => isDeletedPIQ);
                var rsPI = await _contractDiscountLevelRepository.SearchAgg(queryPI, aggsPI);
                var aggPI = rsPI.Aggregations.Terms("product");
                if (aggPI != null)
                {
                    contractProductids = aggPI.Buckets.Select(x => long.Parse(x.Key)).ToList();
                }
            }

            //lay ra OutboundBill k co invoice
            var query = new BoolQueryDescriptor<OutboundBillElastic>();
            var notUnitNameQ = new QueryContainerDescriptor<OutboundBillElastic>();
            notUnitNameQ.Bool(b => b.MustNot(m => m.Exists(e => e.Field(f => f.UnitName))));
            var notProductNameQ = new QueryContainerDescriptor<OutboundBillElastic>();
            notProductNameQ.Bool(b => b.MustNot(m => m.Exists(e => e.Field(f => f.ProductName))));
            var notDestBranchQ = new QueryContainerDescriptor<OutboundBillElastic>();
            notDestBranchQ.Bool(b => b.MustNot(m => m.Exists(e => e.Field(f => f.DestBranchId))));
            var notProductUnitQ = new QueryContainerDescriptor<OutboundBillElastic>();
            notProductUnitQ.Bool(b => b.MustNot(m => m.Exists(e => e.Field(f => f.ProductUnitId))));
            var notBillCOdeQ = new QueryContainerDescriptor<OutboundBillElastic>();
            notBillCOdeQ.Bool(b => b.MustNot(m => m.Exists(e => e.Field(f => f.BillCode))));

            var contractProductQ = new QueryContainerDescriptor<OutboundBillElastic>();
            contractProductQ.Terms(e => e.Field(f => f.ProductId).Terms(contractProductids));
            var notInventoryCodeBillCreatedTimeQ = new QueryContainerDescriptor<OutboundBillElastic>();
            notInventoryCodeBillCreatedTimeQ.Bool(b => b.MustNot(m => m.Exists(e => e.Field(f => f.InventoryCodeInboundCreatedTime))));

            var idQ = new QueryContainerDescriptor<OutboundBillElastic>();
            if (request.Ids.Any()) idQ.Terms(t => t.Field(f => f.Id).Terms(request.Ids));
            if (request.Ids.Any())
                query.Filter(fl => idQ);
            else if (contractProductids.Any())
                query.Filter(fl => notDestBranchQ || notUnitNameQ || notProductUnitQ || notBillCOdeQ || notProductNameQ || (contractProductQ && notInventoryCodeBillCreatedTimeQ));
            else query.Filter(fl => notDestBranchQ || notUnitNameQ || notProductUnitQ || notBillCOdeQ || notProductNameQ);
            var items = await _OutboundBillRepository.Search(query, 2000);

            if (items.Items == null || !items.Items.Any()) return new BaseResult { Message = "No data update", Result = Dto.Results.Result.Success };
            var inbounds = new List<InboundBillElastic>();
            if (contractProductids.Any())
            {
                var queryPI = new BoolQueryDescriptor<InboundBillElastic>();
                var isDeletedPIQ = new QueryContainerDescriptor<InboundBillElastic>();
                isDeletedPIQ.Terms(t => t.Field(f => f.IsDeleted).Terms(false));
                var productIQ = new QueryContainerDescriptor<InboundBillElastic>();
                productIQ.Terms(t => t.Field(f => f.ProductId).Terms(contractProductids));
                var billStatusIQ = new QueryContainerDescriptor<InboundBillElastic>();
                billStatusIQ.Terms(t => t.Field(f => f.BillStatus).Terms(3));
                var inboundStatusIQ = new QueryContainerDescriptor<InboundBillElastic>();
                inboundStatusIQ.Terms(t => t.Field(f => f.InboundStatus).Terms(3));
                var billTypeIQ = new QueryContainerDescriptor<InboundBillElastic>();
                billTypeIQ.Terms(t => t.Field(f => f.BillType).Terms(1));

                var codes = items.Items.Where(x => contractProductids.Contains(x.ProductId)).Select(x => x.InventoryCode).Distinct().ToList();
                var inventoryCodeIQ = new QueryContainerDescriptor<InboundBillElastic>();
                if (codes.Any())
                    inventoryCodeIQ.Terms(e => e.Field(f => f.InventoryCode.Suffix("keyword")).Terms(codes));

                if (codes.Any())
                {
                    queryPI.Filter(f => isDeletedPIQ && productIQ && billStatusIQ && inboundStatusIQ && inventoryCodeIQ && billTypeIQ);
                    var rsPI = await _inboundBillRepository.Search(queryPI, 10000);
                    inbounds = rsPI.Items.ToList();
                }
            }

            var p = new Dto.DapperTypes.IdsParam(items.Items.Select(x => x.Id).ToList());
            var OutboundBills = await _dbConnection.QueryAsync<Domain.Entities.Inventories.Bills.OutboundBill>("Sync_OutboundBill_Get_By_List_Id", p, commandType: CommandType.StoredProcedure);
            if (!OutboundBills.Any()) return new BaseResult { Result = Dto.Results.Result.Failed, Message = "no data" };

            var OutboundBillRs = OutboundBills.ToOutboundBills();

            var parentids = OutboundBills.Select(x => x.ParentId).Distinct().ToList();
            var pparent = new Dto.DapperTypes.IdsParam(parentids);
            var parentOutboundBills = await _dbConnection.QueryAsync<Domain.Entities.Inventories.Bills.OutboundBill>("Sync_OutboundBill_Get_By_List_Id", pparent, commandType: CommandType.StoredProcedure);

            var billIds = OutboundBillRs.Select(x => x.BillId).Distinct().ToList();
            var p2 = new Dto.DapperTypes.IdsParam(billIds);
            var bills = await _dbConnection.QueryAsync<Bill>("Sync_Bill_Get_By_List_Id", p2, commandType: CommandType.StoredProcedure);

            var productIds = OutboundBillRs.Select(x => x.ProductId).Distinct().ToList();
            var products = await _productRepository.GetByIds(productIds);
            var unitIds = OutboundBillRs.Select(x => (long)x.UnitId).Distinct().ToList();
            var units = await _unitRepository.GetAll();
            var productUnits = await _productUnitRepository.GetMinUnitByProductIds(productIds);
            var productUnitAlls = await _productUnitRepository.GetByProductIds(productIds);

            var customerIds = bills.Select(x => (long)x.CustomerId).Distinct().ToList();
            var customers = await _customerRepository.GetByIds(customerIds);

            if (bills != null && bills.Any())
            {
                foreach (var item in OutboundBillRs)
                {
                    var bill = bills.FirstOrDefault(x => x.Id == item.BillId);
                    if (bill != null)
                    {
                        item.BillStatus = bill.BillStatus;
                        item.BillIsDeleted = bill.IsDeleted;
                        item.BillCreatedTime = bill.CreatedTime;
                        item.BillType = bill.BillType;
                        item.BillCode = bill.Code;
                        item.DestBranchId = bill.DestBranchId;
                        item.SupplierId = bill.SupplierId;
                        item.BranchId = bill.BranchId;
                        item.CustomerId = bill.CustomerId;
                        item.BillOrderCode = bill.OrderCode;
                        item.BillMedrepId = bill.MedrepId;
                        item.BillCashierId = bill.CashierId;
                        item.BillCashierDate = bill.CashierDate;
                        item.BillCollectMoneyStatus = bill.CollectMoneyStatus;
                        item.BillDeliveryType = bill.DeliveryType;
                        item.BillSaleType = bill.SaleType;
                        item.BillIsInputCost = bill.IsInputCost;
                        item.BillIsOutputCost = bill.IsOutputCost;
                    }
                    var product = products.Items.FirstOrDefault(x => x.Id == item.ProductId);
                    if (product != null)
                    {
                        item.ProductName = product.Name;
                        item.ProductCode = product.Code;
                    }
                    var unit = units.Items.FirstOrDefault(x => x.Id == item.UnitId);
                    if (unit != null)
                    {
                        item.UnitName = unit.Name;
                    }

                    var productunit = productUnits.Items.FirstOrDefault(x => x.ProductId == item.ProductId);
                    if (productunit != null)
                    {
                        item.ProductUnitId = productunit.UnitId;
                        if (item.ProductUnitId.HasValue && item.ProductUnitId.Value > 0)
                        {
                            var unit2 = units.Items.FirstOrDefault(x => x.Id == item.ProductUnitId);
                            if (unit2 != null)
                            {
                                item.ProductUnitName = unit2.Name;
                            }
                        }
                    }
                    var unitexchange = productUnitAlls.Items.FirstOrDefault(x => x.UnitId == item.UnitId && x.ProductId == item.ProductId);
                    if (unitexchange != null)
                        item.ProductUnitQuantity = unitexchange.Exchange * item.Quantity;

                    if (item.CustomerId.HasValue && item.CustomerId.Value > 0)
                    {
                        var cust = customers.Items.FirstOrDefault(x => x.Id == item.CustomerId);
                        if (cust != null)
                        {
                            item.CustomerManagerId = cust.ManagerId;
                            item.CustomerType = cust.CustomerType;
                        }
                    }

                    var inbound = inbounds.FirstOrDefault(x => x.InventoryCode == item.InventoryCode && x.BillType == BillType.InboundFromSupplier);
                    if (inbound != null)
                    {
                        item.InventoryCodeInboundCreatedTime = inbound.CreatedTime;
                        item.InventoryCodeBillId = inbound.BillId;
                        if (!item.SupplierId.HasValue || (item.SupplierId.HasValue && item.SupplierId.Value <= 0))
                            item.SupplierId = inbound.SupplierId;
                    }
                    if (item.ParentId > 0)
                    {
                        var parentobill = parentOutboundBills.FirstOrDefault(x => x.Id == item.ParentId);
                        if (parentobill != null)
                        {
                            item.ProductParentId = parentobill.ProductId;
                            item.SupplierParentProductId = parentobill.SupplierId;
                        }
                    }
                    listUpdated.Add(item);
                }
            }

            var inventorycodes = listUpdated.Where(x => x.SupplierId == 0).Select(x => x.InventoryCode).Distinct().ToList();

            if (inventorycodes.Any())
            {
                var notDeleteCenterBacklogQ = new QueryContainerDescriptor<ProductInventoryElastic>();
                notDeleteCenterBacklogQ.Terms(t => t.Field(f => f.IsDeleted).Terms(false));
                var inventoryCodeCenterBacklogQ = new QueryContainerDescriptor<ProductInventoryElastic>();
                inventoryCodeCenterBacklogQ.Terms(e => e.Field(f => f.InventoryCode.Suffix("keyword")).Terms(inventorycodes));
                var queryCenterBacklog = new BoolQueryDescriptor<ProductInventoryElastic>();
                queryCenterBacklog.Filter(f => inventoryCodeCenterBacklogQ && notDeleteCenterBacklogQ);
                var centerbacklogs = await _productInventoryRepository.Search(queryCenterBacklog, 10000);
                foreach (var item in listUpdated)
                {
                    if (item.SupplierId == 0)
                    {
                        var cb = centerbacklogs.Items.FirstOrDefault(x => x.InventoryCode == item.InventoryCode);
                        if (cb != null)
                            item.SupplierId = cb.SupplierId;
                    }
                }
            }

            if (listUpdated.Any())
            {
                var rs2 = await _OutboundBillRepository.AddManyAsync(listUpdated);
                if (rs2.Result == Dto.Results.Result.Success)
                {
                    rs.Message = $"{listUpdated.Count} - updated";
                    rs.Result = Dto.Results.Result.Success;

                    //cap nhat gia von trung binh cho product Inventory

                    var queryProducIinventory = new BoolQueryDescriptor<ProductInventoryElastic>();
                    var productPIQ = new QueryContainerDescriptor<ProductInventoryElastic>();
                    productPIQ.Terms(e => e.Field(f => f.ProductId).Terms(productIds));
                    var quantityPIQ = new QueryContainerDescriptor<ProductInventoryElastic>();
                    quantityPIQ.Range(r => r.Field(f => f.Quantity).GreaterThan(0));
                    var notDeletePIQ = new QueryContainerDescriptor<ProductInventoryElastic>();
                    notDeletePIQ.Terms(t => t.Field(f => f.IsDeleted).Terms(false));
                    queryProducIinventory.Filter(f => notDeletePIQ && productPIQ && quantityPIQ);
                    var rpi = await _productInventoryRepository.Search(queryProducIinventory);
                    if (rpi.Items.Any())
                    {
                        var idspi = rpi.Items.Select(x => x.Id).ToList();
                        var length = idspi.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($"UpdateToElasticProductInventoryEx", ids.ConvertListLongToString());
                                    }
                                }
                            }
                        }
                    }
                }
                else
                {
                    rs.Message = $"can not insert db";
                    rs.Result = Dto.Results.Result.Success;
                }
            }
            return rs;
        }
    }
}