namespace Hims.Infrastructure.Services
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Threading.Tasks;
    using Dapper;
    using Domain.Entities;
    using Domain.Repositories.UnitOfWork;
    using Domain.Services;
    using Hims.Shared.Library.Enums;
    using Hims.Shared.UserModels.ProviderSchedule;
    using Hims.Shared.UserModels.Scan.ScanMachineAvailability;
    using Hims.Shared.UserModels.Slots;
    using Newtonsoft.Json;

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

        /// <inheritdoc cref="IProviderLocationService" />
        public ScanMachineAvailabilityService(IUnitOfWork unitOfWork) => this.unitOfWork = unitOfWork;

        public Task<int> ActivateOrDeactivateTest(ScanMachineAvailabilityInsertModel model)
        {
            throw new NotImplementedException();
        }

        public Task<IEnumerable<ScanMachineAvailabilityFilterModel>> FetchAllAsync(ScanMachineAvailabilityFilterModel model)
        {
            var where = $@"where 1=1 and SA.""Active"" is true";

            if (model.LocationId != null)
            {
                where += $@" and Sa.""LocationId"" = {model.LocationId}";
            }

            if (model.ScanMachineMasterId != null)
            {
                where += $@" and SA.""ScanMachineMasterId"" in ({model.ScanMachineMasterId})";
            }

            var query = $@"select SA.""ScanMachineAvailabilityId"",SA.""ScanMachineMasterId"",sm.""MachineName"",sm.""DisplayName"",SA.""AvailableDays"",SA.""Active"",SA.""Availability"",SA.""FromDate"",SA.""ToDate"",
                                                         SA.""CreatedBy"",SA.""CreatedDate"",SA.""ModifiedBy"",SA.""ModifiedDate"",C.""FullName"" as ""CreatedByName"",
                                                         M.""FullName"" as ""ModifiedByName"",SA.""LocationId"",l.""Name"" as ""LocationName"", SA.""FromTime"", SA.""ToTime"", SA.""ReferenceBreakId"" as ""ScanAvailabilityStatus"", SA.""ReferenceBlockId"" as ""ScanAvailabilityReason"",
                                                         lv.""Name"" as ""ScanAvailabilityStatusName"", lvb.""Name"" ""ScanAvailabilityReasonName"",
                                                         CONCAT(""Availability"", 'T', ""FromTime"", ':00') as ""StartDate"",
														  CONCAT(""Availability"", 'T', ""ToTime"", ':00') as ""EndDate"",
                                                         SA.""ScanWeekId""
                                                         from ""ScanMachineAvailability"" SA
                             							 join ""ScanMachineMaster"" sm on sm.""ScanMachineMasterId"" = SA.""ScanMachineMasterId""
                                                         left join ""LookupValue"" lv ON lv.""LookupValueId"" = SA.""ReferenceBreakId""
                                                         left join ""LookupValue"" lvb ON lvb.""LookupValueId"" = SA.""ReferenceBlockId""
                                                         left JOIN ""Location"" l ON l.""LocationId"" = Sa.""LocationId""
                                                         join ""Account"" C on C.""AccountId"" = SA.""CreatedBy""
                                                         left join ""Account"" M on M.""AccountId"" = SA.""ModifiedBy"" {where}
                                                         order by SA.""CreatedDate"" desc";
            return this.unitOfWork.Current.QueryAsync<ScanMachineAvailabilityFilterModel>(query);
        }

        public async Task<int> InsertAsync(ScanMachineAvailabilityInsertModel model)
        {
            var dates = model.Availability.Split(',');

            var times = JsonConvert.DeserializeObject<List<SlotModelTest>>(model.Slots);
            var insertionModel = new List<ScanMachineAvailability>();

            Array.ForEach(dates, x =>
            {
                foreach (var date in times)
                {
                    insertionModel.Add(new ScanMachineAvailability
                    {
                        ScanMachineMasterId = model.ScanMachineMasterId,
                        Active = true,
                        CreatedBy = model.CreatedBy,
                        CreatedDate = DateTime.Now,
                        LocationId = model.LocationId,
                        Availability = x.ToString(),
                        AvailableDays = model.AvailableDays,
                        FromDate = model.FromDate,
                        ToDate = model.ToDate,
                        FromTime = date.FromTime,
                        ToTime = date.ToTime,
                        ReferenceBlockId = date.ScanAvailabilityReason,
                        ReferenceBreakId = date.ScanAvailabilityStatus,
                        ScanWeekId = model.ScanWeekId,
                        AvailableDate = model.Availability
                    });
                }
            });
            return await this.unitOfWork.ScanMachineAvailabilitys.BulkInsertAsync(insertionModel);

        }

        ///// <summary>
        ///// the update async
        ///// </summary>
        ///// <param name="model"></param>
        ///// <returns></returns>
        //public async Task<int> UpdateAsync(ScanMachineAvailabilityInsertModel model)
        //{
        //    var availability = await this.unitOfWork.ScanMachineAvailabilitys.FindAsync(m => m.ScanMachineAvailabilityId == model.ScanMachineAvailabilityId);
        //    if (availability == null)
        //    {
        //        return -1;
        //    }
        //    var foundValues = await this.unitOfWork.ScanMachineAvailabilitys.FindAllAsync(x => x.AvailableDate == availability.AvailableDate && x.FromDate == availability.FromDate
        //                && x.ToDate == availability.ToDate && x.FromTime == availability.FromTime && x.ToTime == availability.ToTime && x.ReferenceBlockId == availability.ReferenceBlockId
        //                && x.ReferenceBreakId == availability.ReferenceBreakId);
        //    if (foundValues == null)
        //    {
        //        return -2;
        //    }
        //    var reqColumn = foundValues.Select(x => x.ScanMachineAvailabilityId).ToList();
        //    string commaSeparatedValues = string.Join(",", reqColumn);
        //    var deleteQuery = $@"Delete from ""ScanMachineAvailability"" where ""ScanMachineAvailabilityId"" in ({commaSeparatedValues})";
        //    int executedStatues = await this.unitOfWork.Current.ExecuteAsync(deleteQuery);
        //    if (executedStatues <= 0)
        //    {
        //        return -1;
        //    }
        //    var dates = model.Availability.Split(',');
        //    var times = JsonConvert.DeserializeObject<List<SlotModelTest>>(model.Slots);
        //    var insertionModel = new List<ScanMachineAvailability>();
        //    Array.ForEach(dates, x =>
        //    {
        //        foreach (var date in times)
        //        {
        //            insertionModel.Add(new ScanMachineAvailability
        //            {
        //                ScanMachineMasterId = model.ScanMachineMasterId,
        //                Active = true,
        //                CreatedBy = model.CreatedBy,
        //                CreatedDate = DateTime.Now,
        //                LocationId = model.LocationId,
        //                Availability = x.ToString(),
        //                AvailableDays = model.AvailableDays,
        //                FromDate = model.FromDate,
        //                ToDate = model.ToDate,
        //                FromTime = date.FromTime,
        //                ToTime = date.ToTime,
        //                ReferenceBlockId = date.ScanAvailabilityReason,
        //                ReferenceBreakId = date.ScanAvailabilityStatus,
        //                AvailableDate = model.Availability,
        //                ScanWeekId = model.ScanWeekId,
        //            });
        //        }
        //    });
        //    return await this.unitOfWork.ScanMachineAvailabilitys.BulkInsertAsync(insertionModel);
        //}

        /// <summary>
        /// the update async
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task<int> UpdateAsync(ScanMachineAvailabilityInsertModel model)
        {

            var availability = await this.unitOfWork.ScanMachineAvailabilitys.FindAsync(m => m.ScanMachineAvailabilityId == model.ScanMachineAvailabilityId);

            if (availability == null)
            {
                return -1;
            }

            var foundValues = await this.unitOfWork.ScanMachineAvailabilitys.FindAllAsync(x => x.AvailableDate == availability.AvailableDate && x.FromDate == availability.FromDate
                        && x.ToDate == availability.ToDate && x.FromTime == availability.FromTime && x.ToTime == availability.ToTime && x.ReferenceBlockId == availability.ReferenceBlockId
                        && x.ReferenceBreakId == availability.ReferenceBreakId);

            if (foundValues == null)
            {
                return -2;
            }

            var reqColumn = foundValues.Select(x => x.ScanMachineAvailabilityId).ToList();
            string commaSeparatedValues = string.Join(",", reqColumn);


            //var query = $@"SELECT string_agg(""ScanMachineAvailabilityId""::text, ',') AS ""Comma_Separated_Ids""  FROM ""ScanMachineAvailability"" WHERE ""AvailableDate"" = '{availability.AvailableDate}' and ""FromDate""::Date = '{availability.FromDate}'::Date  
            //               and ""ToDate""::Date = '{availability.ToDate}'::Date and ""FromTime"" = '{availability.FromTime}' and ""ToTime"" = '{availability.ToTime}' 
            //                    and ""ReferenceBlockId"" = {availability.ReferenceBlockId} and ""ReferenceBreakId"" = {availability.ReferenceBreakId} and ""Active"" is true";
            //var response = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<string>(query);

            //if (response == null)
            //{
            //    return -1;
            //}

            var deleteQuery = $@"Delete from ""ScanMachineAvailability"" where ""ScanMachineAvailabilityId"" in ({commaSeparatedValues})";
            int executedStatues = await this.unitOfWork.Current.ExecuteAsync(deleteQuery);
            if (executedStatues <= 0)
            {
                return -1;
            }

            //var query = $@"Delete from ""ScanMachineAvailability"" where ""ScanMachineAvailabilityId"" = {availability.ScanMachineAvailabilityId}";
            //int executedStatues = await this.unitOfWork.Current.ExecuteAsync(query);
            //if (executedStatues <= 0)
            //{
            //    return -1;
            //}
            var dates = model.Availability.Split(',');

            var times = JsonConvert.DeserializeObject<List<SlotModelTest>>(model.Slots);
            var insertionModel = new List<ScanMachineAvailability>();

            Array.ForEach(dates, x =>
            {
                foreach (var date in times)
                {
                    insertionModel.Add(new ScanMachineAvailability
                    {
                        ScanMachineMasterId = model.ScanMachineMasterId,
                        Active = true,
                        CreatedBy = model.CreatedBy,
                        CreatedDate = DateTime.Now,
                        LocationId = model.LocationId,
                        Availability = x.ToString(),
                        AvailableDays = model.AvailableDays,
                        FromDate = model.FromDate,
                        ToDate = model.ToDate,
                        FromTime = date.FromTime,
                        ToTime = date.ToTime,
                        ReferenceBlockId = date.ScanAvailabilityReason,
                        ReferenceBreakId = date.ScanAvailabilityStatus,
                        AvailableDate = model.Availability,
                        ScanWeekId = model.ScanWeekId,
                    });
                }
            });
            return await this.unitOfWork.ScanMachineAvailabilitys.BulkInsertAsync(insertionModel);

            //availability.ModifiedBy = model.CreatedBy;
            //availability.ModifiedDate = DateTime.Now;
            //availability.ScanMachineMasterId = model.ScanMachineMasterId;
            //availability.Availability = model.Availability;
            //availability.AvailableDays = model.AvailableDays;
            //availability.FromDate = model.FromDate;
            //availability.ToDate = model.ToDate;
            //return await this.unitOfWork.ScanMachineAvailabilitys.UpdateAsync(availability);

        }

        /// <inheritdoc />
        public Task<int> DeleteAsync(int? scanMachineAvailabilityId)
        {
            var query = $@"DELETE FROM ""ScanMachineAvailability"" WHERE ""ScanMachineAvailabilityId""= {scanMachineAvailabilityId}";
            return this.unitOfWork.Current.ExecuteAsync(query);
        }

        public Task<IEnumerable<ScanMachineAvailabilityFilterModel>> FetchAltAsync(ScanMachineAvailabilityFetchModel model)
        {
            var where = " WHERE 1 = 1 ";
            if (!string.IsNullOrEmpty(model.ScanTestMasterId))
            {
                where += $@"  and STM.""ScanTestMasterId"" in ({model.ScanTestMasterId})";
            }
            if (!string.IsNullOrEmpty(model.ScanMachineMasterId))
            {
                where += $@" and sm.""ScanMachineMasterId"" in ({model.ScanMachineMasterId})";
            }
            if (!string.IsNullOrEmpty(model.StartDate) && !string.IsNullOrEmpty(model.EndDate))
            {
                where += $@" AND SA.""Availability""::DATE >= '{model.StartDate}'::DATE";
                where += $@" AND SA.""Availability""::DATE <= '{model.EndDate}'::DATE";
            }
            if (model.LocationId > 0)
            {
                where += $@" and SMTM.""LocationId"" = {model.LocationId}";
            }
            var query = $@"select SA.""ScanMachineAvailabilityId"",SA.""FromDate"",SA.""ToDate"",SA.""FromTime"",SA.""ToTime"",SA.""ScanMachineMasterId"",sm.""DisplayName"",sm.""MachineName"",SA.""AvailableDays"",SA.""Active"",SA.""Availability"",
                        SA.""CreatedBy"",SA.""CreatedDate"",SA.""ModifiedBy"",SA.""ModifiedDate"",C.""FullName"" as ""CreatedByName"",SMTM.""ScanTestMasterId"",STM.""ScanTestName"",STM.""Duration"" AS ""SlotDuration"", STM.""ScanTestCode"",
                        M.""FullName"" as ""ModifiedByName"",SA.""LocationId"",l.""Name"" as ""LocationName"" ,SA.""ReferenceBreakId"" as ""ScanAvailabilityStatus"", SA.""ReferenceBlockId"" as ""ScanAvailabilityReason""
                        ,LVS.""Name"" as ""ScanAvailabilityStatusName"", LVR.""Name"" as ""ScanAvailabilityReasonName"", LVR.""RowColor"" as ""ScanAvailabilityReasonColor""
                     from ""ScanMachineAvailability"" SA
					 join ""ScanMachineMaster"" sm on sm.""ScanMachineMasterId"" = SA.""ScanMachineMasterId"" and sm.""Active"" is true
					 join ""ScanMachineTestMap"" SMTM on SMTM.""ScanMachineMasterId"" = sm.""ScanMachineMasterId"" 
					 join ""ScanTestMaster"" STM on STM.""ScanTestMasterId"" = SMTM.""ScanTestMasterId"" and STM.""Active"" is true
					 left JOIN ""Location"" l ON l.""LocationId"" = Sa.""LocationId""
					 join ""Account"" C on C.""AccountId"" = SA.""CreatedBy""
					 left join ""Account"" M on M.""AccountId"" = SA.""ModifiedBy""  
                     left join ""LookupValue"" LVS on LVS.""LookupValueId"" = SA.""ReferenceBreakId""
                     left join ""LookupValue"" LVR on LVR.""LookupValueId"" = SA.""ReferenceBlockId""
                     {where}  and SA.""Active"" IS TRUE
                     order by SA.""CreatedDate"" desc";
            return this.unitOfWork.Current.QueryAsync<ScanMachineAvailabilityFilterModel>(query);
        }

        public Task<IEnumerable<ScanMachineAvailabilityFilterModel>> FetchMachineAvailabilityAsync(ScanMachineAvailabilityFetchModel model)
        {
            var where = " WHERE 1 = 1 ";

            if (!string.IsNullOrEmpty(model.ScanMachineMasterId))
            {
                where += $@" and SA.""ScanMachineMasterId"" in ({model.ScanMachineMasterId})";
            }

            if (!string.IsNullOrEmpty(model.StartDate) && !string.IsNullOrEmpty(model.EndDate))
            {
                where += $@" AND SA.""Availability""::DATE >= '{model.StartDate}'::DATE";
                where += $@" AND SA.""Availability""::DATE <= '{model.EndDate}'::DATE";
            }

            if (model.LocationId > 0)
            {
                where += $@" and SA.""LocationId"" = {model.LocationId}";
            }

            var query = $@"select SA.""ScanMachineAvailabilityId"",SA.""FromDate"",SA.""ToDate"",SA.""FromTime"",SA.""ToTime"",SA.""ScanMachineMasterId"",SA.""AvailableDays"",
                SA.""Active"",SA.""Availability"",SA.""CreatedBy"",SA.""CreatedDate"",SA.""ModifiedBy"",SA.""ModifiedDate"",SA.""ReferenceBreakId"" as ""ScanAvailabilityStatus"",
                SA.""ReferenceBlockId"" as ""ScanAvailabilityReason"",LVS.""Name"" as ""ScanAvailabilityStatusName"", LVR.""Name"" as ""ScanAvailabilityReasonName"", 
                LVR.""RowColor"" as ""ScanAvailabilityReasonColor""
                from ""ScanMachineAvailability"" SA 
                left join ""LookupValue"" LVS on LVS.""LookupValueId"" = SA.""ReferenceBreakId"" 
                left join ""LookupValue"" LVR on LVR.""LookupValueId"" = SA.""ReferenceBlockId"" {where} AND SA.""Active"" IS TRUE";
            return this.unitOfWork.Current.QueryAsync<ScanMachineAvailabilityFilterModel>(query);
        }

        public async Task<IEnumerable<DateWiseMachines>> FetchMachineAvailabilityBasedOnDaysAsync(ScanMachineAvailabilityBasedOnDaysModel model)
        {
            DateTime dt = DateTime.Now;
            var date = dt.Date;
            var count = 0;
            if (model.FromDate == null && model.ToDate == null)
            {
                count = (int)model.Count;
            }
            else
            {
                var from = model.FromDate;
                DateTime convertFromDat = DateTime.Parse(from);
                var d = Convert.ToDateTime(convertFromDat).Date;
                var toDate = model.ToDate;
                DateTime convertToDate = DateTime.Parse(toDate);
                var f = Convert.ToDateTime(convertToDate).Date;
                var diff = f.Subtract(d);
                count = ((int)diff.TotalDays) + 1;
            }
            DateTime aptDate = Convert.ToDateTime(model.AppointmentDate);
            var appointmentDate = aptDate.Date;

            string dateString = null;
            var gettingScanMachine = new List<ScanMachineAvailabilityBasedOnDaysModel>();
            var dateWiseMacine = new List<DateWiseMachines>();
            var gettingScanMachines = new List<ScanMachineAvailabilityBasedOnDaysModel>();
            for (var n = 1; n <= count; n++)
            {
                if (model.FromDate != null)
                {
                    if (n == 1)
                    {
                        date = Convert.ToDateTime(model.FromDate);
                    }
                    dateString = date.ToString("yyyy-MM-dd");
                    date = date.AddDays(1);

                }
                else
                {
                    dt = model.Count > 1 ? date.AddDays(n) : appointmentDate;
                    dateString = dt.ToString("yyyy-MM-dd");
                }

                if (model.ScanMachineMasterId > 0)
                {
                    var query1 = $@"select ST.""ScanTestMasterId"",ST.""ScanTestName"", ST.""Duration"" ,  SMM.""MachineName"", SMM.""ScanMachineMasterId"",SM.""LocationId""
                from ""ScanTestMaster"" ST
                join ""ScanMachineTestMap"" SM ON SM.""ScanTestMasterId""=ST.""ScanTestMasterId""
                join ""ScanMachineMaster"" SMM ON SMM.""ScanMachineMasterId""=SM.""ScanMachineMasterId""
                WHERE ST.""ScanTestMasterId""={model.ScanTestMasterId} and SMM.""ScanMachineMasterId"" = {model.ScanMachineMasterId} and SM.""LocationId""={model.LocationId} and SMM.""Active""= true ";
                    gettingScanMachines = (List<ScanMachineAvailabilityBasedOnDaysModel>)await this.unitOfWork.Current.QueryAsync<ScanMachineAvailabilityBasedOnDaysModel>(query1);
                }
                else
                {
                    var query2 = $@"select ST.""ScanTestMasterId"",ST.""ScanTestName"", ST.""Duration"" ,  SMM.""MachineName"", SMM.""ScanMachineMasterId"",SM.""LocationId""
                from ""ScanTestMaster"" ST
                join ""ScanMachineTestMap"" SM ON SM.""ScanTestMasterId""=ST.""ScanTestMasterId""
                join ""ScanMachineMaster"" SMM ON SMM.""ScanMachineMasterId""=SM.""ScanMachineMasterId""
                WHERE ST.""ScanTestMasterId""={model.ScanTestMasterId} and SM.""LocationId""={model.LocationId} and SMM.""Active""= true ";
                    gettingScanMachines = (List<ScanMachineAvailabilityBasedOnDaysModel>)await this.unitOfWork.Current.QueryAsync<ScanMachineAvailabilityBasedOnDaysModel>(query2);
                }
                var pushDateWiseMachines = new DateWiseMachines();
                var machineSlots = new List<MachineSlots>();
                if (gettingScanMachines.Count > 0)
                {
                    var num = 0;
                    foreach (var item in gettingScanMachines)
                    {
                        num = num + 100;

                        var availabilityOfMachineQuery = $@"select distinct SA.""ScanMachineAvailabilityId"", SA.""ScanMachineMasterId"", SA.""AvailableDays"",  SA.""Availability"",  
                        SA.""LocationId"", SA.""Active"", SA.""CreatedBy"", SA.""CreatedDate"",  SA.""FromDate"", SA.""ToDate"", SA.""FromTime"",SA.""ToTime"", 
                        SA.""ReferenceBreakId"", SA.""ReferenceBlockId"", LVS.""Name"" as ""ScanAvailabilityStatusName"",LVR.""Name"" as ""ScanAvailabilityReasonName""
                        from ""ScanMachineAvailability"" SA
                        left join ""LookupValue"" LVS on LVS.""LookupValueId"" = SA.""ReferenceBreakId""
                        left join ""LookupValue"" LVR on LVR.""LookupValueId"" = SA.""ReferenceBlockId""
                         where SA. ""ScanMachineMasterId"" ={item.ScanMachineMasterId} and SA.""Availability""='{dateString}' and SA.""LocationId""={model.LocationId} and SA.""Active"" IS TRUE";

                        var gettingMachineAvailability = (List<ScanMachineAvailabilityFilterModel>)await this.unitOfWork.Current.QueryAsync<ScanMachineAvailabilityFilterModel>(availabilityOfMachineQuery);

                        var forEachMachine = new MachineSlots();
                        if (gettingMachineAvailability.Count > 0)
                        {
                            var scanTestDuration = new List<ScanMachineAvailabilityBasedOnDaysModel>();
                            scanTestDuration = (List<ScanMachineAvailabilityBasedOnDaysModel>)await this.GetScanTestDuration(model.ScanTestMasterId);
                            var duration = 0;
                            if (scanTestDuration.Count > 0)
                            {
                                duration = (int)scanTestDuration[0].Duration;
                            }
                            var scanAppointmentDetails = await this.FetchScanAppointmentTimeDetails((int)item.ScanMachineMasterId, dateString, (int)item.LocationId);

                            var appointmenTimes = scanAppointmentDetails.ToList();
                            var scanTimeSlot = new List<ScanSlotModel>();
                            var breakSlots = new List<ScanSlotModel>();
                            var availableOnly = gettingMachineAvailability.Where(x => x.ScanAvailabilityReasonName != "Permanant Block").ToList();
                            var blockOnly = gettingMachineAvailability.Where(x => x.ScanAvailabilityReasonName == "Permanant Block").ToList();

                            foreach (var availability in availableOnly)
                            {
                                var startTimeTokens = availability.FromTime.Split(":");
                                var endTimeTokens = availability.ToTime.Split(":");
                                DateTime startTime = new DateTime(
                                            DateTime.Now.Year,
                                            DateTime.Now.Month,
                                            DateTime.Now.Day,
                                            Convert.ToInt32(startTimeTokens[0]),
                                            Convert.ToInt32(startTimeTokens[1]),
                                            0);
                                DateTime endTime = new DateTime(
                                    DateTime.Now.Year,
                                    DateTime.Now.Month,
                                    DateTime.Now.Day,
                                    Convert.ToInt32(endTimeTokens[0]),
                                    Convert.ToInt32(endTimeTokens[1]),
                                    0
                                );
                                var counter = 0;

                                while (startTime < endTime)
                                {
                                    var isBreak = false;
                                    foreach (var block in blockOnly)
                                    {
                                        var blockFromTimeTokens = block.FromTime.Split(":");
                                        var blockFromTimeDate = new DateTime(
                                            DateTime.Now.Year,
                                            DateTime.Now.Month,
                                            DateTime.Now.Day,
                                            Convert.ToInt32(blockFromTimeTokens[0]),
                                            Convert.ToInt32(blockFromTimeTokens[1]),
                                            0
                                        );
                                        var blockToTimeTokens = block.ToTime.Split(":");
                                        var blockToTimeDate = new DateTime(
                                            DateTime.Now.Year,
                                            DateTime.Now.Month,
                                            DateTime.Now.Day,
                                            Convert.ToInt32(blockToTimeTokens[0]),
                                            Convert.ToInt32(blockToTimeTokens[1]),
                                            0
                                        );
                                        if (startTime >= blockFromTimeDate && startTime < blockToTimeDate)
                                        {
                                            isBreak = true;
                                            break;
                                        }
                                    }

                                    var nextTime = startTime.AddMinutes((double)duration);
                                    if (!isBreak && !scanTimeSlot.Any(x => x.SlotValue == startTime.ToString("HH:mm")))
                                    {
                                        ++counter;
                                        var status = false;
                                        foreach (var value in scanAppointmentDetails)
                                        {
                                            if (((startTime.TimeOfDay >= value.AppointmentTime && startTime.TimeOfDay < value.AppointmentEndTime) && (nextTime.TimeOfDay > value.AppointmentTime && startTime.TimeOfDay <= value.AppointmentEndTime)) ||
                                                (value.AppointmentTime > startTime.TimeOfDay && value.AppointmentTime < nextTime.TimeOfDay))
                                            {
                                                status = true;
                                            }
                                        }

                                        var timeSlots = new ScanSlotModel
                                        {
                                            TokenNumber = counter,
                                            SlotValue = startTime.ToString("HH:mm"),
                                            SlotValue24HoursEnd = nextTime.ToString("HH:mm"),
                                            SlotName = startTime.ToString("hh:mm tt"),
                                            SlotTime = startTime.TimeOfDay,
                                            EndTime = nextTime.ToString("hh:mm tt"),
                                            Duration = duration,
                                            Status = status == true ? SlotStatus.Booked : SlotStatus.Available,
                                            AvailableDate = dateString,
                                            ScanMachineMasterId = availability.ScanMachineMasterId,
                                            Id = num + "" + counter
                                        };
                                        scanTimeSlot.Add(timeSlots);
                                    }
                                    startTime = nextTime;
                                }
                            }

                            forEachMachine.MachineName = item.MachineName;
                            forEachMachine.ScanMachineMasterId = item.ScanMachineMasterId;
                            forEachMachine.ScanTimeSlot = new List<ScanSlotModel>();
                            forEachMachine.ScanTimeSlot.AddRange(scanTimeSlot.OrderBy(x => x.SlotTime));
                            machineSlots.Add(forEachMachine);
                        }
                        else
                        {
                            forEachMachine.MachineName = item.MachineName;
                            forEachMachine.ScanMachineMasterId = item.ScanMachineMasterId;
                            forEachMachine.Meassage = "Slots are not available for this machine.";
                            machineSlots.Add(forEachMachine);
                        }
                    }
                    pushDateWiseMachines.Dates = dateString;
                    pushDateWiseMachines.MachineSlots = new List<MachineSlots>();
                    pushDateWiseMachines.MachineSlots.AddRange(machineSlots);
                    dateWiseMacine.Add(pushDateWiseMachines);
                }
                else
                {
                    pushDateWiseMachines.Dates = dateString;
                    pushDateWiseMachines.AvailabilityMessage = "There is no machine available for this test";
                }
            }

            return dateWiseMacine;
        }



        public async Task<IEnumerable<ScanMachineAvailabilityBasedOnDaysModel>> GetScanTestDuration(int? scanTestMasterId)
        {
            var scanTestDuration = $@"select * from ""ScanTestMaster"" where ""ScanTestMasterId""={scanTestMasterId}";
            return await this.unitOfWork.Current.QueryAsync<ScanMachineAvailabilityBasedOnDaysModel>(scanTestDuration);
        }

        public async Task<ScanMachineAvailabilityBasedOnDaysModel> GetScanMachineMininumDuration(int scanMachineMasterId)
        {
            var query = $@"select min(""Duration"") as ""Duration"" from ""ScanTestMaster"" 
                    where ""ScanTestMasterId"" 
            in (select ""ScanTestMasterId"" from ""ScanMachineTestMap"" where ""ScanMachineMasterId"" = {scanMachineMasterId})";
            return await this.unitOfWork.Current.QuerySingleAsync<ScanMachineAvailabilityBasedOnDaysModel>(query);
        }

        public Task<IEnumerable<TimeSpan>> FetchScanAppointmentDetails(int scanMachineMasterId, string appointmentDate)
        {
            var query = $@"Select distinct ""AppointmentTime"" from ""BookScanAppointment"" where  ""ScanMachineMasterId"" = '{scanMachineMasterId}'  AND ""AppointmentDate"" = '{appointmentDate}' AND ""Active"" IS TRUE";
            return this.unitOfWork.Current.QueryAsync<TimeSpan>(query);
        }

        public Task<IEnumerable<AppointmentTimings>> FetchScanAppointmentTimeDetails(int scanMachineMasterId, string appointmentDate, int locationId)
        {
            var query = $@"Select distinct ""AppointmentTime"",""AppointmentEndTime"" from ""BookScanAppointment"" where  ""ScanMachineMasterId"" = '{scanMachineMasterId}' AND ""LocationId"" = {locationId} AND ""AppointmentDate"" = '{appointmentDate}' AND ""Status"" != 'C' AND ""Active"" IS TRUE";
            return this.unitOfWork.Current.QueryAsync<AppointmentTimings>(query);
        }


        public async Task<string> FindDateByScanAvailabilityId(int? ScanMachineAvailabilityId)
        {
            var query = $@"SELECT ""Availability""::text  FROM ""ScanMachineAvailability"" WHERE ""ScanMachineAvailabilityId"" = {ScanMachineAvailabilityId}";
            var response = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<string>(query);
            return response;
        }

        /// <inheritdoc />
        public async Task<int> CancelAvailabilityAsync(int? ScanMachineAvailabilityId, string FromDate, string ToDate)
        {
            var availability = await this.unitOfWork.ScanMachineAvailabilitys.FindAsync(m => m.ScanMachineAvailabilityId == ScanMachineAvailabilityId);

            if (availability == null)
            {
                return 0;
            }

            var foundValues = await this.unitOfWork.ScanMachineAvailabilitys.FindAllAsync(x => x.AvailableDate == availability.AvailableDate && x.FromDate == availability.FromDate
                            && x.ToDate == availability.ToDate && x.FromTime == availability.FromTime && x.ToTime == availability.ToTime && x.ReferenceBlockId == availability.ReferenceBlockId
                            && x.ReferenceBreakId == availability.ReferenceBreakId);

            if (foundValues == null)
            {
                return -2;
            }
            //var foundRequiredValues = foundValues.ToList().FindAll(x => x.FromDate <= DateTime.Parse(FromDate) && DateTime.Parse(x.Availability) <= DateTime.Parse(ToDate) ); // I wrote to delete from date to todate

            var reqColumn = foundValues.Select(x => x.ScanMachineAvailabilityId).ToList(); string commaSeparatedValues = string.Join(",", reqColumn);

            //var query = $@"SELECT string_agg(""ScanMachineAvailabilityId""::text, ',') AS ""Comma_Separated_Ids""  FROM ""ScanMachineAvailability"" WHERE ""AvailableDate"" = '{availability.AvailableDate}' and ""FromDate"" =   CAST('{availability.FromDate}' AS timestamp)
            //                   and ""ToDate"" = CAST('{availability.ToDate}' AS timestamp) and ""FromTime"" = '{availability.FromTime}' and ""ToTime"" = '{availability.ToTime}' 
            //                        and ""ReferenceBlockId"" = {availability.ReferenceBlockId} and ""ReferenceBreakId"" = {availability.ReferenceBreakId} and ""Active"" is true";
            //var response = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<string>(query);

            //if (response == null)
            //{
            //    return 2;
            //}
            var updateQuery = $@"Update ""ScanMachineAvailability"" set ""Active"" = 'false' WHERE ""ScanMachineAvailabilityId"" in ({commaSeparatedValues})";
            return await this.unitOfWork.Current.ExecuteAsync(updateQuery);
        }

        public async Task<IEnumerable<ScanSlotModel>> FetchMachineSlotsAsync(ScanMachineAvailabilityBasedOnDaysModel model)
        {
            DateTime dt = DateTime.Now;
            var date = dt.Date;
            // var count = model.Count;
            string dateString = null;
            dateString = dt.ToString("yyyy-MM-dd");

            var num = 0;
            num = num + 100;

            var availabilityOfMachineQuery = $@"select distinct SA.""ScanMachineAvailabilityId"", SA.""ScanMachineMasterId"", SA.""AvailableDays"",  SA.""Availability"",  
                        SA.""LocationId"", SA.""Active"", SA.""CreatedBy"", SA.""CreatedDate"",  SA.""FromDate"", SA.""ToDate"", SA.""FromTime"",SA.""ToTime"", 
                        SA.""ReferenceBreakId"", SA.""ReferenceBlockId"", LVS.""Name"" as ""ScanAvailabilityStatusName"",LVR.""Name"" as ""ScanAvailabilityReasonName""
                        from ""ScanMachineAvailability"" SA
                        left join ""LookupValue"" LVS on LVS.""LookupValueId"" = SA.""ReferenceBreakId""
                        left join ""LookupValue"" LVR on LVR.""LookupValueId"" = SA.""ReferenceBlockId""
                         where SA. ""ScanMachineMasterId"" ={model.ScanMachineMasterId} and SA.""Availability""='{dateString}' and SA.""LocationId""={model.LocationId} and SA.""Active"" IS TRUE";
            //available blocks
            var gettingMachineAvailability = (List<ScanMachineAvailabilityFilterModel>)await this.unitOfWork.Current.QueryAsync<ScanMachineAvailabilityFilterModel>(availabilityOfMachineQuery);

            var forEachMachine = new MachineSlots();//machine slot per machine
            if (gettingMachineAvailability.Count > 0)//available blocks
            {
                //duration
                var duration = new ScanMachineAvailabilityBasedOnDaysModel();
                duration = await this.GetScanMachineMininumDuration((int)model.ScanMachineMasterId);
                //booked appointment.
                var scanAppointmentDetails = await this.FetchScanAppointmentTimeDetails((int)model.ScanMachineMasterId, dateString, (int)model.LocationId);

                //var appointmenTimes = scanAppointmentDetails.ToList();
                var scanTimeSlot = new List<ScanSlotModel>();///to prepare emplty slot
                var breakSlots = new List<ScanSlotModel>(); // another slot block slot
                var availableOnly = gettingMachineAvailability.Where(x => x.ScanAvailabilityReasonName != "Permanant Block").ToList();
                var blockOnly = gettingMachineAvailability.Where(x => x.ScanAvailabilityReasonName == "Permanant Block").ToList();

                foreach (var availability in availableOnly)
                {
                    var startTimeTokens = availability.FromTime.Split(":");
                    var endTimeTokens = availability.ToTime.Split(":");
                    DateTime startTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, Convert.ToInt32(startTimeTokens[0]), Convert.ToInt32(startTimeTokens[1]), 0);
                    DateTime endTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, Convert.ToInt32(endTimeTokens[0]), Convert.ToInt32(endTimeTokens[1]), 0);
                    var counter = 0;

                    while (startTime < endTime)
                    {
                        var isBreak = false;
                        foreach (var block in blockOnly)
                        {
                            var blockFromTimeTokens = block.FromTime.Split(":");
                            var blockFromTimeDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, Convert.ToInt32(blockFromTimeTokens[0]), Convert.ToInt32(blockFromTimeTokens[1]), 0);
                            var blockToTimeTokens = block.ToTime.Split(":");
                            var blockToTimeDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, Convert.ToInt32(blockToTimeTokens[0]), Convert.ToInt32(blockToTimeTokens[1]), 0);
                            if (startTime >= blockFromTimeDate && startTime < blockToTimeDate)
                            {
                                isBreak = true;
                                break;
                            }
                        }
                        var nextTime = startTime.AddMinutes((int)duration.Duration);
                        if (!isBreak && !scanTimeSlot.Any(x => x.SlotValue == startTime.ToString("HH:mm")))
                        {
                            ++counter;
                            var status = false;
                            foreach (var value in scanAppointmentDetails)
                            {
                                if (((startTime.TimeOfDay >= value.AppointmentTime && startTime.TimeOfDay < value.AppointmentEndTime) && (nextTime.TimeOfDay > value.AppointmentTime && startTime.TimeOfDay <= value.AppointmentEndTime)) ||
                                    (value.AppointmentTime > startTime.TimeOfDay && value.AppointmentTime < nextTime.TimeOfDay))
                                {
                                    status = true;
                                }
                            }

                            var timeSlots = new ScanSlotModel
                            {
                                SlotValue = startTime.ToString("HH:mm"),
                                SlotValue24HoursEnd = nextTime.ToString("HH:mm"),
                                SlotName = startTime.ToString("hh:mm tt"),
                                SlotTime = startTime.TimeOfDay,
                                EndTime = nextTime.ToString("hh:mm tt"),
                                Duration = (int)duration.Duration,
                                Status = status == true ? SlotStatus.Booked : SlotStatus.Available,
                                AvailableDate = dateString,
                                ScanMachineMasterId = availability.ScanMachineMasterId,
                                Id = num + "" + counter
                            };
                            scanTimeSlot.Add(timeSlots);
                        }
                        startTime = nextTime;
                    }
                }
                return scanTimeSlot;

            }
            else
            {
                return null;
            }
        }

    }

}
