using Dapper;
using Hims.Domain.Entities;
using Hims.Domain.Repositories.UnitOfWork;
using Hims.Domain.Services;
using Hims.Shared.DataFilters;
using Hims.Shared.EntityModels;
using Hims.Shared.UserModels.Counselling;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Hims.Infrastructure.Services
{
    public class CounsellingServices : ICounsellingService
    {
        private readonly IUnitOfWork unitOfWork;

        public CounsellingServices(IUnitOfWork unitOfWork)
        {
            this.unitOfWork = unitOfWork;
        }

        public async Task<int> AddAsync(CounsellingModel model, List<CounsellingDetailModel> counsellingDetails)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            var counselling = await this.unitOfWork.Counsellings.FindAsync(m => m.PatientId == model.PatientId && m.Active, transaction);
            if (counselling != null && counselling.CounsellingId != 0)
            {
                transaction.Rollback();
                return -1;
            }

            // Counselling
            counselling = new Counselling
            {
                Active = true,
                CreatedBy = model.CreatedBy,
                CreatedDate = DateTime.UtcNow.AddMinutes(330),
                IsDraft = model.IsDraft,
                CounsellingTypeId = model.CounsellingTypeId,
                PayTypeId = model.PayTypeId,
                InsuranceCompanyId = model.InsuranceCompanyId,
                TPAId = model.TPAId,
                PatientOrganization = model.PatientOrganization,
                Discount = model.Discount,
                PackageDiscount = model.PackageDiscount ?? 0,
                DiscountAmount = model.DiscountAmount,
                DiscountPercentage = model.DiscountPercentage,
                DiscountType = model.DiscountType,
                LocationId = model.LocationId,
                PackageModuleId = model.PackageModuleId,
                PatientId = model.PatientId,
                TotalQuantity = model.TotalQuantity,
                SubTotal = model.SubTotal ?? 0,
                Total = model.Total ?? 0,
                CounsellingNo = await this.GetCounsellingNoAsync(),
                ChargeCategoryId = model.ChargeCategoryId,
                IsInUse = false
            };
            counselling.CounsellingId = await this.unitOfWork.Counsellings.InsertAsync(counselling, transaction);
            if (counselling.CounsellingId == 0)
            {
                transaction.Rollback();
                return 0;
            }

            // Counselling Details
            foreach (var item in counsellingDetails)
            {
                var counsellingDetail = new CounsellingDetail
                {
                    CounsellingId = counselling.CounsellingId,
                    Active = true,
                    CreatedBy = counselling.CreatedBy,
                    CreatedDate = DateTime.UtcNow.AddMinutes(330),
                    PackageModuleDetailId = item.PackageModuleDetailId,
                    Amount = item.Amount ?? 0,
                    IsFree = item.IsFree,
                    Quantity = item.Quantity
                };
                    counsellingDetail.CounsellingDetailId = await this.unitOfWork.CounsellingDetails.InsertAsync(counsellingDetail, transaction);
                if (counsellingDetail.CounsellingDetailId == 0)
                {
                    transaction.Rollback();
                    return 0;
                }
            }

            transaction.Commit();
            return counselling.CounsellingId;
        }

        public async Task<int> UpdateAsync(CounsellingModel model, List<CounsellingDetailModel> counsellingDetails)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            var counselling = await this.unitOfWork.Counsellings.FindAsync(m => m.PatientId == model.PatientId && m.Active && m.CounsellingId != model.CounsellingId, transaction);
            if (counselling != null && counselling.CounsellingId != 0)
            {
                transaction.Rollback();
                return -1;
            }

            // Counselling
            counselling = await this.unitOfWork.Counsellings.FindAsync(m => m.CounsellingId == model.CounsellingId, transaction);
            counselling.ModifiedBy = model.ModifiedBy;
            counselling.ModifiedDate = model.ModifiedDate;
            counselling.IsDraft = model.IsDraft;
            counselling.PackageModuleId = model.PackageModuleId;
            counselling.ChargeCategoryId = model.ChargeCategoryId;
            counselling.CounsellingTypeId = model.CounsellingTypeId;
            counselling.PayTypeId = model.PayTypeId;
            counselling.InsuranceCompanyId = model.InsuranceCompanyId;
            counselling.TPAId = model.TPAId;
            counselling.PatientOrganization = model.PatientOrganization;
            counselling.PatientId = model.PatientId;
            counselling.LocationId = model.LocationId;
            counselling.TotalQuantity = model.TotalQuantity;
            counselling.SubTotal = model.SubTotal ?? 0;
            counselling.Total = model.Total ?? 0;
            counselling.PackageDiscount = model.PackageDiscount ?? 0;
            counselling.Discount = model.Discount;
            counselling.DiscountAmount = model.DiscountAmount;
            counselling.DiscountPercentage = model.DiscountPercentage;
            counselling.DiscountType = model.DiscountType;

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

            // Counselling Details

            transaction.Commit();
            return updated;
        }

        public async Task<int> UpdateSignaturesAsync(int counsellingId, string signaturePath, bool isPatient)
        {
            var counselling = await this.unitOfWork.Counsellings.FindAsync(m => m.CounsellingId == counsellingId);
            if (isPatient) counselling.PatientSignature = signaturePath;
            if (!isPatient) counselling.CounsellorSignature = signaturePath;
            return await this.unitOfWork.Counsellings.UpdateAsync(counselling);
        }

        public async Task<int> DeleteAsync(int counsellingId)
        {
            var counselling = await this.unitOfWork.Counsellings.FindAsync(m => m.CounsellingId == counsellingId);
            if (counselling == null || counselling.CounsellingId == 0)
                return 0;
            if (counselling.IsInUse)
                return -1;

            return await this.unitOfWork.Counsellings.DeleteAsync(counselling) ? 1 : 0;
        }

        public async Task<int> PublishAsync(int counsellingId, int publishedBy)
        {
            var counselling = await this.unitOfWork.Counsellings.FindAsync(m => m.CounsellingId == counsellingId);
            if (counselling == null || counselling.CounsellingId == 0)
                return 0;

            counselling.IsDraft = false;
            counselling.ModifiedBy = publishedBy;
            counselling.ModifiedDate = DateTime.UtcNow.AddMinutes(330);
            return await this.unitOfWork.Counsellings.UpdateAsync(counselling);
        }

        public async Task<int> DisableAsync(int counsellingId, int disabledBy)
        {
            var counselling = await this.unitOfWork.Counsellings.FindAsync(m => m.CounsellingId == counsellingId);
            if (counselling == null || counselling.CounsellingId == 0)
                return 0;

            counselling.Active = false;
            counselling.ModifiedBy = disabledBy;
            counselling.ModifiedDate = DateTime.UtcNow.AddMinutes(330);
            return await this.unitOfWork.Counsellings.UpdateAsync(counselling);
        }

        public async Task<int> EnableAsync(int counsellingId, int enabledBy)
        {
            var counselling = await this.unitOfWork.Counsellings.FindAsync(m => m.CounsellingId == counsellingId);
            if (counselling == null || counselling.CounsellingId == 0)
                return 0;


            var existingCounselling = await this.unitOfWork.Counsellings.FindAsync(m => m.PatientId == counselling.PatientId && m.Active && m.CounsellingId != counsellingId);
            if (existingCounselling != null && existingCounselling.CounsellingId != 0)
                return -1;

            counselling.Active = true;
            counselling.ModifiedBy = enabledBy;
            counselling.ModifiedDate = DateTime.UtcNow.AddMinutes(330);
            return await this.unitOfWork.Counsellings.UpdateAsync(counselling);
        }

        public Task<IEnumerable<CounsellingModel>> FetchAsync(int patientId)
        {
            var query = $@"SELECT c.*, clt.""Name"" as ""CounsellingTypeName"", cc.""ChargeCategoryName"", l.""Name"" AS ""LocationName"", pm.""PackageName"",
            pt.""Name"" AS ""PackageTypeName"", mt.""Name"" AS ""ModuleTypeName"", pm.""ChargeModuleTemplateId"", cmt.""TemplateName"",
            cmt.""StartDate"", cmt.""EndDate"", pat.""FullName"" AS ""PatientName"", pat.""UMRNo"", pat.""Gender"" AS ""PatientGender"",
            crt.""RoleId"" AS ""CreatedByRoleId"", crt.""FullName"" AS ""CreatedByName"", mdf.""FullName"" AS ""ModifiedByName""
            FROM ""Counselling"" c
            JOIN ""LookupValue"" clt ON clt.""LookupValueId"" = c.""CounsellingTypeId""
            JOIN ""ChargeCategory"" cc ON cc.""ChargeCategoryId"" = c.""ChargeCategoryId"" AND cc.""Active"" IS TRUE
            JOIN ""Location"" l ON l.""LocationId"" = c.""LocationId"" AND l.""Active"" IS TRUE
            JOIN ""PackageModule"" pm ON pm.""PackageModuleId"" = c.""PackageModuleId"" AND pm.""Active"" IS TRUE
            JOIN ""LookupValue"" pt ON pt.""LookupValueId"" = pm.""PackageTypeId""
            JOIN ""LookupValue"" mt ON mt.""LookupValueId"" = pm.""ModuleTypeId""
            JOIN ""ChargeModuleTemplate"" cmt ON cmt.""ChargeModuleTemplateId"" = pm.""ChargeModuleTemplateId"" AND cmt.""Active"" IS TRUE
            JOIN ""Patient"" pat ON pat.""PatientId"" = c.""PatientId"" AND pat.""Active"" IS TRUE
            JOIN ""Account"" crt ON crt.""AccountId"" = c.""CreatedBy"" AND crt.""Active"" IS TRUE
            LEFT JOIN ""Account"" mdf ON mdf.""AccountId"" = c.""ModifiedBy"" AND mdf.""Active"" IS TRUE
            WHERE c.""PatientId"" = {patientId}";
            return this.unitOfWork.Current.QueryAsync<CounsellingModel>(query);
        }

        public Task<IEnumerable<CounsellingBasicModel>> FetchBasicsAsync(PackageTypeModel model)
        {
            var query = $@"select 
	                c.""CounsellingId"",
	                c.""CounsellingNo"",
	                c.""PackageModuleId"",
	                c.""ChargeCategoryId"",
	                c.""PatientId"",
	                c.""PayTypeId"",
	                c.""InsuranceCompanyId"",
	                c.""TPAId"",
	                c.""PatientOrganization""
                from ""Counselling"" c
                JOIN ""PackageModule"" PM on PM.""PackageModuleId"" = c.""PackageModuleId""
                JOIN ""LookupValue"" PTL ON PTL.""LookupValueId"" = PM.""PackageTypeId""
                WHERE c.""IsInUse"" IS FALSE AND c.""IsDraft"" IS FALSE AND c.""Active"" IS TRUE AND PTL.""Name"" ILIKE '%{model.Type}%' AND PM.""LocationId"" = {model.LocationId}";
            return this.unitOfWork.Current.QueryAsync<CounsellingBasicModel>(query);
        }

        public Task<CounsellingModel> FindAsync(int counsellingId)
        {
            var where = $@" WHERE 1 = 1 AND c.""CounsellingId"" = {counsellingId}";
            var query = $@"SELECT c.*, clt.""Name"" as ""CounsellingTypeName"", cc.""ChargeCategoryName"", l.""Name"" AS ""LocationName"", pm.""PackageName"",
            pt.""Name"" AS ""PackageTypeName"", mt.""Name"" AS ""ModuleTypeName"", pm.""ChargeModuleTemplateId"", cmt.""TemplateName"",
            cmt.""StartDate"", cmt.""EndDate"", pat.""FullName"" AS ""PatientName"", pat.""UMRNo"", pat.""Gender"" AS ""PatientGender"",
            atp.""AdmissionPayTypeName"" as ""PayTypeName"", ins.""FullName"" as ""InsuranceCompanyName"", tpa.""Name"" as ""TPAName"",
            crt.""RoleId"" AS ""CreatedByRoleId"", crt.""FullName"" AS ""CreatedByName"", mdf.""FullName"" AS ""ModifiedByName""
            FROM ""Counselling"" c
            JOIN ""LookupValue"" clt ON clt.""LookupValueId"" = c.""CounsellingTypeId""
            JOIN ""ChargeCategory"" cc ON cc.""ChargeCategoryId"" = c.""ChargeCategoryId"" AND cc.""Active"" IS TRUE
            JOIN ""Location"" l ON l.""LocationId"" = c.""LocationId"" AND l.""Active"" IS TRUE
            JOIN ""PackageModule"" pm ON pm.""PackageModuleId"" = c.""PackageModuleId"" AND pm.""Active"" IS TRUE
            JOIN ""LookupValue"" pt ON pt.""LookupValueId"" = pm.""PackageTypeId""
            JOIN ""LookupValue"" mt ON mt.""LookupValueId"" = pm.""ModuleTypeId""
            JOIN ""ChargeModuleTemplate"" cmt ON cmt.""ChargeModuleTemplateId"" = pm.""ChargeModuleTemplateId"" AND cmt.""Active"" IS TRUE
            JOIN ""Patient"" pat ON pat.""PatientId"" = c.""PatientId"" AND pat.""Active"" IS TRUE
            LEFT JOIN ""AdmissionPayType"" atp ON atp.""AdmissionPayTypeId"" = c.""PayTypeId"" AND atp.""Active"" IS TRUE
            LEFT JOIN ""InsuranceCompany"" ins ON ins.""InsuranceCompanyId"" = c.""PayTypeId"" AND ins.""Active"" IS TRUE
            LEFT JOIN ""Tpa"" tpa ON tpa.""TpaId"" = c.""TPAId"" AND tpa.""Active"" IS TRUE
            JOIN ""Account"" crt ON crt.""AccountId"" = c.""CreatedBy"" AND crt.""Active"" IS TRUE
            LEFT JOIN ""Account"" mdf ON mdf.""AccountId"" = c.""ModifiedBy"" AND mdf.""Active"" IS TRUE
            {where}";
            return this.unitOfWork.Current.QueryFirstOrDefaultAsync<CounsellingModel>(query);
        }

        public Task<IEnumerable<CounsellingDetailModel>> FetchDetailsAsync(int counsellingId)
        {
            var where = $@" WHERE 1 = 1 AND cd.""CounsellingId"" = {counsellingId}";
            var query = $@"SELECT cd.* FROM ""CounsellingDetail"" cd {where}";
            return this.unitOfWork.Current.QueryAsync<CounsellingDetailModel>(query);
        }

        public Task<CounsellingPatientModel> FindPatientInfoAsync(int patientId)
        {
            var query = $@"SELECT pat.""FullName"" AS ""PatientName"", pat.""UMRNo"", pat.""Gender"" AS ""PatientGender""
                        FROM ""Patient"" pat
                        WHERE pat.""PatientId"" = {patientId} AND pat.""Active"" IS TRUE";
            return this.unitOfWork.Current.QueryFirstOrDefaultAsync<CounsellingPatientModel>(query);
        }

        private async Task<string> GetCounsellingNoAsync()
        {
            // Ex: CL20010005
            var type = "CL";
            var admissionNumber = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<string>($@"SELECT ""CounsellingNo"" FROM ""Counselling"" where ""CounsellingNo"" ILIKE '%{type}%' ORDER BY ""CounsellingId"" DESC");
            return CoreFilter.GetTransactionId(admissionNumber, type);
        }
    }
}
