namespace Hims.Infrastructure.Services
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Dapper;
    using Domain.Entities;
    using Domain.Repositories.UnitOfWork;
    using Domain.Services;
    using Hims.Shared.Library.Enums;
    using Hims.Shared.UserModels;
    using Newtonsoft.Json;
    using Hims.Shared.UserModels.Slots;
    /// <inheritdoc />
    public class OTRoomAvailabilityService : IOTRoomAvailabilityService
    {
        /// <summary>
        /// The unit of work.
        /// </summary>
        private readonly IUnitOfWork unitOfWork;

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

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

        public Task<IEnumerable<OTRoomAvailabilityFilterModel>> FetchAllAsync(OTRoomAvailabilityFilterModel model)
        {
            var where = $@"where 1=1";

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

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

            var query = $@"select SA.""OTRoomAvailabilityId"",SA.""OTRoomId"",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"",
                                                            sm.""RoomName"",
                                                         CONCAT(""Availability"", 'T', ""FromTime"", ':00') as ""StartDate"",
														  CONCAT(""Availability"", 'T', ""ToTime"", ':00') as ""EndDate""
                                                         from ""OTRoomAvailability"" SA
                             							 join ""OTRoom"" sm on sm.""OTRoomId"" = SA.""OTRoomId""
                                                         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<OTRoomAvailabilityFilterModel>(query);
        }

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

            var times = JsonConvert.DeserializeObject<List<SlotModelTest>>(model.Slots);
            var insertionModel = new List<OTRoomAvailability>();
            Array.ForEach(dates, x =>
            {
                foreach (var date in times)
                {
                    insertionModel.Add(new OTRoomAvailability
                    {
                        OTRoomId = model.OTRoomId,
                        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

                    });
                }
            });
            return await this.unitOfWork.OTRoomAvailabilitys.BulkInsertAsync(insertionModel);

        }

        /// <summary>
        /// the update async
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task<int> UpdateAsync(OTRoomAvailabilityInsertModel model)
        {
            var availability = await this.unitOfWork.OTRoomAvailabilitys.FindAsync(m => m.OTRoomAvailabilityId == model.OTRoomAvailabilityId);

            if (availability == null)
            {
                return -1;
            }
            var query = $@"Delete from ""OTRoomAvailability"" where ""OTRoomAvailabilityId"" = {availability.OTRoomAvailabilityId}";
            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<OTRoomAvailability>();

            Array.ForEach(dates, x =>
            {
                foreach (var date in times)
                {
                    insertionModel.Add(new OTRoomAvailability
                    {
                        OTRoomId = model.OTRoomId,
                        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
                    });
                }
            });
            return await this.unitOfWork.OTRoomAvailabilitys.BulkInsertAsync(insertionModel);
        }

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

        public Task<IEnumerable<OTRoomAvailabilityFilterModel>> FetchAltAsync(OTRoomAvailabilityFetchModel model)
        {
            var where = " WHERE 1 = 1 ";
            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.""OTRoomAvailabilityId"",SA.""FromDate"",SA.""ToDate"",SA.""FromTime"",SA.""ToTime"",SA.""OTRoomId"",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.""ScanTestCode"",

            //M.""FullName"" as ""ModifiedByName"",SA.""LocationId"",l.""Name"" as ""LocationName"" ,SA.""ReferenceBreakId"" as ""ScanAvailabilityStatus"", SA.""ReferenceBlockId"" as ""ScanAvailabilityReason""



            //               from ""OTRoomAvailability"" SA
            //join ""OTRoom"" sm on sm.""OTRoomId"" = SA.""OTRoomId"" and sm.""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"" 
            //                      { where}
            //              order by SA.""CreatedDate"" desc";
            var query = $@"select SA.""OTRoomAvailabilityId"",SA.""FromDate"",SA.""ToDate"",SA.""FromTime"",SA.""ToTime"",SA.""OTRoomId"",SA.""AvailableDays"",SA.""Active"",SA.""Availability"",
                                                         SA.""CreatedBy"",SA.""CreatedDate"",SA.""ModifiedBy"",SA.""ModifiedDate"",C.""FullName"" as ""CreatedByName"",

					 M.""FullName"" as ""ModifiedByName"",SA.""LocationId"",l.""Name"" as ""LocationName"" 

                    

                     from ""OTRoomAvailability"" SA
					 join ""OTRoom"" sm on sm.""OTRoomId"" = SA.""OTRoomId"" and sm.""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"" 
                            { where}
                    order by SA.""CreatedDate"" desc";
            return this.unitOfWork.Current.QueryAsync<OTRoomAvailabilityFilterModel>(query);
        }
        public Task<IEnumerable<OTRoomAvailabilityFilterModel>> FetchFilterMachineAvailabilityAsync(OTRoomAvailabilityFetchModel model)
        {
            var where = " WHERE 1 = 1 ";

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

            if (model.LocationId > 0)
            {
                where += $@" and SA.""LocationId"" = {model.LocationId}";
            }
            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";
            }
            var query = $@"select SA.""OTRoomAvailabilityId"",SA.""FromDate"",SA.""ToDate"",SA.""FromTime"",SA.""ToTime"",SA.""OTRoomId"",sm.""RoomName"",SA.""AvailableDays"",SA.""Active"",SA.""Availability"",
                                                         SA.""CreatedBy"",SA.""CreatedDate"",SA.""ModifiedBy"",SA.""ModifiedDate"",C.""FullName"" as ""CreatedByName"",SMTM.""SurgeryId"",STM.""Name"" as SurgeryName,

					 M.""FullName"" as ""ModifiedByName"",SA.""LocationId"",l.""Name"" as ""LocationName"" 
                     from ""OTRoomAvailability"" SA
                    join ""OTRoom"" sm on sm.""OTRoomId"" = SA.""OTRoomId"" and sm.""Active"" is true
                    join ""OTRoomSurgeryMap"" SMTM on SMTM.""OTRoomId"" = sm.""OTRoomId"" 
					 join ""Surgery"" STM on STM.""SurgeryId"" = SMTM.""SurgeryId"" 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"" 
                            { where}
                    order by SA.""CreatedDate"" desc";
            return this.unitOfWork.Current.QueryAsync<OTRoomAvailabilityFilterModel>(query);


        }

        public Task<IEnumerable<DateTime>> FetchOTAppointmentDetails(int surgeryId, int otRoomId, string signInDate)
        {
            var query = $@"Select distinct TO_CHAR(""SignInDate"", 'hh12:mi AM') as ""AppointmentTime"" from ""OTRegister"" where ""SurgeryId"" = {surgeryId} AND ""OTRoomId"" = {otRoomId}  AND ""SignInDate""::Date = '{signInDate}' AND ""Active"" IS TRUE";           
            return this.unitOfWork.Current.QueryAsync<DateTime>(query);
        }
        public async Task<List<MultipleDays>> FetchSlotsForMultipleDays(SlotRequest model)
        {
            var roomName = "";
            DateTime dt = DateTime.Now;
            var date = dt.Date;
            var count = model.Count;
            string dateString = null;
            var dayWiseSlots = new List<MultipleDays>();
            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;
            }
            
            for (var n = 1; n <= count; n++)
            {
                var roomWiseSlots = new List<MultipleOTRooms>();
                if (model.FromDate != null)
                {
                    if (n == 1)
                    {
                        date = Convert.ToDateTime(model.FromDate);
                    }
                    dateString = date.ToString("yyyy-MM-dd");
                    date = date.AddDays(1);

                }
                else
                {
                    if (n == 1)
                    {
                        dt = date;
                    }
                    else
                    {
                        dt = date.AddDays(n - 1);
                    }
                    dateString = dt.ToString("yyyy-MM-dd");
                }

                var where = " WHERE 1 = 1 ";
                if ((model.SurgeryId > 0) && (model.SurgeryId != null))
                {
                    where += $@"  and STM.""SurgeryId"" = {model.SurgeryId}";
                }

                if (model.LocationId > 0)
                {
                    where += $@" and SA.""LocationId"" = {model.LocationId}";
                }
                if (dateString != null)
                {
                    where += $@" AND SA.""Availability""::DATE='{dateString}'::DATE";
                }
                var availabilityQuery = $@"select SA.""OTRoomAvailabilityId"",SA.""FromDate"",SA.""ToDate"",SA.""FromTime"",SA.""ToTime"",SA.""OTRoomId"",sm.""RoomName"",SA.""AvailableDays"",SA.""Active"",SA.""Availability"",
                                                         SA.""CreatedBy"",SA.""CreatedDate"",SA.""ModifiedBy"",SA.""ModifiedDate"",C.""FullName"" as ""CreatedByName"",SMTM.""SurgeryId"",STM.""Name"" as SurgeryName,STM.""Duration"" as ""SlotDuration"",


                     M.""FullName"" as ""ModifiedByName"",SA.""LocationId"",l.""Name"" as ""LocationName"" 
                     from ""OTRoomAvailability"" SA
                    join ""OTRoom"" sm on sm.""OTRoomId"" = SA.""OTRoomId"" and sm.""Active"" is true
                    join ""OTRoomSurgeryMap"" SMTM on SMTM.""OTRoomId"" = sm.""OTRoomId"" 
					 join ""Surgery"" STM on STM.""SurgeryId"" = SMTM.""SurgeryId"" 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"" 
                            { where}
                    order by SA.""CreatedDate"" desc";

                    var availabilityResponse = await this.unitOfWork.Current.QueryAsync<OTRoomAvailabilityFilterModel>(availabilityQuery);
                    var availabilitSlotsSorted = availabilityResponse.OrderBy(x => x.FromTime);
                    var slotDate = new DateTime();

                    var slots = new List<TimeSlotModel>();
                    var otherVisiTypeSlots = new List<TimeSlotModel>();
                    var offlineSlots = new List<TimeSlotModel>();
                    var breakSlots = new List<TimeSlotModel>();
                    var allSlots = new List<TimeSlotModel>();
                    var multipleOTRoomSlots = new MultipleOTRooms();
                    var num = 0;
                
                    if (availabilityResponse.Any())
                    {
                        foreach (var availability in availabilitSlotsSorted)
                        {
                            num = num + 100;
                            otherVisiTypeSlots = new List<TimeSlotModel>();
                            multipleOTRoomSlots = new MultipleOTRooms();
                            if (!string.IsNullOrEmpty(model.SlotDate))
                            {
                                slotDate = Convert.ToDateTime(model.SlotDate.Trim());
                            }
                            var otAppointmentDetails = await this.FetchOTAppointmentDetails((int)model.SurgeryId, (int)availability.OTRoomId, dateString);
                            var starttimetokens = availability.FromTime.Split(":");
                            //var time = otAppointmentDetails.TimeOfDay
                            var endtimetokens = availability.ToTime.Split(":");
                            roomName = availability.RoomName;
                            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)
                            {
                                ++counter;
                                var nextTime = startTime.AddMinutes((double)availability.SlotDuration);
                                var otherVisitType = new TimeSlotModel
                                {
                                    TokenNumber = counter,
                                    SlotValue = startTime.ToString("HH:mm"),
                                    SlotValue24HoursEnd = nextTime.ToString("HH:mm"),
                                    SlotName = startTime.ToString("hh:mm tt"),
                                    SlotTime = startTime.TimeOfDay,
                                    SlotName12HoursEnd = nextTime.ToString("hh:mm tt"),
                                    Duration = availability.SlotDuration,
                                    OTRoomAvailabilityId = availability.OTRoomAvailabilityId,
                                    Status = otAppointmentDetails.Any(a => a == startTime) ? SlotStatus.Booked : SlotStatus.Available,
                                    OTRoomId = availability.OTRoomId,
                                    AvailableDate = dateString,
                                    Id = num + "" + counter
                                };
                                otherVisiTypeSlots.Add(otherVisitType);
                                startTime = nextTime;
                            }
                            otherVisiTypeSlots.RemoveAt(otherVisiTypeSlots.Count - 1);
                            multipleOTRoomSlots.OtRoom = roomName;
                            multipleOTRoomSlots.Slots = new List<TimeSlotModel>();
                            multipleOTRoomSlots.Slots.AddRange(otherVisiTypeSlots);
                            roomWiseSlots.Add(multipleOTRoomSlots);
                        }
                        var multipleDays = new MultipleDays();
                        multipleDays.Slots = new List<TimeSlotModel>();
                        multipleDays.Date = Convert.ToDateTime(dateString).Date;
                    multipleDays.OtRoom = roomName;
                        multipleDays.RoomSlots = new List<MultipleOTRooms>();
                        multipleDays.RoomSlots.AddRange(roomWiseSlots);
                        dayWiseSlots.Add(multipleDays);
                    }
                    else
                    {
                        var noSlots = new MultipleDays();
                        noSlots.Date = Convert.ToDateTime(dateString).Date; ;
                        noSlots.message = "There is no slots available for this date";
                        dayWiseSlots.Add(noSlots);
                    }

                }
                return dayWiseSlots;
            
        }
    }

}
