using System.Collections.Generic;
using System.Data;
using System.Linq;
using Dapper;
using Hims.Domain.Entities;
using Hims.Shared.UserModels.FinalBill;
using ServiceOrderPayload = Hims.Shared.UserModels.FinalBill.ServiceOrder;
using ServicePackage = Hims.Shared.UserModels.ServiceOrder.Package;

namespace Hims.Infrastructure.Services
{
    using System;
    using System.Threading.Tasks;
    using Hims.Domain.Entities.Enums;
    using Hims.Domain.Repositories.UnitOfWork;
    using Hims.Domain.Services;
    using Hims.Shared.UserModels.Common;
    using Hims.Shared.UserModels.Laboratory;
    using Hims.Shared.UserModels.Labs;
    using Hims.Shared.UserModels.Slots;

    /// <summary> The chat service.</summary>
    public class FinalBillService : IFinalBillService
    {
        /// <summary>
        /// The unit of work.
        /// </summary>
        private readonly IUnitOfWork unitOfWork;
        private readonly IAdmissionService admissionsServices;
        /// <inheritdoc cref="IFinalBillService" />
        public FinalBillService(IUnitOfWork unitOfWork, IAdmissionService admissionsServices)
        {
            this.unitOfWork = unitOfWork;
            this.admissionsServices = admissionsServices;
        }

        public async Task<BasicViewModel> GetBasicAsync(FilterModel model)
        {
            var record = model.IsAdmission
                ? await this.unitOfWork.FinalBill.FindAsync(x => x.AdmissionId == model.AdmissionId && x.Active == true)
                : await this.unitOfWork.FinalBill.FindAsync(x => x.AppointmentId == model.AdmissionId && x.Active == true);
            if (record == null) return new BasicViewModel();

            var checkservice = model.IsAdmission
                ? await this.unitOfWork.ServiceOrder.FindAsync(x => x.AdmissionId == model.AdmissionId && x.Active == true)
                : await this.unitOfWork.ServiceOrder.FindAsync(x => x.AppointmentId == model.AdmissionId && x.Active == true);
            var checkFinalGatePass = await this.unitOfWork.GatePass.FindAsync(x => x.AdmissionId == model.AdmissionId && x.TypeId == (int)GatePassType.Final);
            var checkProvisionalGatePass = await this.unitOfWork.GatePass.FindAsync(x => x.AdmissionId == model.AdmissionId && x.TypeId == (int)GatePassType.Provisional);


            return new BasicViewModel
            {
                IsFinalBillGenerated = true,
                TotalAmount = record.TotalAmount,
                DiscountTypeId = (int?)record.DiscountTypeId,
                DiscountDetails = record.DiscountDetails,
                FinalAmount = record.FinalAmount,
                IsServiceTaken = checkservice == null ? false : true,
                IsFinalGatePass = checkFinalGatePass == null ? false : true,
                IsProvisionalGatePass = checkProvisionalGatePass == null ? false : true
            };

        }

        public async Task<DateTime?> IsDischargedAsync(FilterModel model)
        {
            var record = await this.unitOfWork.Discharge.FindAsync(x => x.AdmissionId == model.AdmissionId && x.Active == true);
            return record?.CreatedDate;
        }

        private async Task<int> DeactivateExistingAsync(int admissionId, bool isAdmission, IDbTransaction transaction)
        {
            var existingRecords = isAdmission
                ? await this.unitOfWork.FinalBill.FindAllAsync(x =>
                    x.Active == true && x.AdmissionId == admissionId, transaction)
                : await this.unitOfWork.FinalBill.FindAllAsync(x =>
                    x.Active == true && x.AppointmentId == admissionId, transaction);
            foreach (var record in existingRecords)
            {
                record.Active = false;
                var isUpdated = await this.unitOfWork.FinalBill.UpdateAsync(record, transaction);
                if (isUpdated > 0) continue;
                return -1;
            }

            return 1;
        }

        public async Task<int> GetDefaultChargeId()
        {
            var record = await this.unitOfWork.ChargeCategorys.FindAsync(x => x.Default == true);
            return record.ChargeCategoryId;
        }

        public async Task<ViewModel> FetchAsync(FilterModel model)
        {
            var record = model.IsAdmission
                ? await this.unitOfWork.FinalBill.FindAsync(x => x.Active == true && x.AdmissionId == model.AdmissionId)
                : await this.unitOfWork.FinalBill.FindAsync(x => x.Active == true && x.AppointmentId == model.AdmissionId);

            if (record == null)
            {
                return null;
            }

            var viewModel = new ViewModel
            {
                Basics = new BasicModel
                {
                    FinalBillId = record.FinalBillId,
                    DiscountTypeId = (int?)record.DiscountTypeId,
                    DiscountDetails = record.DiscountDetails,
                    TotalAmount = record.TotalAmount,
                },
                GeneralRecords = new List<ServiceOrderPayload.ViewModel>()

            };
            var typeCondition = model.IsAdmission ? @"""AdmissionId""" : @"""AppointmentId""";
            var query = $@"SELECT C
	                        .""ChargeName"",
	                        c.""ChargeId"",
                            c.""RepeatTypeId"",
	                        cg.""ChargeGroupName"",
	                        d.""DepartmentName"",
                            pp.""FullName"" AS ""ChargeTypeMainName"",
	                        s.""Unit"",
	                        s.""Cost"",
                            s.""Discount""
                        FROM
	                        ""FinalBill"" f
	                        JOIN ""FinalBillServiceOrder"" s ON s.""FinalBillId"" = f.""FinalBillId""
	                        JOIN ""Charge"" C ON C.""ChargeId"" = s.""ChargeId""
	                        JOIN ""ChargeGroup"" cg ON cg.""ChargeGroupId"" = C.""ChargeGroupId""
	                        JOIN ""Department"" d ON d.""DepartmentId"" = cg.""DepartmentId"" 
                            LEFT JOIN ""Provider"" pp on (pp.""ProviderId"" = s.""ChargeTypeMainId"" AND s.""ChargeTypeId"" = 1)
                        WHERE
	                        f.{typeCondition} = {model.AdmissionId} AND f.""Active"" IS TRUE AND s.""ChargeId"" IS NOT NULL
                        ORDER BY
	                        s.""FinalBillServiceOrderId""";

            var generalRecords = await this.unitOfWork.Current.QueryAsync<ServiceOrderPayload.ViewModel>(query);
            viewModel.GeneralRecords = generalRecords.ToList();

            if (model.IsAdmission)
            {
                query = $@"SELECT
	                        P.""ProductName"" AS ""ChargeName"",
	                        s.""ProductId"",
	                        l.""Name"" AS ""ChargeGroupName"",
	                        s.""Unit"",
	                        s.""Cost"" ,
	                        s.""DiscountPercentage""
                        FROM
	                        ""FinalBill"" f
	                        JOIN ""FinalBillServiceOrder"" s ON s.""FinalBillId"" = f.""FinalBillId""
	                        JOIN ""PharmacyProduct"" P ON P.""PharmacyProductId"" = s.""ProductId""
	                        LEFT JOIN ""LookupValue"" l on l.""LookupValueId"" = P.""CategoryId"" 
                        WHERE
	                        f.""AdmissionId"" = {model.AdmissionId} AND f.""Active"" IS TRUE AND s.""ProductId"" IS NOT NULL
                        ORDER BY
	                        s.""FinalBillServiceOrderId""";

                var pharmacyRecords = await this.unitOfWork.Current.QueryAsync<ServiceOrderPayload.ViewModel>(query);
                viewModel.PharmacyRecords = pharmacyRecords.ToList();
            }
            else
            {
                viewModel.PharmacyRecords = new List<ServiceOrderPayload.ViewModel>();
            }

            //query = $@"SELECT
            //             p.""PackageId"",
            //             p.""Name"",
            //                p.""Active"",
            //                general.""TotalGeneral"",
            //             medicines.""TotalMedicines"",
            //             kits.""TotalKits"",
            //             p.""Amount"" AS ""Cost"",
            //                1 AS ""Unit""
            //            FROM
            //                ""FinalBill"" f
            //                JOIN ""FinalBillServiceOrder"" s ON s.""FinalBillId"" = f.""FinalBillId""
            //             JOIN ""PackageModule"" p on p.""PackageModuleId"" = s.""PackageModuleId""

            //            LEFT JOIN LATERAL (
            //             SELECT COUNT(c.""PackageChargeId"") OVER() AS ""TotalGeneral"" FROM ""PackageCharge"" c
            //             WHERE p.""PackageId"" = c.""PackageId"" AND c.""ChargeId"" IS NOT NULL
            //             LIMIT 1
            //            ) general ON true
            //            LEFT JOIN LATERAL (
            //             SELECT COUNT(c.""PackageChargeId"") OVER() AS ""TotalMedicines"" FROM ""PackageCharge"" c
            //             WHERE p.""PackageId"" = c.""PackageId"" AND c.""ProductId"" IS NOT NULL
            //             LIMIT 1
            //            ) medicines ON true
            //            LEFT JOIN LATERAL (
            //             SELECT COUNT(c.""PackageChargeId"") OVER() AS ""TotalLabs"" FROM ""PackageCharge"" c
            //             WHERE p.""PackageId"" = c.""PackageId"" AND c.""LabHeaderId"" IS NOT NULL
            //             LIMIT 1
            //            ) labs ON true
            //            LEFT JOIN LATERAL (
            //             SELECT COUNT(c.""PackageChargeId"") OVER() AS ""TotalKits"" FROM ""PackageCharge"" c
            //             WHERE p.""PackageId"" = c.""PackageId"" AND c.""SurgeryKitId"" IS NOT NULL
            //             LIMIT 1
            //            ) kits ON true 
            //            WHERE
            //             f.{typeCondition} = {model.AdmissionId} AND f.""Active"" IS TRUE AND s.""PackageId"" IS NOT NULL
            //            ORDER BY
            //             s.""FinalBillServiceOrderId""";

            query = $@"SELECT
	                        p.""PackageModuleId"",
	                        p.""PackageName"",
                            p.""LocationId"",
                            p.""ChargeModuleTemplateId"",
	                        p.""ModulesMasterIds"",
                            PTL.""Name"" AS ""ModuleTypeName"",
                            p.""Quantity"",
                            p.""FreeQuantity"",
                            p.""ExpiresIn"",
                            p.""Active"",
	                        0 AS ""Cost"",
                            1 AS ""Unit""
                        FROM
                            ""FinalBill"" f
                            JOIN ""FinalBillServiceOrder"" s ON s.""FinalBillId"" = f.""FinalBillId""
	                        JOIN ""PackageModule"" p on p.""PackageModuleId"" = s.""PackageModuleId""
                            JOIN ""LookupValue"" PTL on PTL.""LookupValueId"" = p.""ModuleTypeId""
                        WHERE
	                        f.{typeCondition} = {model.AdmissionId} AND f.""Active"" IS TRUE AND s.""PackageModuleId"" IS NOT NULL
                        ORDER BY
	                        s.""FinalBillServiceOrderId""";

            var packageRecords = await this.unitOfWork.Current.QueryAsync<ServicePackage.ViewModel>(query);
            viewModel.PackageRecords = packageRecords.ToList();

            var typeCondition1 = model.IsAdmission ? @"""AdmissionId""" : @"""AppointmentId""";
            var joinOnCondition = model.IsAdmission ? @" nlbh.""AdmissionId"" = s.""AdmissionId"" " : @"nlbh.""AppointmentId"" = s.""AppointmentId""";
            var activeCondition = @"AND s.""Active"" IS TRUE";

            var query1 = $@"SELECT nlbh.""NewLabBookingHeaderId"",nlbd.""NewLabBookingDetailId"",C.""TestName"",
                            case when s.""Active"" = true then lbs.""Status"" else null end ""LabStatus"",
                            c.""TestCode"",
                            s.""LabServicesId"",
	                        s.""Unit"",
	                        s.""Cost"",
                            s.""IsMain"",
                            C.""LabMainDetailId"",
                            C.""ModulesMasterId"",
                            s.""Active"",
                            s.""AdmissionPackageId"",
                            s.""PackageModuleDetailId"",
                            s.""UsedQuantity"",
                            s.""UsedCost"",
                            s.""DiscountType"",
                            s.""DiscountPercentage"",
                            s.""DiscountAmount"",
                            s.""Discount"",
                            s.""CreatedDate"",
                            s.""ModifiedDate"",
                            a.""FullName"" AS ""CreatedByName"",
                            m.""FullName"" AS ""ModifiedByName""
                        FROM
	                        ""LabServices"" s
	                        JOIN ""LabMainDetail"" C ON C.""LabMainDetailId"" = s.""LabMainDetailId""
                            JOIN ""NewLabBookingHeader"" nlbh on {joinOnCondition}
							JOIN ""NewLabBookingDetail"" nlbd on nlbd.""NewLabBookingHeaderId"" = nlbh.""NewLabBookingHeaderId"" and nlbd.""LabMainDetailId"" = C.""LabMainDetailId"" and nlbd.""LabServicesId"" = s.""LabServicesId""
                            Join ""LabBookingStatus"" lbs on lbs.""LabBookingStatusId"" = nlbd.""LabBookingStatusId""
                            JOIN ""Account"" a ON a.""AccountId"" = s.""CreatedBy""
	                        LEFT JOIN ""Account"" m ON m.""AccountId"" = s.""ModifiedBy""
                        WHERE s.{typeCondition} = {model.AdmissionId} {activeCondition}
                        ORDER BY
	                        ""LabServicesId"" DESC";
            var labServices = await this.unitOfWork.Current.QueryAsync<LabServicesModel>(query1);
            viewModel.LabServicesRecords = labServices.ToList();
            return viewModel;
        }

        public async Task<int> InsertAsync(InsertModel model)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            var billId = 0;
            var admissionData = await this.admissionsServices.FindAsync(Convert.ToInt32(model.Id));
            var existingCount = model.IsAdmission
                ? await this.unitOfWork.FinalBill.CountAsync(x => x.AdmissionId == model.AdmissionId, transaction)
                : await this.unitOfWork.FinalBill.CountAsync(x => x.AppointmentId == model.AdmissionId, transaction);

            if (existingCount > 0)
            {
                var response = await this.DeactivateExistingAsync(model.AdmissionId, model.IsAdmission, transaction);
                if (response <= 0)
                {
                    transaction.Rollback();
                    return -1;
                }
            }

            var finalBill = new FinalBill
            {
                Active = true,
                AdmissionId = model.IsAdmission ? (int?)model.AdmissionId : null,
                AppointmentId = !model.IsAdmission ? (int?)model.AdmissionId : null,
                CreatedBy = model.CreatedBy,
                CreatedDate = DateTime.Now,
                DiscountTypeId = model.DiscountTypeId,
                DiscountDetails = model.DiscountDetails,
                TotalAmount = model.TotalAmount,
                FinalAmount = model.FinalAmount
            };

            var finalBillId = await this.unitOfWork.FinalBill.InsertAsync(finalBill, transaction);
            if (finalBillId <= 0)
            {
                transaction.Rollback();
                return -1;
            }

            // Final Bill Service Orders
            var serviceOrders = model.GeneralRecords.Select(record => new FinalBillServiceOrder
            {
                Cost = record.Cost,
                ChargeId = record.ChargeId,
                Unit = record.Unit,
                FinalBillId = finalBillId,
                ChargeTypeMainId = record.ChargeTypeMainId,
                ChargeTypeId = record.ChargeTypeId,
                Notes = record.Notes,
                Discount = record.Discount
            }).ToList();

            if (model.IsAdmission)
            {
                var pharmacyRecords = model.PharmacyRecords.Select(record => new FinalBillServiceOrder
                {
                    Cost = record.Cost,
                    ProductId = record.ProductId,
                    Unit = record.Unit,
                    FinalBillId = finalBillId,
                    DiscountPercentage = record.DiscountPercentage,
                    Notes = record.Notes
                }).ToList();
                serviceOrders.AddRange(pharmacyRecords);
            }

            var packageRecords = model.PackageRecords.Select(record => new FinalBillServiceOrder
            {
                Cost = record.Cost,
                PackageModuleId = record.PackageId,
                Unit = 1,
                FinalBillId = finalBillId,
                Notes = record.Notes
            }).ToList();
            serviceOrders.AddRange(packageRecords);

            if (serviceOrders.Any())
            {
                var isInserted = await this.unitOfWork.FinalBillServiceOrder.BulkInsertAsync(serviceOrders, transaction);
                if (isInserted <= 0)
                {
                    transaction.Rollback();
                    return -1;
                }
            }
            //begin-MasterBill
            if(admissionData != null)
            {
                var getbillrecord = await this.unitOfWork.MasterBill.FindAsync(x => x.PatientId == admissionData.PatientId && x.BillStatusTypeId == 2 && x.ReceiptAreaTypeId == (int)ReceiptAreaType.Services && x.ModuleId == model.AdmissionId); //need to check more
                if (getbillrecord == null)
                {
                    var onebill = new MasterBill
                    {
                        ReceiptAreaTypeId = (int)ReceiptAreaType.Services,//main module
                                                                          //ModuleId = (int)ReceiptAreaType.Labs, //sub module
                        ModuleId = model.AdmissionId, // module-main table id.
                        BillDate = DateTime.Now,
                        BillNumber = string.Format("{0:000000}", new Random().Next(0, 999999).ToString()),
                        BillStatusTypeId = (int)BillStatusType.Not_Generated,
                        CreatedBy = model.CreatedBy,
                        CreatedDate = DateTime.Now,
                        Active = true,
                        PatientId = admissionData.PatientId,
                        Total = model.TotalAmount,
                        Discount = model.TotalAmount - model.FinalAmount,
                        NetTotal = model.FinalAmount,
                        Rounding = model.DiscountDetails - model.DiscountDetails,
                        LocationId = (int)model.LocationId
                    };
                    onebill.MasterBillId = await this.unitOfWork.MasterBill.InsertAsync(onebill, transaction);
                    billId = onebill.MasterBillId;
                    if (onebill.MasterBillId == 0)
                    {
                        transaction.Rollback();
                        return -5;
                    }
                }
            }

           
            //end-MasterBill
            transaction.Commit();
            return finalBillId;
        }

        public async Task<int> CancelAsync(FilterModel model)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            var response = await this.DeactivateExistingAsync(model.AdmissionId, model.IsAdmission, transaction);
            if (response > 0)
            {
                transaction.Commit();
                return 1;
            };
            transaction.Rollback();
            return -1;
        }

        public async Task<BasicModel> FetchFinalBillDetailsAsync(IdModel model)
        {
            var where = "where 1=1";
            var condition = model.IsAdmission ? $@" join ""Admission"" A on A.""AdmissionId"" = F.""AdmissionId""" : $@" join ""Appointment"" A on A.""AppointmentId"" = F.""AppointmentId""";

            where += model.IsAdmission ? $@" and  A.""AdmissionId"" = {model.Id}" : $@" and A.""AppointmentId"" = {model.Id}";

            var query = $@"select F.""FinalBillId"", P.""FullName""
                             from ""FinalBill"" F
                            {condition}
                            join ""Patient"" P on P.""PatientId"" = A.""PatientId""
                            {where}";
            return this.unitOfWork.Current.QueryFirstOrDefault<BasicModel>(query);
        }

        public async Task<int> UpdatePackageActivity(int id, bool isAdmission, bool active)
        {
            var condition = isAdmission ? $@"""AdmissionId"" = {id}" : $@"""AppointmentId"" = {id}";
            var query = $@"update ""AdmissionPackage"" set ""Active"" ={active} where {condition}";
            return await this.unitOfWork.Current.ExecuteAsync(query);
        }

        public async Task<int> UpdatePackageCompleteness(int id, bool isAdmission, bool active)
        {
            var condition = isAdmission ? $@"""AdmissionId"" = {id}" : $@"""AppointmentId"" = {id}";
            var query = $@"update ""AdmissionPackage"" set ""IsPackageComplete"" ={active} where {condition}";
            return await this.unitOfWork.Current.ExecuteAsync(query);
        }
    }
}
