using Bp.Sync.BackEnd.Infrastructure.Repositories;
using Bp.Sync.BackEnd.Infrastructure.Repositories.FcBusinesses;
using Bp.Sync.Domain.Elastics.Inventories;
using Bp.Sync.Domain.Elastics.Inventories.Bills;
using Bp.Sync.Domain.Elastics.Inventories.Configurations;
using Bp.Sync.Domain.Elastics.Inventories.Orders;
using Bp.Sync.Domain.Elastics.Inventories.Products;
using Bp.Sync.Domain.Entities.FcBusinesses.Bills;
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.InboundBills
{
    public class UpdateInboundBillHandler : IRequestHandler<UpdateInboundBillCommand, BaseResult>
    {
        private readonly IInboundBillRepository _InboundBillRepository;
        private readonly IBillRepository _billRepository;
        private readonly IProductRepository _productRepository;
        private readonly IUnitRepository _unitRepository;
        private readonly IProductUnitRepository _productUnitRepository;
        private readonly IDbConnection _dbConnection;
        private readonly IBatchRepository _batchRepository;
        private readonly IProductOrderRepository _productOrderRepository;
        private readonly IBus _bus;
        private readonly ICustomerRepository _customerRepository;
        private readonly IContractDiscountLevelRepository _contractDiscountLevelRepository;
        private readonly IProductInventoryRepository _productInventoryRepository;

        public UpdateInboundBillHandler(IInboundBillRepository InboundBillRepository,
            IBillRepository billRepository,
            IProductUnitRepository productUnitRepository,
            IConfiguration configuration,
            IBatchRepository batchRepository,
            IProductRepository productRepository,
            IProductOrderRepository productOrderRepository,
            IBus bus,
            ICustomerRepository customerRepository,
            IContractDiscountLevelRepository contractDiscountLevelRepository,
            IProductInventoryRepository productInventoryRepository,
            IUnitRepository unitRepository)
        {
            var connectionString = configuration.GetConnectionString("InventoryConnection");
            _dbConnection = new SqlConnection(connectionString);
            _InboundBillRepository = InboundBillRepository;
            _billRepository = billRepository;
            _productRepository = productRepository;
            _unitRepository = unitRepository;
            _productUnitRepository = productUnitRepository;
            _batchRepository = batchRepository;
            _productOrderRepository = productOrderRepository;
            _bus = bus;
            _customerRepository = customerRepository;
            _contractDiscountLevelRepository = contractDiscountLevelRepository;
            _productInventoryRepository = productInventoryRepository;
        }

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

            var listUpdated = new List<InboundBillElastic>();

            //lay ra InboundBill k co invoice
            var query = new BoolQueryDescriptor<InboundBillElastic>();
            var notUnitNameQ = new QueryContainerDescriptor<InboundBillElastic>();
            notUnitNameQ.Bool(b => b.MustNot(m => m.Exists(e => e.Field(f => f.UnitName))));
            var notDestBranchQ = new QueryContainerDescriptor<InboundBillElastic>();
            notDestBranchQ.Bool(b => b.MustNot(m => m.Exists(e => e.Field(f => f.DestBranchId))));
            var notProductUnitQ = new QueryContainerDescriptor<InboundBillElastic>();
            notProductUnitQ.Bool(b => b.MustNot(m => m.Exists(e => e.Field(f => f.ProductUnitId))));
            var notBillCOdeQ = new QueryContainerDescriptor<InboundBillElastic>();
            notBillCOdeQ.Bool(b => b.MustNot(m => m.Exists(e => e.Field(f => f.BillCode))));
            //var difPriceRateQ = new QueryContainerDescriptor<InboundBillElastic>();
            //difPriceRateQ.Range(e => e.Field(f => f.DifPriceRate).LessThanOrEquals(0));

            var billTypeQ = new QueryContainerDescriptor<InboundBillElastic>();
            billTypeQ.Terms(e => e.Field(f => f.BillType).Terms(2, 3));
            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();
                }
            }

            var contractProductQ = new QueryContainerDescriptor<InboundBillElastic>();
            contractProductQ.Terms(e => e.Field(f => f.ProductId).Terms(contractProductids));
            var notInventoryCodeBillCreatedTimeQ = new QueryContainerDescriptor<InboundBillElastic>();
            notInventoryCodeBillCreatedTimeQ.Bool(b => b.MustNot(m => m.Exists(e => e.Field(f => f.InventoryCodeInboundCreatedTime))));
            var notSupplierQ = new QueryContainerDescriptor<InboundBillElastic>();
            notSupplierQ.Bool(b => b.MustNot(m => m.Range(e => e.Field(f => f.SupplierId).GreaterThan(0))));

            var idQ = new QueryContainerDescriptor<InboundBillElastic>();
            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 || (billTypeQ && contractProductQ && (notInventoryCodeBillCreatedTimeQ || notSupplierQ))/* || difPriceRateQ*/);
            else query.Filter(fl => notDestBranchQ || notUnitNameQ || notProductUnitQ || notBillCOdeQ/* || difPriceRateQ*/);
            var items = await _InboundBillRepository.Search(query, 2000);

            if (items.Items == null || !items.Items.Any()) return new BaseResult { Message = "No data update", Result = Dto.Results.Result.Success };

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

            var InboundBillRs = InboundBills.ToInboundBills();

            var billIds = InboundBillRs.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 billRelateIds = bills.Select(x => x.RelateId).Distinct().ToList();
            var p3 = new Dto.DapperTypes.IdsParam(billRelateIds);
            var billRelates = await _dbConnection.QueryAsync<Bill>("Sync_Bill_Get_By_List_Id", p3, commandType: CommandType.StoredProcedure);

            var productIds = InboundBillRs.Select(x => x.ProductId).Distinct().ToList();
            var products = await _productRepository.GetByIds(productIds);
            var unitIds = InboundBillRs.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 => x.CustomerId).Distinct().ToList();

            var customerRelateIds = billRelates.Select(x => (long)x.CustomerId).Distinct().ToList();
            var customerRelates = await _customerRepository.GetByIds(customerRelateIds);

            var inboundType23s = 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.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));

                queryPI.Filter(f => isDeletedPIQ && productIQ && billStatusIQ && inboundStatusIQ && inventoryCodeIQ && billTypeIQ);
                var rsPI = await _InboundBillRepository.Search(queryPI, 10000);
                inboundType23s = rsPI.Items.ToList();
            }

            if (bills != null && bills.Any())
            {
                //lấy ra relate bill
                //lấy ngày hết hạn

                var queryBatch = new BoolQueryDescriptor<BatchElastic>();
                var inventoryCodes = InboundBillRs.Where(x => !string.IsNullOrEmpty(x.InventoryCode) && !string.IsNullOrWhiteSpace(x.InventoryCode)).Select(x => x.InventoryCode).Distinct().ToList();
                var inventoryCodeBatchQ = new QueryContainerDescriptor<BatchElastic>();
                if (inventoryCodes.Any())
                    inventoryCodeBatchQ.Terms(e => e.Field(f => f.InventoryCode.Suffix("keyword")).Terms(inventoryCodes));
                var productBatchQ = new QueryContainerDescriptor<BatchElastic>();
                productBatchQ.Terms(e => e.Field(f => f.ProductId).Terms(productIds));
                var notDeleteBatchQ = new QueryContainerDescriptor<BatchElastic>();
                notDeleteBatchQ.Terms(t => t.Field(f => f.IsDeleted).Terms(false));
                queryBatch.Filter(f => inventoryCodeBatchQ && productBatchQ && notDeleteBatchQ);
                var batches = await _batchRepository.Search(queryBatch, 10000);

                foreach (var item in InboundBillRs)
                {
                    var br = batches.Items.FirstOrDefault(x => x.InventoryCode == item.InventoryCode && x.ProductId == item.ProductId);
                    item.ExpiryDate = br?.ExpiryDate;
                    item.BatchName = br?.Name;
                    var bill = bills.FirstOrDefault(x => x.Id == item.BillId);
                    if (bill != null)
                    {
                        var billrelate = billRelates.FirstOrDefault(x => x.Id == bill.RelateId);
                        if (billrelate != null && billrelate.MedrepId > 0)
                        {
                            item.BillIsMedrepReturn = true;
                            item.BillIdMedrepReturn = billrelate.MedrepId;
                            item.BillRelateCreatedTime = billrelate.CreatedTime;
                            item.BillRelateSaleTypeReturn = billrelate.SaleType;
                            item.BillRelateCode = billrelate.Code;
                            item.BillRelateCustomerId = billrelate.CustomerId;
                        }
                        item.BillIsDeleted = bill.IsDeleted;
                        item.BillStatus = bill.BillStatus;
                        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.BillInvoiceCode = bill.InvoiceCode;
                        item.BillVocherDate = bill.VocherDate;
                    }

                    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.PreCostPrice == 0)
                    {
                        var productQ = new QueryContainerDescriptor<InboundBillElastic>();
                        productQ.Terms(e => e.Field(f => f.ProductId).Terms(item.ProductId));
                        var idLessThanQ = new QueryContainerDescriptor<InboundBillElastic>();
                        idLessThanQ.Range(e => e.Field(f => f.Id).LessThan(item.Id));
                        var statusQ = new QueryContainerDescriptor<InboundBillElastic>();
                        statusQ.Terms(e => e.Field(f => f.BillStatus).Terms(3));
                        var inboundstatusQ = new QueryContainerDescriptor<InboundBillElastic>();
                        inboundstatusQ.Terms(e => e.Field(f => f.InboundStatus).Terms(3));
                        var unitQ = new QueryContainerDescriptor<InboundBillElastic>();
                        unitQ.Terms(e => e.Field(f => f.UnitId).Terms(item.UnitId));
                        var query2 = new BoolQueryDescriptor<InboundBillElastic>();
                        query2.Filter(f => productQ && idLessThanQ && statusQ && inboundstatusQ && unitQ);

                        var ib = await _InboundBillRepository.Search(query2, 1);
                        if (ib.Items.Any())
                            item.PreCostPrice = ib.Items.First().CostPrice;
                        else
                        {
                            query2.Filter(f => productQ && idLessThanQ && statusQ && inboundstatusQ);
                            var ib2 = await _InboundBillRepository.Search(query2, 1);
                            if (ib2.Items.Any())
                            {
                                var ib22 = ib2.Items.First();
                                var unt = productUnitAlls.Items.FirstOrDefault(x => x.UnitId == item.UnitId && x.ProductId == item.ProductId);
                                var unt2 = productUnitAlls.Items.FirstOrDefault(x => x.UnitId == ib22.UnitId && x.ProductId == ib22.ProductId);
                                item.PreCostPrice = ib22.CostPrice * unt.Exchange / unt2.Exchange;
                            }
                        }
                    }

                    if (item.BillRelateCustomerId.HasValue && item.BillRelateCustomerId.Value > 0)
                    {
                        var cust = customerRelates.Items.FirstOrDefault(x => x.Id == item.BillRelateCustomerId);
                        if (cust != null)
                        {
                            item.BillRelateCustomerManagerId = cust.ManagerId;
                            item.BillRelateCustomerType = cust.CustomerType;
                        }
                    }
                    if (item.BillType != Domain.Entities.Inventories.BillType.InboundFromSupplier)
                    {
                        var inboundtype23 = inboundType23s.FirstOrDefault(x => x.InventoryCode == item.InventoryCode && x.BillType == Domain.Entities.Inventories.BillType.InboundFromSupplier);
                        if (inboundtype23 != null)
                        {
                            item.InventoryCodeInboundCreatedTime = inboundtype23.CreatedTime;
                            item.InventoryCodeBillId = inboundtype23.BillId;
                            item.SupplierId = inboundtype23.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 _InboundBillRepository.AddManyAsync(listUpdated);
                if (rs2.Result == Dto.Results.Result.Success)
                {
                    rs.Message = $"{listUpdated.Count} - updated";
                    rs.Result = Dto.Results.Result.Success;

                    #region đồng bộ các sản phẩm ở đơn hàng chưa được nhập kho

                    //lay ra thieu gia von
                    var query2 = new Nest.BoolQueryDescriptor<ProductOrderElastic>();
                    var notDelete2Q = new Nest.QueryContainerDescriptor<ProductOrderElastic>();
                    notDelete2Q.Terms(t => t.Field(f => f.IsDeleted).Terms(false));
                    var productids = listUpdated.Select(x => x.ProductId).Distinct().ToList();
                    var product2Q = new Nest.QueryContainerDescriptor<ProductOrderElastic>();
                    product2Q.Terms(t => t.Field(f => f.ProductId).Terms(productids));
                    var notInboundDate2Q = new Nest.QueryContainerDescriptor<ProductOrderElastic>();
                    notInboundDate2Q.Bool(b => b.MustNot(m => m.Exists(t => t.Field(f => f.InboundDate))));

                    query2.Filter(f => notDelete2Q && product2Q && notInboundDate2Q);
                    var pros = await _productOrderRepository.Search(query2, 10000);

                    if (pros.Items.Any())
                    {
                        var pids = pros.Items.Select(x => x.Id).Distinct().ToList();
                        if (pids.Count() > 1000)
                        {
                            var length = pids.Count();
                            if (length > 0)
                            {
                                for (int i = 0; i <= length / 1000; i++)
                                {
                                    if (length > i * 1000)
                                    {
                                        var ids = pids.Skip(i * 1000).Take(1000).ToList();
                                        if (ids.Count() > 0)
                                        {
                                            await _bus.SendAsync($"UpdateToElasticProductOrder", ids.ConvertListLongToString());
                                        }
                                    }
                                }
                            }
                        }
                    }

                    #endregion đồng bộ các sản phẩm ở đơn hàng chưa được nhập kho
                }
                else
                {
                    rs.Message = $"can not insert db";
                    rs.Result = Dto.Results.Result.Success;
                }
            }
            return rs;
        }
    }
}