namespace Hims.Api.Controllers
{
    using System;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Collections.Generic;

    using Domain.Services;

    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;

    using Shared.DataFilters;
    using Shared.EntityModels;
    using Shared.UserModels;
    using Shared.UserModels.Inventory;
    using Shared.Library.Enums;

    using Utilities;
    using Hims.Api.Models;

    /// <summary>
    /// The inventory controller.
    /// </summary>
    [Authorize]
    [Route("api/inventory-import")]
    [Consumes("application/json")]
    [Produces("application/json")]
    public class InventoryImportController : BaseController
    {
        /// <summary>
        /// The inventory service.
        /// </summary>
        private readonly IInventoryService inventoryService;

        /// <summary>
        /// The Inventory log services.
        /// </summary>
        private readonly IInventoryLogService inventoryLogServices;

        /// <summary>
        /// The company service.
        /// </summary>
        private readonly ICompanyService companyService;

        /// <summary>
        /// The supplier service.
        /// </summary>
        private readonly ISupplierService supplierService;

        /// <summary>
        /// The excel upload history service.
        /// </summary>
        private readonly IExcelUploadHistoryService excelUploadHistoryService;

        /// <summary>
        /// The audit log services.
        /// </summary>
        private readonly IAuditLogService auditLogServices;

        /// <inheritdoc />
        public InventoryImportController(
            IInventoryService inventoryService,
            IInventoryLogService inventoryLogServices,
            ICompanyService companyService,
            ISupplierService supplierService,
            IExcelUploadHistoryService excelUploadHistoryService,
            IAuditLogService auditLogServices)
        {
            this.inventoryService = inventoryService;
            this.inventoryLogServices = inventoryLogServices;
            this.companyService = companyService;
            this.supplierService = supplierService;
            this.excelUploadHistoryService = excelUploadHistoryService;
            this.auditLogServices = auditLogServices;
        }

        /// <summary>
        /// The add products from excel async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>
        /// </returns>
        [HttpPost]
        [Route("add-product-sheet")]
        public async Task<ActionResult> AddProductsFromExcelAsync([FromBody] ExcelInventoryProduct model,[FromHeader]LocationHeader header)
        {
            model = (ExcelInventoryProduct)EmptyFilter.Handler(model);
            if (model.Products.Count == 0)
            {
                return this.BadRequest("Invalid sheet");
            }
            var productList = await this.AddInventoryProductAsync(model);

            var excelHistory = new ExcelUploadHistoryModel
            {
                ExcelUploadHistoryId = model.ExcelUploadHistoryId,
                AddedProducts = string.Join(",", productList.Select(m => m.InventoryProductId)),
                SheetName = model.SheetName,
                SheetType = "Inventory Product Sheet",
                TypeOf = "I",
                UploadedBy = model.CreatedBy,
                ProductStatus = string.Join(",", productList.Select(m => m.IsGetProductId))
            };

            var response = await this.excelUploadHistoryService.ModifyExcelUploadAsync(excelHistory);

            if (model.IsCreateLog)
            {
                try
                {
                    var record = await this.excelUploadHistoryService.GetSingleExcelHistory(response);
                    var iLogs = new InventoryLogModel
                    {
                        AccountId = model.CreatedBy,
                        InventoryLogTypeId = (int)InventoryLogTypes.ExcelProductSheet,
                        LogFrom = (short)model.LoginRoleId,
                        LogDate = DateTime.UtcNow.AddMinutes(330),
                        LogDescription = $@"New Excel sheet with <b>{record.AddedProducts.Split(",").Length} products</b> has been <b>Added</b> Successfully"
                    };
                    await this.inventoryLogServices.LogAsync(iLogs);

                    var auditLogModel = new AuditLogModel
                    {
                        AccountId = model.CreatedBy,
                        LogTypeId = (int)LogTypes.ExcelSheet,
                        LogFrom = (short)model.LoginRoleId,
                        LogDate = DateTime.UtcNow.AddMinutes(330),
                        LogDescription = $@"New excel sheet <b>{model.SheetName}</b> has been uploaded.",
                        LocationId= Convert.ToInt32(header.LocationId)
                    };
                    await this.auditLogServices.LogAsync(auditLogModel);
                }
                catch (Exception)
                {
                    //logs
                }
            }

            return this.Success(response);
        }

        /// <summary>
        /// The add products from excel with purchase bill async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>
        /// </returns>
        [HttpPost]
        [Route("add-product-sheet-with-purchase-bill")]
        public async Task<ActionResult> AddProductsFromExcelWithPurchaseBillAsync([FromBody] ExcelInventoryProduct model, [FromHeader] LocationHeader header)
        {
            model = (ExcelInventoryProduct)EmptyFilter.Handler(model);
            if (model.Products.Count == 0)
            {
                return this.BadRequest("Invalid sheet");
            }
            if (model.PurchaseBills.Count == 0)
            {
                return this.BadRequest("Invalid sheet");
            }

            var productList = await this.AddInventoryProductAsync(model);

            var billResponse = await this.AddPurchaseBillFromExcel(productList, model.PurchaseBills, model.CreatedBy);

            var excelHistory = new ExcelUploadHistoryModel
            {
                ExcelUploadHistoryId = model.ExcelUploadHistoryId,
                AddedProducts = string.Join(",", billResponse.Select(m => m)),
                SheetName = model.SheetName,
                SheetType = "Inventory Purchase Sheet",
                TypeOf = "I",
                UploadedBy = model.CreatedBy,
                ProductStatus = string.Empty
            };

            var response = await this.excelUploadHistoryService.ModifyExcelUploadAsync(excelHistory);

            if (model.IsCreateLog)
            {
                try
                {
                    var record = await this.excelUploadHistoryService.GetSingleExcelHistory(response);
                    var iLogs = new InventoryLogModel
                    {
                        AccountId = model.CreatedBy,
                        InventoryLogTypeId = (int)InventoryLogTypes.ExcelProductSheet,
                        LogFrom = (short)model.LoginRoleId,
                        LogDate = DateTime.UtcNow.AddMinutes(330),
                        LogDescription = $@"New Excel sheet with <b>{record.AddedProducts.Split(",").Length} products and purchase bill</b> has been <b>Added</b> Successfully"
                    };
                    await this.inventoryLogServices.LogAsync(iLogs);

                    var auditLogModel = new AuditLogModel
                    {
                        AccountId = model.CreatedBy,
                        LogTypeId = (int)LogTypes.ExcelSheet,
                        LogFrom = (short)model.LoginRoleId,
                        LogDate = DateTime.UtcNow.AddMinutes(330),
                        LogDescription = $@"New excel sheet <b>{model.SheetName}</b> has been uploaded.",
                        LocationId= Convert.ToInt32(header.LocationId)
                    };
                    await this.auditLogServices.LogAsync(auditLogModel);
                }
                catch (Exception)
                {
                    //logs
                }
            }

            return this.Success(response);
        }

        /// <summary>
        /// The fetch all excel upload history.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>
        /// </returns>
        [HttpPost]
        [Route("fetch-upload-history")]
        public async Task<ActionResult> FetchAllExcelUploadHistory([FromBody] ExcelUploadHistoryModel model)
        {
            model = (ExcelUploadHistoryModel)EmptyFilter.Handler(model);
            var response = await this.excelUploadHistoryService.FetchAllAsync(model);
            return this.Success(response);
        }

        /// <summary>
        /// The fetch all excel products async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>
        /// </returns>
        [HttpPost]
        [Route("fetch-uploaded-products")]
        public async Task<ActionResult> FetchAllExcelProductsAsync([FromBody] ExcelUploadHistoryModel model)
        {
            model = (ExcelUploadHistoryModel)EmptyFilter.Handler(model);
            var response = (await this.inventoryService.FetchInventoryProduct(new ProductModel { BulkProductIds = model.AddedProducts })).ToList();
            var productArray = model.AddedProducts.Split(",");
            var productStatusArray = model.ProductStatus.Split(",");
            var productList = new List<ProductModel>();
            for (int i = 0; i < productArray.Length; i++)
            {
                var productId = int.Parse(productArray[i]);
                var status = bool.Parse(productStatusArray[i]);
                var product = response.Find(m => m.InventoryProductId == productId);
                if (status)
                {
                    product.IsGetProductId = true;
                }
                productList.Add(product);
            }

            return this.Success(productList);
        }

        /// <summary>
        /// The fetch all excel purchase bill async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>
        /// </returns>
        [HttpPost]
        [Route("fetch-uploaded-purchase-bills")]
        public async Task<ActionResult> FetchAllExcelPurchaseBillAsync([FromBody] ExcelUploadHistoryModel model)
        {
            model = (ExcelUploadHistoryModel)EmptyFilter.Handler(model);

            var ids = model.AddedProducts.Split(",");
            var allIds = new List<int>();
            foreach (var id in ids)
            {
                allIds.Add(int.Parse(id));
            }

            var distinctId = allIds.Distinct().ToArray();
            var billsList = new List<AddedPurchaseBillFromExcelModel>();
            foreach (var id in distinctId)
            {
                var singleBill = new AddedPurchaseBillFromExcelModel
                {
                    Bills = new List<PurchaseBillModel>()
                };
                var purchaseBill = (await this.inventoryService.FetchAddedPurchaseBillAsync(id, string.Empty)).ToList();
                singleBill.BillNumber = purchaseBill[0].BillNumber;
                singleBill.InventoryPurchaseHeaderId = purchaseBill[0].InventoryPurchaseHeaderId;
                singleBill.Bills = purchaseBill;
                billsList.Add(singleBill);
            }

            return this.Success(billsList);
        }

        /// <summary>
        /// The add inventory product async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>
        /// </returns>
        private async Task<List<ProductModel>> AddInventoryProductAsync(ExcelInventoryProduct model)
        {
            var totalProductList = new List<ProductModel>();

            foreach (var product in model.Products)
            {
                var singleProduct = new ProductModel();
                var checkCompanyExits = await this.companyService.FetchAsync(new CompanyModel { TypeOf = "inventoryCompany", Name = product.Company });
                if (checkCompanyExits == null)
                {
                    var newCompany = new CompanyModel
                    {
                        Name = product.Company,
                        TypeOf = "inventoryCompany",
                        Location = "Hyderabad"
                    };

                    newCompany.CompanyId = await this.companyService.InsertCompanyAsync(newCompany);
                    singleProduct.CompanyId = newCompany.CompanyId;
                }
                else
                {
                    singleProduct.CompanyId = checkCompanyExits.CompanyId;
                }
                LookupValueModel lookupModel = new LookupValueModel();
                lookupModel.Name = "InventoryCategory";
                var category = await this.CommonLookupValuesAsync(lookupModel, product.Category);
                singleProduct.CategoryId = category.LookupValueId;

                //lookupModel.Name = "InventoryRack";
                //var rack = await this.CommonLookupValuesAsync(lookupModel, product.Rack);
                //singleProduct.RackId = rack.LookupValueId;

                lookupModel.Name = "InventoryUnit";
                var saleUnit = await this.CommonLookupValuesAsync(lookupModel, product.SaleUnit);
                singleProduct.SaleUnit = saleUnit.LookupValueId;

                lookupModel.Name = "InventoryUnit";
                var purchaseUnit = await this.CommonLookupValuesAsync(lookupModel, product.PurchaseUnit);
                singleProduct.PurchaseUnit = purchaseUnit.LookupValueId;

                lookupModel.Name = "InventoryGst";
                var gst = await this.CommonLookupValuesAsync(lookupModel, product.Gst);
                singleProduct.TaxId = gst.LookupValueId;
                singleProduct.Tax = product.Gst;

                singleProduct.PurchaseUnitQuantity = string.IsNullOrEmpty(product.PurchaseQuantity) ? 1 : int.Parse(product.PurchaseQuantity);
                singleProduct.SaleUnitQuantity = string.IsNullOrEmpty(product.SaleQuantity) ? 1 : int.Parse(product.SaleQuantity);
                singleProduct.ProductName = product.ProductName;
                singleProduct.IsBatchNumber = !string.IsNullOrEmpty(product.BatchNumberRequired) && product.BatchNumberRequired.Equals("y", StringComparison.CurrentCultureIgnoreCase);
                singleProduct.IsExpiry = !string.IsNullOrEmpty(product.ProductExpire) && product.ProductExpire.Equals("y", StringComparison.CurrentCultureIgnoreCase);
                //singleProduct.MaxQuantity = string.IsNullOrEmpty(product.MaxQuantity) ? (int?)null : Convert.ToInt32(product.MaxQuantity);
                //singleProduct.MinQuantity = string.IsNullOrEmpty(product.MinQuantity) ? (int?)null : Convert.ToInt32(product.MinQuantity);
                singleProduct.CreatedBy = model.CreatedBy;
               // singleProduct.ReorderLevelQuantity = string.IsNullOrEmpty(product.ReorderQuantity) ? 0 : int.Parse(product.ReorderQuantity);
                singleProduct.SupplierId = (int?)null;
                try
                {
                    singleProduct.IsGetProductId = true;
                    singleProduct.InventoryProductId = await this.inventoryService.CreateInventoryProduct(singleProduct);
                    if (singleProduct.InventoryProductId < 0)
                    {
                        singleProduct.InventoryProductId *= -1;
                    }
                    else
                    {
                        singleProduct.IsGetProductId = false;
                    }
                }
                catch (Exception e)
                {
                    continue;
                }
                finally
                {
                    singleProduct.BillNumber = product.BillNumber;
                    totalProductList.Add(singleProduct);
                }
            }

            return totalProductList;
        }

        /// <summary>
        /// The common lookup values async.
        /// </summary>
        /// <param name="lookupName">
        /// The lookup name.
        /// </param>
        /// <param name="lookupValueName">
        /// The lookup value name.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>
        /// </returns>
        private async Task<LookupValueModel> CommonLookupValuesAsync(LookupValueModel model, string lookupValueName)
        {
            var allCategory = await this.inventoryService.FetchLookupValues(model);
            var checkCategoryExists = allCategory?.ToList().Find(
            m => string.Equals(m.Name, lookupValueName, StringComparison.CurrentCultureIgnoreCase));
            if (checkCategoryExists == null)
            {
                var newCategory = new LookupValueModel
                {
                    LookupId = 0,
                    Name = lookupValueName
                };

                switch (model.Name)
                {
                    case "InventoryCategory":
                        newCategory.LookupValueId = await this.inventoryService.CreateCategoryAsync(newCategory);
                        break;
                    case "InventoryUnit":
                        newCategory.LookupValueId = await this.inventoryService.CreateUnitAsync(newCategory);
                        break;
                    case "InventoryRack":
                        newCategory.LookupValueId = await this.inventoryService.CreateRackAsync(newCategory);
                        break;
                    case "InventoryGst":
                        newCategory.LookupValueId = await this.inventoryService.CreateGstAsync(newCategory);
                        break;
                }

                return newCategory;
            }
            else
            {
                return checkCategoryExists;
            }
        }

        /// <summary>
        /// The add purchase bill from excel.
        /// </summary>
        /// <param name="products">
        /// The products.
        /// </param>
        /// <param name="bills">
        /// The bills.
        /// </param>
        /// <param name="createdBy">
        /// The created by.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>
        /// </returns>
        private async Task<List<int>> AddPurchaseBillFromExcel(List<ProductModel> products, List<RawPurchaseBillModel> bills, int createdBy)
        {
            var purchaseHeaderIds = new List<int>();
            foreach (var product in products)
            {
                var findBill = bills.Find(m => string.Equals(m.ProductName, product.ProductName, StringComparison.CurrentCultureIgnoreCase) && string.Equals(m.BillNumber, product.BillNumber, StringComparison.CurrentCultureIgnoreCase));
                var purchaseBillModel = new InventoryPurchaseBillModel
                {
                    BatchNumber = findBill.BatchNumber,
                    ExpiryDate = string.IsNullOrEmpty(findBill.ExpiryDate) ? (DateTime?)null : Convert.ToDateTime(findBill.ExpiryDate),
                    BillNumber = findBill.BillNumber,
                    CreatedBy = createdBy,
                    ProductId = product.InventoryProductId,
                    BillType = "Cash"
                };
                var getSupplier = await this.supplierService.FetchAsync(new SupplierModel { SupplierOf = "inventory", Name = findBill.SupplierName });

                if (getSupplier == null)
                {
                    purchaseBillModel.SupplierId = await this.supplierService.InsertSupplierAsync(new SupplierModel { Name = findBill.SupplierName, City = "Hyderabad", Address = "Hyderabad", SupplierOf = "inventory" });
                }
                else
                {
                    purchaseBillModel.SupplierId = getSupplier.SupplierId;
                }
                purchaseBillModel.PurchaseRate = double.Parse(findBill.PurchaseRate);
                purchaseBillModel.Quantity = int.Parse(findBill.Quantity);
                purchaseBillModel.Discount = 0;
                purchaseBillModel.DiscountAmount = 0;
                purchaseBillModel.Total = purchaseBillModel.NetAmount = Math.Round(double.Parse(findBill.Quantity) * double.Parse(findBill.PurchaseRate), 2);
                var gst = double.Parse(product.Tax);
                purchaseBillModel.TaxAmount = Math.Round((purchaseBillModel.NetAmount * gst) / 100, 2);

                var response = await this.inventoryService.AddPurchaseBillAsync(purchaseBillModel);
                purchaseHeaderIds.Add(response);
            }

            return purchaseHeaderIds;
        }
    }
}
