using Dapper;
using Hims.Domain.Configurations;
using Hims.Domain.Entities;
using Hims.Domain.Repositories.UnitOfWork;
using Hims.Domain.Services;
using Hims.Shared.EntityModels;
using Hims.Shared.Library;
using Hims.Shared.UserModels.Common;
using Hims.Shared.UserModels.PackageModule;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Hims.Infrastructure.Services
{
    public class PackageModuleServices : IPackageModuleService
    {
        private readonly IUnitOfWork unitOfWork;
        private readonly IRunningEnvironment runningEnvironment;

        public PackageModuleServices(IUnitOfWork unitOfWork, IRunningEnvironment runningEnvironment)
        {
            this.unitOfWork = unitOfWork;
            this.runningEnvironment = runningEnvironment;
        }

        public Task<IEnumerable<Resource>> FetchLocationsAsync()
        {
            var query = $@"SELECT lc.""LocationId"" AS ""Id"", lc.""Name"", cmt.""TemplateName"" AS ""OptionalText"", cmt.""ChargeModuleTemplateId"" AS ""OptionalId""
                        FROM ""Location"" lc
                        JOIN ""ChargeModuleTemplate"" cmt ON cmt.""LocationId"" = lc.""LocationId"" AND cmt.""IsInUse"" IS TRUE AND cmt.""Active"" IS TRUE
                        WHERE lc.""Active"" IS TRUE
                        ORDER BY lc.""Name"" ASC";
            return this.unitOfWork.Current.QueryAsync<Resource>(query);
        }

        public Task<IEnumerable<ModuleModel>> FetchModulesAsync(int chargeModuleTemplateId)
        {
            var query = $@"SELECT DISTINCT MM.""ModulesMasterId"", MM.""ModuleName"", MM.""ModuleIcon"", MM.""PackageType"", MM.""IsChargeCategoryApplicable"",MM.""IsPackageApplicable""
                            FROM ""ModulesMaster"" MM
                            LEFT JOIN ""ChargeModuleCategory"" CMC ON CMC.""ModulesMasterId"" = MM.""ModulesMasterId"" AND CMC.""Active"" IS TRUE AND CMC.""ChargeModuleTemplateId"" = {chargeModuleTemplateId}
                            WHERE (CASE WHEN MM.""IsChargeCategoryApplicable"" IS TRUE THEN CMC.""ChargeModuleCategoryId"" IS NOT NULL ELSE TRUE END)
                            ORDER BY MM.""ModuleName"" ASC";
            return this.unitOfWork.Current.QueryAsync<ModuleModel>(query);
        }

        public async Task<IEnumerable<ModuleChargeModel>> FetchModuleChargesAsync(string modulesMasterIds, int locationId)
        {
            var moduleCharges = new List<ModuleChargeModel>();
            if (modulesMasterIds.IndexOf(",") > 0)
            {
                foreach (var item in modulesMasterIds.Split(","))
                {
                    var moduleMasterId = Convert.ToInt32(item);
                    var query = $@"SELECT * FROM ""udf_fetch_moduleChargeDetails""({moduleMasterId},{locationId})";
                    var data = await this.unitOfWork.Current.QueryAsync<ModuleChargeModel>(query);
                    if (data.Any())
                    {
                        moduleCharges.AddRange(data);
                    }
                }
            }
            else
            {
                var moduleMasterId = Convert.ToInt32(modulesMasterIds);
                var query = $@"SELECT * FROM ""udf_fetch_moduleChargeDetails""({moduleMasterId},{locationId})";
                var data = await this.unitOfWork.Current.QueryAsync<ModuleChargeModel>(query);
                if (data.Any())
                {
                    moduleCharges.AddRange(data);
                }
            }

            return moduleCharges;
        }

        public Task<IEnumerable<Resource>> FetchChargeCategoriesAsync(int chargeModuleTemplateId)
        {
            var query = $@"SELECT DISTINCT CMC.""ChargeCategoryId"" AS ""Id"", CC.""ChargeCategoryName"" AS ""Name""
                            FROM ""ChargeModuleCategory"" CMC
                            JOIN ""ChargeCategory"" CC ON CC.""ChargeCategoryId"" = CMC.""ChargeCategoryId"" AND CC.""Active"" IS TRUE
                            WHERE CMC.""ChargeModuleTemplateId"" = {chargeModuleTemplateId} AND CMC.""Active"" IS TRUE
                            ORDER BY CMC.""ChargeCategoryId"" ASC";
            return this.unitOfWork.Current.QueryAsync<Resource>(query);
        }

        public async Task<IEnumerable<Resource>> FetchChargeCategoriesWithTotalAsync(int packageModuleId, int chargeModuleTemplateId, int locationId)
        {
            var query = $@"SELECT DISTINCT CMC.""ChargeCategoryId"" AS ""Id"", CC.""ChargeCategoryName"" AS ""Name""
                            FROM ""ChargeModuleCategory"" CMC
                            JOIN ""ChargeCategory"" CC ON CC.""ChargeCategoryId"" = CMC.""ChargeCategoryId"" AND CC.""Active"" IS TRUE
                            WHERE CMC.""ChargeModuleTemplateId"" = {chargeModuleTemplateId} AND CMC.""Active"" IS TRUE
                            ORDER BY CMC.""ChargeCategoryId"" ASC";
            var chargeCategories = await this.unitOfWork.Current.QueryAsync<Resource>(query);

            query = $@"SELECT ""ModulesMasterId"", string_agg(""ReferenceId""::VARCHAR, ',') AS ""ChargeReferenceIds"" FROM ""PackageModuleDetail"" WHERE ""PackageModuleId"" = {packageModuleId} AND ""ReferenceId"" <> 0 GROUP BY ""ModulesMasterId"";";
            var packageDetails = await this.unitOfWork.Current.QueryAsync<PackageModuleDetailModel>(query);

            if (chargeCategories.Any() && packageDetails.Any())
            {
                foreach (var chargeCategory in chargeCategories)
                {
                    foreach (var item in packageDetails)
                    {
                        query = $@"SELECT ""ChargeModuleCategoryId"" FROM ""ChargeModuleCategory"" WHERE ""ChargeCategoryId"" = {chargeCategory.Id}
                                AND ""ModulesMasterId"" = {item.ModulesMasterId} AND ""ChargeModuleTemplateId"" = {chargeModuleTemplateId}";
                        var chargeModuleCategoryId = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<int>(query);

                        query = $@"SELECT coalesce(SUM(B.""Amount""), 0) FROM public.""udf_fetch_chargeModuleDetails""({chargeModuleCategoryId}, {locationId}) B
                        JOIN (SELECT PMD.* FROM ""PackageModuleDetail"" PMD
                        JOIN ""PackageModule"" PM ON PM.""PackageModuleId"" = PMD.""PackageModuleId""
                        LEFT JOIN ""udf_fetch_moduleChargeDetails""(PMD.""ModulesMasterId"", PM.""LocationId"") MCD
                        ON MCD.""ModulesMasterId"" = PMD.""ModulesMasterId""
                        AND MCD.""ReferenceId"" = PMD.""ReferenceId"") A
	                        ON A.""ModulesMasterId"" = B.""ModulesMasterId""
	                        AND A.""ReferenceId"" = B.""ReferenceId""
	                        AND A.""PackageModuleId"" = {packageModuleId}
                        WHERE B.""ReferenceId"" IN ({item.ChargeReferenceIds}) AND A.""IsFree"" IS FALSE AND B.""ModulesMasterId"" = {item.ModulesMasterId}";
                        item.Amount = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<decimal>(query);
                    }

                    query = $@"SELECT COALESCE(SUM(""Amount""), 0) FROM ""PackageModuleDetail"" WHERE ""PackageModuleId"" = {packageModuleId} AND ""ReferenceId"" = 0 ";
                    var nonChargeCategoryAmount = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<decimal>(query);
                    var total = packageDetails.Sum(m => m.Amount) + nonChargeCategoryAmount;
                    chargeCategory.Value = total.ToString();
                }
            }

            return chargeCategories;
        }

        public async Task<IEnumerable<ChargeModuleDetailsModel>> FetchChargeModuleDetailsAsync(ChargeModuleDetailsRequestModel request)
        {
            var chargeModules = await this.FetchChargeModulesAsync(request.PackageModuleId, request.ChargeCategoryId, request.ChargeModuleTemplateId, request.LocationId, request.ModulesMasterIds);
            var chargeModuleDetails = new List<ChargeModuleDetailsModel>();
            foreach (var item in chargeModules)
            {
                var query = $@"SELECT * FROM ""udf_fetch_chargeModuleDetails""({item.ChargeModuleCategoryId},{request.LocationId}) WHERE ""ModulesMasterId"" IN ({item.ModulesMasterId}) AND ""ReferenceId"" IN ({item.ChargeReferenceIds})";
                var data = await this.unitOfWork.Current.QueryAsync<ChargeModuleDetailsModel>(query);
                if (data.Any())
                    chargeModuleDetails.AddRange(data);
            }

            return chargeModuleDetails;
        }

        public async Task<int> AddAsync(PackageModuleModel model, List<PackageModuleDetailModel> packageModuleDetails)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            var checkIf = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<int>($@"Select count(*) from ""PackageModule"" where  lower(""PackageName"") = '{model.PackageName.ToLower()}'");
            if (checkIf > 0)
            {
                return -1;
            }
            // Package Module
            var packageModule = new PackageModule
            {
                Active = true,
                CreatedBy = model.UserAccountId,
                CreatedDate = DateTime.Now,
                PackageName = model.PackageName,
                PackageTypeId = model.PackageTypeId,
                ModuleTypeId = model.ModuleTypeId,
                ChargeModuleTemplateId = model.ChargeModuleTemplateId,
                ModulesMasterIds = model.ModulesMasterIds,
                LocationId = model.LocationId,
                ProviderId = model.ProviderId,
                Quantity = model.Quantity,
                FreeQuantity = model.FreeQuantity,
                Exclusions = model.Exclusions,
                ExpiresIn = model.ExpiresIn,
                Notes = model.Notes,
                DiscountType = model.DiscountType,
                DiscountAmount = model.DiscountAmount,
                DiscountPercentage = model.DiscountPercentage
            };

            packageModule.PackageModuleId = await this.unitOfWork.PackageModules.InsertAsync(packageModule, transaction);
            if (packageModule.PackageModuleId == 0)
            {
                transaction.Rollback();
                return 0;
            }

            // Package Module Details
            var failureCount = 0;
            foreach (var item in packageModuleDetails)
            {
                var packageModuleDetail = new PackageModuleDetail
                {
                    Active = true,
                    CreatedBy = model.UserAccountId,
                    CreatedDate = DateTime.Now,
                    ModulesMasterId = item.ModulesMasterId,
                    PackageModuleId = packageModule.PackageModuleId,
                    ReferenceId = item.ReferenceId,
                    Quantity = item.Quantity,
                    IsFree = item.IsFree,
                    Amount = item.Amount
                };

                packageModuleDetail.PackageModuleDetailId = await this.unitOfWork.PackageModuleDetails.InsertAsync(packageModuleDetail, transaction);
                if (packageModuleDetail.PackageModuleDetailId == 0)
                {
                    failureCount++;
                }
            }

            if (failureCount > 0)
            {
                transaction.Rollback();
                return 0;
            }

            transaction.Commit();
            return packageModule.PackageModuleId;
        }

        public async Task<int> UpdateAsync(PackageModuleModel model, List<PackageModuleDetailModel> packageModuleDetails)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            // Package Module
            var packageModule = await this.unitOfWork.PackageModules.FindAsync(m => m.PackageModuleId == model.PackageModuleId, transaction);
            if (packageModule == null || packageModule.PackageModuleId == 0)
            {
                transaction.Rollback();
                return 0;
            }

            var checkIf = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<int>($@"Select ""PackageModuleId"" from ""PackageModule"" where  lower(""PackageName"") = '{model.PackageName.ToLower()}' order by 1 desc limit 1");
            if (checkIf > 0)
            {
                if (packageModule.PackageModuleId != checkIf)
                {
                    return -1;
                }
            }

            packageModule.PackageName = model.PackageName;
            packageModule.PackageTypeId = model.PackageTypeId;
            packageModule.ModuleTypeId = model.ModuleTypeId;
            packageModule.ChargeModuleTemplateId = model.ChargeModuleTemplateId;
            packageModule.ModulesMasterIds = model.ModulesMasterIds;
            packageModule.LocationId = model.LocationId;
            packageModule.ProviderId = model.ProviderId;
            packageModule.FreeQuantity = model.FreeQuantity;
            packageModule.Quantity = model.Quantity;
            packageModule.Notes = model.Notes;
            packageModule.Exclusions = model.Exclusions;
            packageModule.ExpiresIn = model.ExpiresIn;
            packageModule.DiscountType = model.DiscountType;
            packageModule.DiscountAmount = model.DiscountAmount;
            packageModule.DiscountPercentage = model.DiscountPercentage;
            packageModule.ModifiedBy = model.UserAccountId;
            packageModule.ModifiedDate = DateTime.Now;

            var updated = await this.unitOfWork.PackageModules.UpdateAsync(packageModule, transaction);
            if (updated == 0)
            {
                transaction.Rollback();
                return 0;
            }

            var failureCount = 0;

            // Delete Package ModuleDetails
            var srcIds = await this.unitOfWork.Current.QueryAsync<int>($@"SELECT ""PackageModuleDetailId"" FROM ""PackageModuleDetail"" WHERE ""PackageModuleId"" = {model.PackageModuleId}", transaction);
            var destIds = packageModuleDetails.Select(m => m.PackageModuleDetailId).Where(m => m != 0);
            if (destIds.Any() && srcIds.Any())
            {
                var removedPackageModuleDetailIds = srcIds.Except(destIds);
                if (removedPackageModuleDetailIds.Any())
                {
                    var query = $@"DELETE FROM ""PackageModuleDetail"" WHERE ""PackageModuleDetailId"" IN ({string.Join(",", removedPackageModuleDetailIds)})";
                    var deleted = await this.unitOfWork.Current.ExecuteAsync(query, transaction);
                    if (deleted != removedPackageModuleDetailIds.Count())
                    {
                        transaction.Rollback();
                        return 0;
                    }
                }
            }

            // Add/Update Package Module Details
            foreach (var item in packageModuleDetails)
            {
                if (item.PackageModuleDetailId == 0)
                {
                    var packageModuleDetail = new PackageModuleDetail
                    {
                        Active = true,
                        CreatedBy = model.UserAccountId,
                        CreatedDate = DateTime.Now,
                        ReferenceId = item.ReferenceId,
                        ModulesMasterId = item.ModulesMasterId,
                        PackageModuleId = packageModule.PackageModuleId,
                        Quantity = item.Quantity,
                        IsFree = item.IsFree,
                        Amount = item.Amount
                    };

                    packageModuleDetail.PackageModuleDetailId = await this.unitOfWork.PackageModuleDetails.InsertAsync(packageModuleDetail, transaction);
                    if (packageModuleDetail.PackageModuleDetailId == 0)
                    {
                        failureCount++;
                    }
                }
                else
                {
                    var packageModuleDetail = await this.unitOfWork.PackageModuleDetails.FindAsync(m => m.PackageModuleDetailId == item.PackageModuleDetailId, transaction);
                    if (packageModuleDetail == null || packageModuleDetail.PackageModuleDetailId == 0)
                    {
                        failureCount++;
                    }
                    else if (this.IsPackageModuleDetailModified(packageModuleDetail, item))
                    {
                        packageModuleDetail.Quantity = item.Quantity;
                        packageModuleDetail.IsFree = item.IsFree;
                        packageModuleDetail.Amount = item.Amount;
                        packageModuleDetail.ReferenceId = item.ReferenceId;
                        packageModuleDetail.ModulesMasterId = item.ModulesMasterId;
                        packageModuleDetail.PackageModuleId = packageModule.PackageModuleId;
                        packageModuleDetail.ModifiedBy = model.UserAccountId;
                        packageModuleDetail.ModifiedDate = DateTime.Now;
                        updated = await this.unitOfWork.PackageModuleDetails.UpdateAsync(packageModuleDetail, transaction);
                        if (updated == 0)
                        {
                            failureCount++;
                        }
                    }
                }
            }

            if (failureCount > 0)
            {
                transaction.Rollback();
                return 0;
            }

            transaction.Commit();
            return 1;
        }

        public async Task<IEnumerable<PackageModuleDetail>> FetchPackageDetails(StringIdRequest model)
        {
            var records = await this.unitOfWork.PackageModuleDetails.FindAllAsync(x => x.PackageModuleId == model.MainId);

            var aptQuery = $@"select ""PackageModuleDetailId"",SUM(""UsedQuantity"") ""UsedQuantity"",SUM(""UsedCost"") ""UsedCost"" from ""ServiceOrder""
                        where ""AdmissionPackageId""=(select ""AdmissionPackageId"" from ""AdmissionPackage"" AP
                        join ""Appointment"" APT on APT.""AppointmentId""=AP.""AppointmentId""
                        where AP.""Active"" is true and APT.""PatientId""=(select ""PatientId"" from ""Appointment"" A where A.""AppointmentId""={model.SubMainId})
                        limit 1) AND ""Active"" IS TRUE group by ""PackageModuleDetailId""
                        union
                        select ""PackageModuleDetailId"",SUM(""UsedQuantity"") ""UsedQuantity"",SUM(""UsedCost"") ""UsedCost"" from ""LabServices""
                        where ""AdmissionPackageId""=(select ""AdmissionPackageId"" from ""AdmissionPackage"" AP
                        join ""Appointment"" APT on APT.""AppointmentId""=AP.""AppointmentId""
                        where AP.""Active"" is true and APT.""PatientId""=(select ""PatientId"" from ""Appointment"" A where A.""AppointmentId""={model.SubMainId})
                        limit 1) AND ""Active"" IS TRUE group by ""PackageModuleDetailId""
                        ";

            var admQuery = $@"select ""PackageModuleDetailId"",SUM(""UsedQuantity"") ""UsedQuantity"",SUM(""UsedCost"") ""UsedCost"" from ""ServiceOrder""
                        where ""AdmissionPackageId""=(select ""AdmissionPackageId"" from ""AdmissionPackage"" AP
                        join ""Admission"" APT on APT.""AdmissionId""=AP.""AdmissionId""
                        where AP.""Active"" is true and APT.""PatientId""=(select ""PatientId"" from ""Admission"" A where A.""AdmissionId""={model.SubMainId})
                        limit 1) AND ""Active"" IS TRUE group by ""PackageModuleDetailId""
                        union
                        select ""PackageModuleDetailId"",SUM(""UsedQuantity"") ""UsedQuantity"",SUM(""UsedCost"") ""UsedCost"" from ""LabServices""
                        where ""AdmissionPackageId""=(select ""AdmissionPackageId"" from ""AdmissionPackage"" AP
                        join ""Admission"" APT on APT.""AdmissionId""=AP.""AdmissionId""
                        where AP.""Active"" is true and APT.""PatientId""=(select ""PatientId"" from ""Admission"" A where A.""AdmissionId""={model.SubMainId})
                        limit 1) AND ""Active"" IS TRUE group by ""PackageModuleDetailId""
                        ";
            var query = model.IsAdmission == true ? admQuery : aptQuery;
            var usedRecords = await this.unitOfWork.Current.QueryAsync<ServiceOrderRecord>(query);
            if (usedRecords.Any())
            {
                foreach (var record in usedRecords)
                {
                    var found = records.FirstOrDefault(x => x.PackageModuleDetailId == record.PackageModuleDetailId);
                    if (found != null)
                        if (found.Quantity - record.UsedQuantity <= 0)
                            records = records.Where(x => x.PackageModuleDetailId != record.PackageModuleDetailId);
                        else
                            found.Quantity = (short)(found.Quantity - (record.UsedQuantity ?? 0));
                }
            }
            return records;
        }

        public async Task<IEnumerable<PackageModuleModel>> FetchAsync(PackageModuleFilterModel model)
        {
            var where = $@" WHERE 1 = 1 ";
            if (!string.IsNullOrEmpty(model.PackageName))
            {
                where += $@" AND PM.""PackageName"" ILIKE '%{model.PackageName}%'";
            }

            if (!string.IsNullOrEmpty(model.PackageType))
            {
                where += $@" AND PTL.""Name"" ILIKE '%{model.PackageType}%'";
            }

            if (!string.IsNullOrEmpty(model.ModuleType))
            {
                where += $@" AND MTL.""Name"" ILIKE '%{model.ModuleType}%'";
            }

            if (model.LocationId != null)
            {
                where += $@" AND PM.""LocationId"" = '{model.LocationId}'";
            }

            var query = $@"SELECT COUNT(*) OVER () AS ""TotalItems"", PM.*, PM.""ChargeModuleTemplateId"", CMT.""TemplateName"", CMT.""StartDate"", CMT.""EndDate"",
                    LC.""Name"" AS ""LocationName"", PRA.""FullName"" AS ""PracticeName"", PR.""FullName"" AS ""ProviderName"", DPR.""DepartmentName"",
                    PTL.""Name"" AS ""PackageTypeName"", MTL.""Name"" AS ""ModuleTypeName"", CRT.""FullName"" AS ""CreatedByName"", MDF.""FullName"" AS ""ModifiedName"",
                    (SELECT CASE WHEN COUNT(*) > 0 THEN TRUE ELSE FALSE END FROM ""AdmissionPackage"" WHERE ""AdmissionPackage"".""Active"" IS TRUE AND ""AdmissionPackage"".""PackageId"" = PM.""PackageModuleId"") AS ""IsInUse"",
			        (select ""PackageDocumentId"" from ""PackageDocument"" where ""PackageModuleId""=PM.""PackageModuleId"" LIMIT 1 )
                    FROM ""PackageModule"" PM
                    JOIN ""Location"" LC ON LC.""LocationId"" = PM.""LocationId"" AND LC.""Active"" IS TRUE
                    JOIN ""Practice"" PRA ON PRA.""PracticeId"" = LC.""PracticeId"" AND PRA.""Active"" IS TRUE
                    JOIN ""LookupValue"" PTL ON PTL.""LookupValueId"" = PM.""PackageTypeId""
                    JOIN ""LookupValue"" MTL ON MTL.""LookupValueId"" = PM.""ModuleTypeId""
                    JOIN ""ChargeModuleTemplate"" CMT ON CMT.""ChargeModuleTemplateId"" = PM.""ChargeModuleTemplateId"" AND CMT.""Active"" IS TRUE
                    JOIN ""Account"" CRT ON CRT.""AccountId"" = PM.""CreatedBy"" AND CRT.""Active"" IS TRUE
                    LEFT JOIN ""Account"" MDF ON MDF.""AccountId"" = PM.""ModifiedBy"" AND MDF.""Active"" IS TRUE
                    LEFT JOIN ""Provider"" PR ON PR.""ProviderId"" = PM.""ProviderId"" AND PR.""Active"" IS TRUE
                    LEFT JOIN ""Department"" DPR ON DPR.""DepartmentId"" = PR.""DepartmentId"" AND DPR.""Active"" IS TRUE
                    {where}
                    ORDER BY PM.""PackageModuleId"" DESC";

            if (!model.DisablePagination)
            {
                model.PageIndex--;
                query += " LIMIT " + model.PageSize + " offset " + (model.PageIndex * model.PageSize);
            }

            var packages = await this.unitOfWork.Current.QueryAsync<PackageModuleModel>(query);
            if (model.ChargeCategoryId > 0)
            {
                foreach (var item in packages)
                {
                    var response = await this.GetChargeCategoryTotalAsync((int)model.ChargeCategoryId, item.PackageModuleId, item.LocationId, item.ChargeModuleTemplateId, item.ModulesMasterIds);
                    if (response != null)
                    {
                        decimal discount = 0;
                        if (!string.IsNullOrEmpty(item.DiscountType))
                        {
                            if (item.DiscountType == "P")
                            {
                                discount = response.Item1 * ((item.DiscountPercentage ?? 0) * 0.01M);
                            }
                            else
                            {
                                discount = item.DiscountAmount ?? 0;
                            }
                        }
                        item.TotalAfterDiscount = response.Item1 - discount;
                        item.Total = response.Item1;
                        item.ChargeModuleCategoryIds = response.Item2;
                    }
                
                }
            }

            return packages;
        }

        public async Task<PackageModuleViewModel> ViewAsync(int packageModuleId)
        {
            // Package Module
            var query = $@"SELECT PM.*, PM.""ChargeModuleTemplateId"", CMT.""TemplateName"", CMT.""StartDate"", CMT.""EndDate"", CRT.""FullName"" AS ""CreatedByName"",
                    MDF.""FullName"" AS ""ModifiedName"", LC.""Name"" AS ""LocationName"", PR.""FullName"" AS ""ProviderName"", DPR.""DepartmentName"",
                    PTL.""Name"" AS ""PackageTypeName"", MTL.""Name"" AS ""ModuleTypeName""
                    FROM ""PackageModule"" PM
                    JOIN ""Location"" LC ON LC.""LocationId"" = PM.""LocationId"" AND LC.""Active"" IS TRUE
                    JOIN ""ChargeModuleTemplate"" CMT ON CMT.""ChargeModuleTemplateId"" = PM.""ChargeModuleTemplateId"" AND CMT.""Active"" IS TRUE
                    JOIN ""LookupValue"" PTL ON PTL.""LookupValueId"" = PM.""PackageTypeId""
                    JOIN ""LookupValue"" MTL ON MTL.""LookupValueId"" = PM.""ModuleTypeId""
                    JOIN ""Account"" CRT ON CRT.""AccountId"" = PM.""CreatedBy"" AND CRT.""Active"" IS TRUE
                    LEFT JOIN ""Account"" MDF ON MDF.""AccountId"" = PM.""ModifiedBy"" AND MDF.""Active"" IS TRUE
                    LEFT JOIN ""Provider"" PR ON PR.""ProviderId"" = PM.""ProviderId"" AND PR.""Active"" IS TRUE
                    LEFT JOIN ""Department"" DPR ON DPR.""DepartmentId"" = PR.""DepartmentId"" AND DPR.""Active"" IS TRUE
                    WHERE PM.""PackageModuleId"" = {packageModuleId}";
            var packageModule = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<PackageModuleModel>(query);

            var model = new PackageModuleViewModel();
            if (packageModule.PackageModuleId > 0)
            {
                model.PackageModule = packageModule;

                // Charge Modules
                query = $@"SELECT DISTINCT MM.""ModulesMasterId"", MM.""ModuleName"", MM.""ModuleIcon"", MM.""PackageType"", MM.""IsChargeCategoryApplicable"", MM.""IsPackageApplicable""
                            FROM ""ModulesMaster"" MM
                            WHERE MM.""ModulesMasterId"" IN ({model.PackageModule.ModulesMasterIds})
                            ORDER BY MM.""ModuleName"" ASC";
                var modules = await this.unitOfWork.Current.QueryAsync<ModuleModel>(query);
                if (modules.Any())
                {
                    model.Modules = modules.ToList();
                }

                // Package Details
                query = $@"SELECT PMD.*, MCD.* FROM ""PackageModuleDetail"" PMD
                        JOIN ""PackageModule"" PM ON PM.""PackageModuleId"" = PMD.""PackageModuleId""
                        LEFT JOIN ""udf_fetch_moduleChargeDetails""(PMD.""ModulesMasterId"", PM.""LocationId"") MCD
	                        ON MCD.""ModulesMasterId"" = PMD.""ModulesMasterId""
	                        AND MCD.""ReferenceId"" = PMD.""ReferenceId""
                        WHERE PMD.""PackageModuleId"" = {model.PackageModule.PackageModuleId}";
                var packageDetails = await this.unitOfWork.Current.QueryAsync<PackageModuleDetailModel>(query);
                if (packageDetails.Any())
                {
                    model.PackageModuleDetails = packageDetails.ToList();
                }
            }

            return model;
        }

        public async Task<int> DeleteAsync(int packageModuleId)
        {
            var packageModule = await this.unitOfWork.PackageModules.FindAsync(m => m.PackageModuleId == packageModuleId);
            if (packageModule == null || packageModule.PackageModuleId == 0)
                return 0;

            var usedCount = await this.unitOfWork.AdmissionPackage.CountAsync(m => m.PackageId == packageModule.PackageModuleId && m.Active);
            if (usedCount > 0)
                return -1;

            return await this.unitOfWork.PackageModules.DeleteAsync(packageModule) ? 1 : 0;
        }

        public async Task<int> DisableAsync(int packageModuleId, int disabledBy)
        {
            var packageModule = await this.unitOfWork.PackageModules.FindAsync(m => m.PackageModuleId == packageModuleId);
            if (packageModule == null || packageModule.PackageModuleId == 0)
                return 0;

            var usedCount = await this.unitOfWork.AdmissionPackage.CountAsync(m => m.PackageId == packageModule.PackageModuleId && m.Active);
            if (usedCount > 0)
                return -1;

            packageModule.Active = false;
            packageModule.ModifiedBy = disabledBy;
            packageModule.ModifiedDate = DateTime.Now;
            return await this.unitOfWork.PackageModules.UpdateAsync(packageModule);
        }

        public async Task<int> EnableAsync(int packageModuleId, int enabledBy)
        {
            var packageModule = await this.unitOfWork.PackageModules.FindAsync(m => m.PackageModuleId == packageModuleId);
            if (packageModule == null || packageModule.PackageModuleId == 0)
                return 0;

            packageModule.Active = true;
            packageModule.ModifiedBy = enabledBy;
            packageModule.ModifiedDate = DateTime.Now;
            return await this.unitOfWork.PackageModules.UpdateAsync(packageModule);
        }

        public async Task<Tuple<decimal, string, decimal>> GetChargeCategoryTotalAsync(int chargeCategoryId, int packageModuleId, int locationId, int chargeModuleTemplateId, string modulesMasterIds)
        {
            var chargeModules = await this.FetchChargeModulesAsync(packageModuleId, chargeCategoryId, chargeModuleTemplateId, locationId, modulesMasterIds);
            var chargeModuleCategoryCost = new ChargeModuleCategoryCostModel
            {
                ChargeCategoryId = chargeCategoryId,
                ChargeModuleTemplateId = chargeModuleTemplateId,
                LocationId = locationId,
                PackageModuleId = packageModuleId,
                ChargeModules = chargeModules
            };

            var query = $@"SELECT COALESCE(SUM(""Amount""), 0) FROM ""PackageModuleDetail"" WHERE ""PackageModuleId"" = {packageModuleId} AND ""ReferenceId"" = 0 ";
            var nonChargeApplicableAmount = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<decimal>(query);
            chargeModuleCategoryCost.Total = chargeModules.Sum(m => m.Amount) + nonChargeApplicableAmount;
            var discountAmount = 0M;
            if(chargeModuleCategoryCost.Total > 0)
            {
                var packageModule = await this.unitOfWork.PackageModules.FindAsync(x => x.PackageModuleId == packageModuleId);
                if (packageModule != null && packageModule.DiscountType != null)
                {
                    discountAmount = packageModule.DiscountType == "A" ? (packageModule.DiscountAmount ?? 0)
                    : (chargeModuleCategoryCost.Total * ((packageModule.DiscountPercentage ?? 0) * 0.01M));
                }
            }
            return new Tuple<decimal, string, decimal>(chargeModuleCategoryCost.Total, string.Join(',', chargeModuleCategoryCost.ChargeModules.Select(m => m.ChargeModuleCategoryId)), discountAmount);
        }

        public async Task<decimal> GetCounsellingTotalAsync(int counsellingId)
        {
            var counselling = await this.unitOfWork.Counsellings.FindAsync(x => x.CounsellingId == counsellingId);
            return counselling.Total;
        }

        private bool IsPackageModuleDetailModified(PackageModuleDetail src, PackageModuleDetailModel dest)
        {
            var modifiedCount = new List<bool>();

            if (src.Quantity != dest.Quantity)
            {
                modifiedCount.Add(true);
            }

            if (src.IsFree != dest.IsFree)
            {
                modifiedCount.Add(true);
            }

            if (src.Amount != dest.Amount)
            {
                modifiedCount.Add(true);
            }

            return modifiedCount.Select(m => m).Count() > 0;
        }

        private async Task<List<ChargeModuleCostModel>> FetchChargeModulesAsync(int packageModuleId, int chargeCategoryId, int chargeModuleTemplateId, int locationId, string modulesMasterIds)
        {
            var chargeModules = new List<ChargeModuleCostModel>();

            var mmIds = modulesMasterIds.IndexOf(",") > 0 ? modulesMasterIds.Split(",").Select(m => Convert.ToInt32(m)) : new List<int> { Convert.ToInt32(modulesMasterIds) };
            foreach (var mmId in mmIds)
            {
                var chargeModule = new ChargeModuleCostModel { ModulesMasterId = mmId };

                var query = $@"SELECT string_agg(""ReferenceId""::text, ',') FROM ""PackageModuleDetail"" WHERE ""PackageModuleId"" = {packageModuleId} AND ""ModulesMasterId"" = {chargeModule.ModulesMasterId} AND ""ReferenceId"" <> 0 ";
                chargeModule.ChargeReferenceIds = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<string>(query);

                query = $@"SELECT ""ChargeModuleCategoryId"" FROM ""ChargeModuleCategory"" WHERE ""ChargeCategoryId"" = {chargeCategoryId} AND ""ModulesMasterId"" = {chargeModule.ModulesMasterId}  AND ""ChargeModuleTemplateId"" = {chargeModuleTemplateId}";
                chargeModule.ChargeModuleCategoryId = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<int>(query);

                if (chargeModule.ChargeModuleCategoryId > 0 && !string.IsNullOrEmpty(chargeModule.ChargeReferenceIds))
                {
                    query = $@"SELECT coalesce(SUM(B.""Amount""), 0) FROM public.""udf_fetch_chargeModuleDetails""({chargeModule.ChargeModuleCategoryId}, {locationId}) B
                        JOIN (SELECT PMD.* FROM ""PackageModuleDetail"" PMD
                        JOIN ""PackageModule"" PM ON PM.""PackageModuleId"" = PMD.""PackageModuleId""
                        LEFT JOIN ""udf_fetch_moduleChargeDetails""(PMD.""ModulesMasterId"", PM.""LocationId"") MCD
                        ON MCD.""ModulesMasterId"" = PMD.""ModulesMasterId""
                        AND MCD.""ReferenceId"" = PMD.""ReferenceId"") A
	                        ON A.""ModulesMasterId"" = B.""ModulesMasterId""
	                        AND A.""ReferenceId"" = B.""ReferenceId""
	                        AND A.""PackageModuleId"" = {packageModuleId}
                        WHERE B.""ReferenceId"" IN ({chargeModule.ChargeReferenceIds}) AND A.""IsFree"" IS FALSE AND B.""ModulesMasterId"" = {chargeModule.ModulesMasterId}";
                    chargeModule.Amount = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<decimal>(query);
                    chargeModules.Add(chargeModule);
                }
            }

            return chargeModules;
        }

        public async Task<IEnumerable<int>> AddPackageDocumentAsync(IEnumerable<PackageDocumentModel> model)
        {

            var records = model.Select(item => new PackageDocument
            {
                PackageModuleId = item.PackageModuleId,
                UploadedBy = item.UploadedBy,
                DocumentName = item.DocumentName,
                ContentType = item.ContentType,
                Size = item.Size,
                DocumentUrl = item.DocumentUrl,
                ThumbnailUrl = item.ThumbnailUrl,
                UploadedDate = DateTime.UtcNow
            });
            var responseIds = new List<int>();
            foreach (var document in records)
            {
                var response = await this.unitOfWork.PackageDocuments.InsertAsync(document);
                responseIds.Add(response);
            }
            return responseIds;
        }

        public Task<IEnumerable<PackageDocumentModel>> FetchPackageDocuments(int PackageModuleId)
        {
            var where = string.Empty;
            if (PackageModuleId != 0)
            {
                where = $@" WHERE  ""PackageModuleId"" = {PackageModuleId}";
            }
            var query = $@"SELECT COUNT(*) OVER () AS ""TotalItems"", ""PackageModuleId"", ""PackageDocumentId"", ""DocumentName"",  ""ContentType"", ""Size"",
                                ""UploadedDate"", ""UploadedBy"",
                                (CASE WHEN ""DocumentUrl"" IS NOT NULL THEN CONCAT('{this.runningEnvironment.CurrentEnvironment}', '/PackageDocuments/',""PackageModuleId"", '/', ""DocumentUrl"") ELSE NULL END) AS ""DocumentUrl""
                                FROM ""PackageDocument"" 
                                {where} ORDER BY ""PackageModuleId"" DESC";
            return this.unitOfWork.Current.QueryAsync<PackageDocumentModel>(query);
        }
    }
}