namespace Hims.Infrastructure.Services
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Dapper;
    using Domain.Entities;
    using Domain.Repositories.UnitOfWork;
    using Domain.Services;
    using Shared.EntityModels;

    using Shared.UserModels;
    using Hims.Shared.Library.Enums;
    using Hims.Shared.UserModels.Common;
    using Hims.Shared.UserModels.Pharmacy;
    using Hims.Domain.Entities.Pharmacy;


    /// <inheritdoc />
    public class PharmacyWareHouseServices : IPharmacyWareHouseService
    {
        /// <summary>
        /// The unit of work.
        /// </summary>
        private readonly IUnitOfWork unitOfWork;

        /// <summary>
        /// Initializes a new instance of the <see cref="WebTelemedicineServices"/> class.
        /// </summary>
        /// <param name="unitOfWork">
        /// The unit of work.
        /// </param>
        public PharmacyWareHouseServices(IUnitOfWork unitOfWork) => this.unitOfWork = unitOfWork;

        /// <inheritdoc />
        public async Task<int> InsertPharmacyWareHouse(PharmacyWareHouseModel model)
        {
            var where = " and 1=1 ";
            if (model.LocationId != null)
            {
                where += $@" and ""LocationId""={model.LocationId}";
            }
            if (model.CentralWarehouseLocationId != null)
            {
                where += $@" and ""CentralWarehouseLocationId"" = {model.CentralWarehouseLocationId}";
            }

            var checkIf = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<int>($@"Select count(*) from ""PharmacyWareHouse"" where lower(""WareHouseName"") = lower('{model.WareHouseName}') {where}");

            if (checkIf != 0)
            {
                return -2;
            }

            var transaction = this.unitOfWork.BeginTransaction();
            var warehouse = new PharmacyWareHouse
            {
                CreatedBy = model.CreatedBy,
                CreatedDate = DateTime.Now,
                WareHouseName = model.WareHouseName,
                Active = true,
                LocationId = model.LocationId,
                CentralWarehouseLocationId = model.CentralWarehouseLocationId
            };

            warehouse.PharmacyWareHouseId = await this.unitOfWork.PharmacyWareHouses.InsertAsync(warehouse);
            if (warehouse.PharmacyWareHouseId == 0)
            {
                transaction.Rollback();
                return -1;
            }

            if (!string.IsNullOrEmpty(model.AllowedAccountId))
            {
                var allowedIds = model.AllowedAccountId.Split(",");
                var models = allowedIds.Select(m => new PharmacyWareHouseUser { AccountId = int.Parse(m), PharmacyWareHouseId = warehouse.PharmacyWareHouseId });
                var userResponse = await this.unitOfWork.PharmacyWareHouseUsers.BulkInsertAsync(models, transaction);
                if (userResponse == 0)
                {
                    transaction.Rollback();
                    return -1;
                }
            }

            transaction.Commit();
            try
            {
                if (model.LocationId == null)
                {
                    await this.CheckForRetailInGlobalWarehouse(warehouse.PharmacyWareHouseId, "Insert");
                }
            }
            catch (Exception)
            {
                // ignore
            }
            return warehouse.PharmacyWareHouseId;
        }

        /// <inheritdoc />
        public async Task<int> UpdatePharmacyWareHouse(PharmacyWareHouseModel model)
        {
            var where = " and 1=1 ";
            if (model.LocationId != null)
            {
                where += $@" and ""LocationId""={model.LocationId}";
            }

            var checkIf = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<int>($@"Select count(*) from ""PharmacyWareHouse"" where lower(""WareHouseName"") = lower('{model.WareHouseName}') and ""PharmacyWareHouseId"" <> {model.PharmacyWareHouseId} {where}");

            if (checkIf != 0)
            {
                return -2;
            }

            var warehouse = await this.unitOfWork.PharmacyWareHouses.FindAsync(w => w.PharmacyWareHouseId == model.PharmacyWareHouseId);
            if (warehouse == null)
            {
                return -3;
            }

            var transaction = this.unitOfWork.BeginTransaction();

            warehouse.ModifiedBy = model.CreatedBy;
            warehouse.ModifiedDate = DateTime.Now;
            warehouse.WareHouseName = model.WareHouseName;
            warehouse.LocationId = model.LocationId != null ? model.LocationId : (int?)null;
            warehouse.CentralWarehouseLocationId = model.LocationId != null ? (int?)null : model.CentralWarehouseLocationId;

            var updateReponse = await this.unitOfWork.PharmacyWareHouses.UpdateAsync(warehouse, transaction);

            if (updateReponse == 0)
            {
                transaction.Rollback();
                return -1;
            }

            if (!string.IsNullOrEmpty(model.AllowedAccountId))
            {
                var deleteQuery = $@"DELETE FROM ""PharmacyWareHouseUser""	WHERE ""PharmacyWareHouseId"" = {model.PharmacyWareHouseId}";
                await this.unitOfWork.Current.ExecuteAsync(deleteQuery, transaction);


                var allowedIds = model.AllowedAccountId.Split(",");
                var models = allowedIds.Select(m => new PharmacyWareHouseUser { AccountId = int.Parse(m), PharmacyWareHouseId = warehouse.PharmacyWareHouseId });
                var userResponse = await this.unitOfWork.PharmacyWareHouseUsers.BulkInsertAsync(models, transaction);
                if (userResponse == 0)
                {
                    transaction.Rollback();
                    return -1;
                }
            }

            transaction.Commit();
            try
            {
                if (model.LocationId == null)
                {
                    await this.CheckForRetailInGlobalWarehouse(warehouse.PharmacyWareHouseId, "Update");
                }
            }
            catch (Exception)
            {
                // ignore
            }
            return warehouse.PharmacyWareHouseId;
        }

        /// <inheritdoc />
        public async Task<IEnumerable<PharmacyWareHouseModel>> FetchAllWarehousesAsync(PharmacyWareHouseModel model)
        {
            var where = $@"where 1=1";
            if (model.PharmacyWareHouseId != null && model.PharmacyWareHouseId > 0)
            {
                where += $@" and PWH.""PharmacyWareHouseId"" = {model.PharmacyWareHouseId} and PWH.""LocationId""={model.LocationId}";
            }
            else if (model.LocationId != null)
            {
                where += $@" and (PWH.""LocationId""={model.LocationId} or PWH.""LocationId"" is null)"; // Changing for central pharmacy.
            }
            if (!string.IsNullOrEmpty(model.WarehouseIds))
            {
                where += $@" and PWH.""PharmacyWareHouseId"" in ({model.WarehouseIds})";
            }
            if (model.Active != null)
            {
                where += $@" and PWH.""Active"" IS {model.Active}";
            }
            var query = $@"SELECT count(PWH.*) over() as ""TotalItems"", PWH.""PharmacyWareHouseId"", PWH.""WareHouseName"", PWH.""CreatedBy"", PWH.""CreatedDate"", PWH.""ModifiedBy"", 
		                        PWH.""ModifiedDate"",PWH.""Active"" , CA.""FullName"" as ""CreatedByName"",CR.""RoleName"" as ""CreatedByRole"",
		                        CM.""FullName"" as ""ModifiedByName"",MR.""RoleName"" as ""ModifiedByRole"",L.""Name"" as ""LocationName"",PWH.""LocationId"",
                                cL.""Name"" as ""CentralWarehouseLocation"", PWH.""CentralWarehouseLocationId""
	                        FROM ""PharmacyWareHouse"" PWH
	                        join ""Account"" CA on CA.""AccountId"" = PWH.""CreatedBy""
	                        join ""Role"" CR on CR.""RoleId"" = CA.""RoleId""
	                        left join ""Location"" L on L.""LocationId"" = PWH.""LocationId""
	                        left join ""Account"" CM on CM.""AccountId"" = PWH.""ModifiedBy""
	                        left join ""Role"" MR on MR.""RoleId"" = CM.""RoleId""
                            left join ""Location"" cL on cL.""LocationId"" = PWH.""CentralWarehouseLocationId""
                            {where}                            
                            order by PWH.""CreatedDate"" desc";
            if (model.PageIndex != null && model.PageSize != null)
            {
                model.PageIndex = model.PageIndex > 0 ? model.PageIndex - 1 : model.PageIndex;
                query += $@" limit {model.PageSize} offset {model.PageIndex * model.PageSize}";
            }
            var response = await this.unitOfWork.Current.QueryAsync<PharmacyWareHouseModel>(query);
            if (response == null)
            {
                return response;
            }

            foreach (var data in response)
            {
                data.AllowedUsers = new List<AllowedAccount>();

                var user = $@"where 1=1 and PW.""PharmacyWareHouseId"" = {data.PharmacyWareHouseId}";
                if (model.userBasedStore != null && model.userBasedStore > 0)
                {
                    user += $@" and A.""AccountId"" = {model.userBasedStore}";
                }
                var queryUser = $@"SELECT PW.""PharmacyWareHouseUserId"", PW.""PharmacyWareHouseId"", PW.""AccountId"",A.""FullName"",R.""RoleName""
	                                    FROM ""PharmacyWareHouseUser"" PW
	                                    join ""Account"" A on A.""AccountId"" = PW.""AccountId""
	                                    join ""Role"" R on R.""RoleId"" = A.""RoleId""
	                                    {user}";
                data.AllowedUsers = (await this.unitOfWork.Current.QueryAsync<AllowedAccount>(queryUser)).ToList();

                data.RetailStore = new List<RetailPharmacyModel>();
                var queryStore = $@"Select RP.* from ""RetailPharmacy"" RP
                                            join ""RetailWareHouseLink"" RWL on RWL.""RetailPharmacyId"" = RP.""RetailPharmacyId""
                                            where RWL.""PharmacyWareHouseId"" =  {data.PharmacyWareHouseId}";

                data.RetailStore = (await this.unitOfWork.Current.QueryAsync<RetailPharmacyModel>(queryStore)).ToList();
            }

            if(model.userBasedStore != null)
            {
                response = response.Where(x => x.AllowedUsers.Select(y => y.AccountId).Contains(model.userBasedStore ?? 0));
            }
            
            //if (model.userBasedStore != null && model.userBasedStore > 0)
            //{
            //    var a = response.ToList();
            //    List<PharmacyWareHouseModel> newData = new List<PharmacyWareHouseModel>();
            //    if (a != null)
            //    {
            //        foreach (var data in a)
            //        {
            //            if (data.userBasedStore == model.userBasedStore)
            //            {
            //                newData.AddRange(data) ;
            //            }
            //        }
            //    }
            //    return newData as IEnumerable<PharmacyWareHouseModel>;
            //}
            return response;
        }

        /// <inheritdoc />
        public async Task<IEnumerable<PharmacyWareHouseModel>> FetchAllWarehousesWithRetailCountsAsync(PharmacyWareHouseModel model)
        {
            var where = $@"where 1=1 and PWH.""Active"" is true";
            if (model.LocationId != null)
            {
                where += $@" and PWH.""LocationId""={model.LocationId}";
            }

            if (model.LocationFlag != null)
            {
                where += $@"  or PWH.""LocationId"" is null";
            }

            if (model.LocationId != null && model.LocationFlag != null)
            {
                where = $@"where 1=1 and PWH.""Active"" is true and (PWH.""LocationId""={model.LocationId} or PWH.""LocationId"" is null)";
            }

            if (!string.IsNullOrEmpty(model.WarehouseIds))
            {
                where = $@"where 1=1 and PWH.""PharmacyWareHouseId"" in ({model.WarehouseIds})";
            }

            var query = $@"SELECT PWH.""PharmacyWareHouseId"", PWH.""WareHouseName"",PWH.""LocationId"",PWH.""CentralWarehouseLocationId"",
		                    (Select count(*) from ""RetailPharmacy"" RP 
							join ""RetailWareHouseLink"" RWL on RWL.""RetailPharmacyId"" = RP.""RetailPharmacyId""
							where RWL.""PharmacyWareHouseId"" = PWH.""PharmacyWareHouseId"" and RP.""Active"" is true) as ""RetailStoreCount"",
                            COALESCE(L.""Name"",cL.""Name"") as ""LocationName""
	                        FROM ""PharmacyWareHouse"" PWH	
                            left join ""Location"" L on L.""LocationId"" = PWH.""LocationId""
                            left join ""Location"" cL on cL.""LocationId"" = PWH.""CentralWarehouseLocationId""
							{where}
                            order by PWH.""CreatedDate"" desc";
            return await this.unitOfWork.Current.QueryAsync<PharmacyWareHouseModel>(query);
        }

        /// <inheritdoc />
        public async Task<int> ActivateOrDeactivateWareHouse(PharmacyWareHouseModel model)
        {
            var query = $@"UPDATE ""PharmacyWareHouse""
	                           SET ""ModifiedBy""={model.CreatedBy}, ""ModifiedDate""=now()::timestamp, ""Active""= {model.Active}
	                           WHERE ""PharmacyWareHouseId""= {model.PharmacyWareHouseId}";
            return await this.unitOfWork.Current.ExecuteAsync(query);
        }

        /// <inheritdoc />
        public async Task<int> CheckWareHouseByNameAndLocationAsync(string query)
        {
            return await this.unitOfWork.Current.QuerySingleOrDefaultAsync<int>(query);
        }

        /// <inheritdoc />
        public async Task<IEnumerable<PharmacyWareHouseModel>> GetOnlyWareHousesWithLocationAsync(string condition)
        {
            var where = "where 1=1";
            if (!string.IsNullOrEmpty(condition))
            {
                where += $@" and pwh.""WareHouseName"" in ({condition})";
            }
            var query = $@"select pwh.""PharmacyWareHouseId"" ,pwh.""WareHouseName"", l.""Name"" as ""LocationName"", pwh.""Active""  from ""PharmacyWareHouse"" pwh 
                                  join ""Location"" l on l.""LocationId"" = pwh.""LocationId"" 
                                    {where}
                                  order by l.""Name"" asc";
            return await this.unitOfWork.Current.QueryAsync<PharmacyWareHouseModel>(query);
        }

        /// <inheritdoc />
        public async Task<IEnumerable<PharmacyWareHouseModel>> GetAllWareHousesWithLocationAsync()
        {
            var query = $@"select pwh.""PharmacyWareHouseId"" ,pwh.""WareHouseName"", COALESCE(l.""Name"", cl.""Name"") as ""LocationName"", pwh.""Active""  from ""PharmacyWareHouse"" pwh 
                                  left join ""Location"" l on l.""LocationId"" = pwh.""LocationId"" 
                                  left join ""Location"" cl on cl.""LocationId"" = pwh.""CentralWarehouseLocationId""
                                    where 1=1
                                  order by l.""Name"" asc";
            var warehouses = await this.unitOfWork.Current.QueryAsync<PharmacyWareHouseModel>(query);
            foreach (var warehouse in warehouses)
            {
                warehouse.RetailStore = new List<RetailPharmacyModel>();
                var queryStore = $@"Select RP.* from ""RetailPharmacy"" RP
                                            join ""RetailWareHouseLink"" RWL on RWL.""RetailPharmacyId"" = RP.""RetailPharmacyId""
                                            where RWL.""PharmacyWareHouseId"" =  {warehouse.PharmacyWareHouseId}";

                warehouse.RetailStore = (await this.unitOfWork.Current.QueryAsync<RetailPharmacyModel>(queryStore)).ToList();
            }

            return warehouses;
        }

        /// <inheritdoc />
        public async Task<IEnumerable<LocationModel>> GetLocationIdBasedOnNameAsync(string name)
        {
            var query = $@"select l.""LocationId"" ,l.""Name"" from ""Location"" l where lower(l.""Name"") ilike lower('%{name}%')";
            return await this.unitOfWork.Current.QueryAsync<LocationModel>(query);
        }

        /// <inheritdoc />
        public async Task<int> ModifyPharmacyProductTypeAsync(PharmacyProductTypeModel model)
        {
            var checkIfQuery = $@"select count(*) from ""PharmacyProductType"" where lower(""TypeName"") = lower('{model.TypeName}') and ""IsGeneralItem"" is {model.IsGeneralItem}";
            if (model.PharmacyProductTypeId == 0)
            {
                var checkIf = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<int>(checkIfQuery);
                if (checkIf > 0)
                {
                    return -1;
                }

                var insertion = new PharmacyProductType
                {
                    TypeName = model.TypeName.Trim(),
                    Active = true,
                    CreatedBy = model.CreatedBy,
                    CreatedDate = DateTime.Now,
                    IsGeneralItem = model.IsGeneralItem
                };

                return await this.unitOfWork.PharmacyProductTypes.InsertAsync(insertion);
            }
            else
            {
                checkIfQuery += $@" and ""PharmacyProductTypeId"" <> {model.PharmacyProductTypeId} ";
                var checkIf = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<int>(checkIfQuery);
                if (checkIf > 0)
                {
                    return -1;
                }

                var old = await this.unitOfWork.PharmacyProductTypes.FindAsync(x => x.PharmacyProductTypeId == model.PharmacyProductTypeId);
                if (old == null)
                {
                    return -1;
                }

                old.IsGeneralItem = model.IsGeneralItem;
                old.ModifiedBy = model.CreatedBy;
                old.ModifiedDate = DateTime.Now;
                old.TypeName = model.TypeName;

                return await this.unitOfWork.PharmacyProductTypes.UpdateAsync(old);
            }
        }

        /// <inheritdoc />
        public async Task<int> ModifyPharmacyProductSubTypeAsync(PharmacyProductSubTypeModel model)
        {
            var checkIfQuery = $@"select count(*) from ""PharmacyProductSubType"" where lower(""SubTypeName"") = lower('{model.SubTypeName}') and ""PharmacyProductTypeId"" = {model.PharmacyProductTypeId} ";
            if (model.PharmacyProductSubTypeId == 0)
            {
                var checkIf = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<int>(checkIfQuery);
                if (checkIf > 0)
                {
                    return -1;
                }

                var insertion = new PharmacyProductSubType
                {
                    SubTypeName = model.SubTypeName,
                    Active = true,
                    CreatedBy = model.CreatedBy,
                    CreatedDate = DateTime.Now,
                    PharmacyProductTypeId = model.PharmacyProductTypeId
                };

                return await this.unitOfWork.PharmacyProductSubTypes.InsertAsync(insertion);
            }
            else
            {
                checkIfQuery += $@" and ""PharmacyProductSubTypeId"" <> {model.PharmacyProductSubTypeId} ";
                var checkIf = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<int>(checkIfQuery);
                if (checkIf > 0)
                {
                    return -1;
                }

                var old = await this.unitOfWork.PharmacyProductSubTypes.FindAsync(x => x.PharmacyProductSubTypeId == model.PharmacyProductSubTypeId);
                if (old == null)
                {
                    return -1;
                }

                old.SubTypeName = model.SubTypeName;
                old.ModifiedBy = model.CreatedBy;
                old.ModifiedDate = DateTime.Now;
                old.PharmacyProductTypeId = model.PharmacyProductTypeId;

                return await this.unitOfWork.PharmacyProductSubTypes.UpdateAsync(old);
            }
        }

        /// <inheritdoc />
        public async Task<IEnumerable<PharmacyProductTypeModel>> FetchPharmacyProductTypeAsync(PharmacyProductTypeModel model)
        {
            var where = " where 1=1 ";

            var query = $@"SELECT ppt.""PharmacyProductTypeId"", ppt.""TypeName"", ppt.""Active"", ppt.""CreatedBy"", ppt.""CreatedDate"", ppt.""ModifiedBy"", ppt.""ModifiedDate"", ppt.""IsGeneralItem"",
		                             cr.""FullName"" as  ""CreatedByName"", mr.""FullName"" as ""ModifiedByName""
		                            FROM ""PharmacyProductType"" ppt
		                            left join ""Account"" cr on cr.""AccountId"" = ppt.""CreatedBy"" 
		                            left join ""Account"" mr on mr.""AccountId"" = ppt.""ModifiedBy""  
                                        {where}
		                            order by ppt.""CreatedDate"" desc";

            return await this.unitOfWork.Current.QueryAsync<PharmacyProductTypeModel>(query);
        }

        /// <inheritdoc />
        public async Task<IEnumerable<PharmacyProductSubTypeModel>> FetchPharmacyProductSubTypeAsync(PharmacyProductSubTypeModel model)
        {
            var where = " where 1=1 ";
            if (model.PharmacyProductTypeId > 0)
            {
                where += $@" and ppst.""PharmacyProductTypeId"" = {model.PharmacyProductTypeId}";
            }

            var query = $@"SELECT ppst.""PharmacyProductSubTypeId"" ,ppst.""PharmacyProductTypeId"", ppst.""SubTypeName"" ,ppt.""TypeName"", ppst.""Active"", ppst.""CreatedBy"", 
			                        ppst.""CreatedDate"", ppst.""ModifiedBy"", ppst.""ModifiedDate"", ppt.""IsGeneralItem"",
		                         cr.""FullName"" as  ""CreatedByName"", mr.""FullName"" as ""ModifiedByName""
		                        FROM ""PharmacyProductSubType"" ppst 
		                        join ""PharmacyProductType"" ppt on ppt.""PharmacyProductTypeId"" = ppst.""PharmacyProductTypeId"" 
		                        left join ""Account"" cr on cr.""AccountId"" = ppst.""CreatedBy"" 
		                        left join ""Account"" mr on mr.""AccountId"" = ppst.""ModifiedBy""  
                                    {where}
		                        order by ppst.""CreatedDate"" desc";

            return await this.unitOfWork.Current.QueryAsync<PharmacyProductSubTypeModel>(query);
        }

        /// <summary>
        /// Checks for retail in global warehouse.
        /// </summary>
        /// <param name="pharmacyWareHouseId">The pharmacy ware house identifier.</param>
        /// <param name="condition">The condition.</param>
        /// <returns></returns>
        private async Task<int> CheckForRetailInGlobalWarehouse(int pharmacyWareHouseId, string condition)
        {
            var getAllRetailStore = (await this.unitOfWork.Current.QueryAsync<RetailPharmacy>($@"select * from ""RetailPharmacy""")).ToList();
            switch (condition)
            {
                case "Insert":
                    if (getAllRetailStore.Count > 0)
                    {
                        var retailLink = getAllRetailStore.Select(r => new RetailWareHouseLink
                        {
                            PharmacyWareHouseId = pharmacyWareHouseId,
                            RetailPharmacyId = r.RetailPharmacyId
                        });
                        return await this.unitOfWork.RetailWareHouseLinks.BulkInsertAsync(retailLink);
                    }
                    break;
                case "Update":
                    var getAllLinksOfWareHouse = (await this.unitOfWork.RetailWareHouseLinks.FindAllAsync(l => l.PharmacyWareHouseId == pharmacyWareHouseId)).ToList();
                    if (getAllLinksOfWareHouse.Count > 0)
                    {
                        var retailIdinLinkedTable = (getAllLinksOfWareHouse.Select(x => x.RetailPharmacyId)).ToList();
                        var allRetailIds = (getAllRetailStore.Select(r => r.RetailPharmacyId)).ToList();
                        var newStore = new List<int>();
                        foreach (var retailPharmacyId in allRetailIds)
                        {
                            var getData = (retailIdinLinkedTable.Where(z => z == retailPharmacyId)).ToList();
                            if (getData.Count == 0)
                            {
                                newStore.Add(retailPharmacyId);
                            }
                        }
                        if (newStore.Count > 0)
                        {
                            var retailLink = newStore.Select(r => new RetailWareHouseLink
                            {
                                PharmacyWareHouseId = pharmacyWareHouseId,
                                RetailPharmacyId = r
                            });
                            return await this.unitOfWork.RetailWareHouseLinks.BulkInsertAsync(retailLink);
                        }
                    }
                    break;
            }

            return 0;
        }
    }
}