namespace Hims.Infrastructure.Services
{
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Dapper;
    using Domain.Repositories.UnitOfWork;
    using Domain.Services;
    using Domain.Entities;
    using Shared.UserModels.NurseShift;
    using System;
    using System.Linq;
    using Hims.Domain.Configurations;
    using Hims.Shared.UserModels.NurseShift.Bed;

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

        /// <summary>
        /// The amazon s3 configuration.
        /// </summary>
        private readonly IAmazonS3Configuration configuration;

        /// <inheritdoc cref="INurseShiftMapService" />
        public NurseShiftService(IUnitOfWork unitOfWork, IAmazonS3Configuration configuration)
        {
            this.unitOfWork = unitOfWork;
            this.configuration = configuration;
        }

        public async Task<IEnumerable<BedModel>> FetchBedsAsync(BedFilterModel model)
        {
            var isLeft = model.ToDate != null ? "LEFT" : string.Empty;
            var where = model.ToDate == null
                ? $@"AND nm.""NurseShiftMapId"" = {model.NurseShiftMapId}"
                : $@"AND (('{new DateTime(model.FromDate.Year, model.FromDate.Month, model.FromDate.Day, 0, 0, 0):yyyy-MM-dd}'::DATE BETWEEN to_char(nm.""FromDate"", 'YYYY-MM-DD' )::DATE AND to_char(nm.""ToDate"", 'YYYY-MM-DD' )::DATE) 
						OR ('{new DateTime(model.ToDate.Year, model.ToDate.Month, model.ToDate.Day, 0, 0, 0):yyyy-MM-dd}'::DATE BETWEEN to_char(nm.""FromDate"", 'YYYY-MM-DD' )::DATE AND to_char(nm.""ToDate"", 'YYYY-MM-DD' )::DATE))";

            if (model.IsRetouch == true)
            {
                isLeft = string.Empty;
            }

            var locationIds = new List<int>();
            if(model.NurseAccountId != null && model.ToDate != null)
            {
                locationIds = (await this.unitOfWork.LocationAccountMap.FindAllAsync(x => x.AccountId == model.NurseAccountId))?.Select(x => x.LocationId).ToList() ?? new List<int>();
            }

            var endWhere = $@" WHERE 1 = 1";
            if(locationIds.Any())
            {
                endWhere += $@" AND f.""LocationId"" IN ({string.Join(",", locationIds.Select(x => "'" + x + "'").ToArray())}) ";
            }

            var query = $@"SELECT
							e2.*,
							b.""BedId"",
							b.""BedNumber"" ""BedName"",
							b.""BedStatusId"",
							bs.""BedStatusName"",
							r.""RoomName"",
							r.""RoomRent"",
							w.""WardName"",
							f.""FloorName"",
							e.* 
						FROM
							""Bed"" b
							JOIN ""BedStatus"" bs ON bs.""BedStatusId"" = b.""BedStatusId""
							JOIN ""Room"" r ON r.""RoomId"" = b.""RoomId""
							JOIN ""Ward"" w ON w.""WardId"" = r.""WardId""
							LEFT JOIN ""Floor"" f ON f.""FloorId"" = w.""FloorId""
							{isLeft} JOIN LATERAL (
										SELECT 
											nm.""NurseAccountId"",
											a.""FullName"",
											r.""RoleName"",
											nm.""NurseShiftMapId"",
											nm.""FromDate"",
											nm.""ToDate"",
                                            (CASE WHEN u.""ThumbnailUrl"" IS NOT NULL THEN CONCAT('{this.configuration.BucketURL}', u.""Guid"", '/', u.""ThumbnailUrl"") ELSE NULL END) AS ""NurseImage""
										FROM
											""NurseShiftMap"" nm
											JOIN ""NurseShiftBedMap"" bm on bm.""NurseShiftMapId"" = nm.""NurseShiftMapId""
											JOIN ""Account"" a on a.""AccountId"" = nm.""NurseAccountId""
                                            JOIN ""User"" u on u.""UserId"" = a.""ReferenceId"" 
											JOIN ""Role"" r on r.""RoleId"" = a.""RoleId""
											WHERE bm.""BedId"" = b.""BedId"" AND nm.""ShiftId"" = {model.ShiftId} {where}
											 
							) e2 ON TRUE
							LEFT JOIN LATERAL (
									SELECT A
										.""AdmissionNo"",
										A.""AdmissionDate"",
										P.""FullName"" ""DoctorName"",
										P.""Gender"" ""DoctorGender"",
										P.""Age"" ""DoctorAge"",
										(CASE WHEN p.""ThumbnailUrl"" IS NOT NULL THEN CONCAT('{this.configuration.BucketURL}', p.""Guid"", '/', p.""ThumbnailUrl"") ELSE NULL END) AS  ""DoctorImage"",
										d.""DepartmentName"",
										pa.""FullName"" ""PatientName"",
										pa.""Age"" ""PatientAge"",
										pa.""Gender"" ""PatientGender"",
										pa.""MaritalStatus"" ""PatientMaritalStatus"",
										pa.""UMRNo"",
										pp.""Name"" ""PriorityName"",
										pp.""Icon"" ""PriorityIcon"",
										pp.""Color"" ""PriorityColor"",
										(CASE WHEN pa.""ThumbnailUrl"" IS NOT NULL THEN CONCAT('{this.configuration.BucketURL}', pa.""Guid"", '/', pa.""ThumbnailUrl"") ELSE NULL END) AS ""PatientImage"" 
									FROM
										""Admission""
										A JOIN ""Provider"" P ON P.""ProviderId"" = A.""ProviderId""
										JOIN ""Department"" d ON d.""DepartmentId"" = A.""DepartmentId""
										JOIN ""Patient"" pa ON pa.""PatientId"" = A.""PatientId""
										LEFT JOIN ""PatientPriority"" pp ON pp.""PatientPriorityId"" = A.""PatientPriorityId"" 
										LEFT JOIN ""Discharge"" di ON di.""AdmissionId"" = A.""AdmissionId"" 
									WHERE
										A.""BedId"" = b.""BedId"" 
										AND di.""DischargeId"" IS NULL 
										AND A.""Active"" IS TRUE 
										LIMIT 1 
									) e ON TRUE
                        {endWhere}
						ORDER BY
							b.""BedId""";

            var records = await this.unitOfWork.Current.QueryAsync<BedModel>(query);
            return records;
        }

        public async Task<string> CopyShiftsAsync(CopyPayload model)
        {
            var query = $@"select * from ""Test_rotary"" ('{model.Type}','{DateTime.Now.Date}'::DATE,'{{{string.Join(",", model.NurseAccountIds)}}}'::int[])";
            return await this.unitOfWork.Current.QuerySingleAsync<string>(query);
        }

        public async Task<IEnumerable<BasicBedModel>> FetchBedsBasicAsync(GetPayload model)
        {
            var query = $@"SELECT
							b.""BedId"",
							b.""BedNumber"" ""BedName"",
							r.""RoomName"",
							w.""WardName"",
							f.""FloorName"",
							e.* 
						FROM
							""Bed"" b
							JOIN ""BedStatus"" bs ON bs.""BedStatusId"" = b.""BedStatusId""
							JOIN ""Room"" r ON r.""RoomId"" = b.""RoomId""
							JOIN ""Ward"" w ON w.""WardId"" = r.""WardId""
							LEFT JOIN ""Floor"" f ON f.""FloorId"" = w.""FloorId""
                            JOIN ""NurseShiftBedMap"" bm on bm.""BedId"" = b.""BedId""
                            JOIN ""NurseShiftMap"" nm on nm.""NurseShiftMapId"" = bm.""NurseShiftMapId""
							LEFT JOIN LATERAL (
									SELECT A
										.""AdmissionNo"",
										pa.""FullName"" ""PatientName"",
										pa.""Age"" ""PatientAge"",
										pa.""Gender"" ""PatientGender"",
										pa.""UMRNo"",
										pp.""Name"" ""PriorityName"",
										pp.""Icon"" ""PriorityIcon"",
										pp.""Color"" ""PriorityColor"",
										(CASE WHEN pa.""ThumbnailUrl"" IS NOT NULL THEN CONCAT('{this.configuration.BucketURL}', pa.""Guid"", '/', pa.""ThumbnailUrl"") ELSE NULL END) AS ""PatientImage"" 
									FROM
										""Admission"" A
										JOIN ""Patient"" pa ON pa.""PatientId"" = A.""PatientId""
										LEFT JOIN ""PatientPriority"" pp ON pp.""PatientPriorityId"" = A.""PatientPriorityId"" 
										LEFT JOIN ""Discharge"" di ON di.""AdmissionId"" = A.""AdmissionId"" 
									WHERE
										A.""BedId"" = b.""BedId"" 
										AND di.""DischargeId"" IS NULL 
										AND A.""Active"" IS TRUE 
										LIMIT 1 
									) e ON TRUE 
                        WHERE nm.""NurseAccountId"" = {model.Value}
                        AND '{new DateTime(model.Date.Year, model.Date.Month, model.Date.Day):yyyy-MM-dd}'::DATE >= nm.""FromDate""::DATE 
                        AND '{new DateTime(model.Date.Year, model.Date.Month, model.Date.Day):yyyy-MM-dd}'::DATE <= nm.""ToDate""::DATE 
                        AND f.""LocationId"" = {model.LocationId}
                        ORDER BY
							b.""BedId""";

            var records = await this.unitOfWork.Current.QueryAsync<BasicBedModel>(query);
            return records;
        }

        public async Task<NurseDetailsModel> GetNurseAsync(GetPayload model)
        {
            var query = $@"SELECT
							a.""AccountId"",
							a.""FullName"",
							r.""RoleName"",
							a.""Email"",
							a.""Mobile"",
							c.""CountryCode"",
                            (CASE WHEN u.""ThumbnailUrl"" IS NOT NULL THEN CONCAT('{this.configuration.BucketURL}', u.""Guid"", '/', u.""ThumbnailUrl"") ELSE NULL END) AS ""NurseImage""
						FROM
							""Account"" A
                            JOIN ""User"" u on u.""UserId"" = a.""ReferenceId"" 
							JOIN ""Role"" r ON r.""RoleId"" = A.""RoleId"" 
							LEFT JOIN ""Country"" c on c.""CountryId"" = a.""CountryId""
						WHERE
							""AccountId"" = {model.Value}";

            var record = await this.unitOfWork.Current.QuerySingleAsync<NurseDetailsModel>(query);
            return record;
        }

        public async Task<IEnumerable<ViewModel>> FetchAsync(GetPayload model)
        {
            var today = DateTime.Now.Date;
            model.Date = model.Date ?? new DateModel
            {
                Day = today.Day,
                Month = today.Month,
                Year = today.Year,
            };
            var where = $@" AND (('{new DateTime(model.Date.Year, model.Date.Month, model.Date.Day, 0, 0, 0):yyyy-MM-dd}'::DATE BETWEEN to_char(nm.""FromDate"", 'YYYY-MM-DD' )::DATE AND to_char(nm.""ToDate"", 'YYYY-MM-DD' )::DATE)
                    OR to_char(nm.""FromDate"", 'YYYY-MM-DD' )::DATE >= '{new DateTime(model.Date.Year, model.Date.Month, model.Date.Day, 0, 0, 0):yyyy-MM-dd}'::DATE)";

            var query = $@"SELECT
							nm.""NurseAccountId"",
							nm.""NurseShiftMapId"",
							nm.""FromDate"",
							nm.""ToDate"",
							nm.""ShiftId"",
							ARRAY(
							select bm.""BedId"" FROM ""NurseShiftBedMap"" bm WHERE bm.""NurseShiftMapId"" = nm.""NurseShiftMapId""
							) AS ""BedIds""
						FROM
							""NurseShiftMap"" nm
						WHERE
							""NurseAccountId"" = {model.Value} {where}
                        ORDER BY nm.""FromDate""";

            var record = await this.unitOfWork.Current.QueryAsync<ViewModel>(query);
            return record;
        }

        public async Task<int> InsertAsync(InsertModel model)
        {
            var transaction = this.unitOfWork.BeginTransaction();

            foreach (var item in model.Shifts)
            {
                var nurseShiftMapId = await this.unitOfWork.NurseShiftMap.InsertAsync(new NurseShiftMap
                {
                    CreatedBy = model.CreatedBy,
                    CreatedDate = DateTime.Now,
                    FromDate = new DateTime(item.FromDate.Year, item.FromDate.Month, item.FromDate.Day, 0, 0, 0),
                    ToDate = new DateTime(item.ToDate.Year, item.ToDate.Month, item.ToDate.Day, 0, 0, 0),
                    IsShiftActive = true,
                    NurseAccountId = model.NurseId,
                    ShiftId = item.ShiftId
                }, transaction);

                if (nurseShiftMapId <= 0)
                {
                    transaction.Rollback();
                    return -1;
                }

                var nurseShiftBedMap = await this.unitOfWork.NurseShiftBedMap.BulkInsertAsync(item.BedIds.Select(x => new NurseShiftBedMap
                {
                    BedId = x,
                    CreatedBy = model.CreatedBy,
                    CreatedDate = DateTime.Now,
                    NurseShiftMapId = nurseShiftMapId
                }), transaction);

                if (nurseShiftBedMap <= 0)
                {
                    transaction.Rollback();
                    return -1;
                }
            }

            transaction.Commit();
            return 1;
        }

        public async Task<int> MoveAsync(MoveModel model)
        {
            var data = new MedicationMove
            {
                ProgressReportMedicationFrequencyId = model.ProgressReportMedicationFrequencyId,
                CreatedBy = model.CreatedBy,
                CreatedDate = DateTime.Now,
                Hour = model.Hour ?? 0,
                Minute = model.Minute ?? 0,
                Reason = model.Reason
            };
            var response = await this.unitOfWork.MedicationMove.InsertAsync(data);
            return response;
        }

        public async Task<IEnumerable<MoveViewModel>> FetchMovesAsync(GetPayload model)
        {
            var query = $@"SELECT
	                        ""Hour"",
	                        ""Minute"",
	                        ""Reason"" 
                        FROM
	                        ""MedicationMove"" 
                        WHERE
	                        ""ProgressReportMedicationFrequencyId"" = {model.Value}
	                        ORDER BY  ""ProgressReportMedicationFrequencyId"" DESC";
            var response = await this.unitOfWork.Current.QueryAsync<MoveViewModel>(query);
            return response;
        }

        public async Task<int> UpdateAsync(UpdateModel model)
        {
            var transaction = this.unitOfWork.BeginTransaction();

            var firstMap = model.Shifts.First();
            if (model.IsSingle && firstMap.NurseShiftMapId != null)
            {
                // Edit Single
                var map = await this.unitOfWork.NurseShiftMap.FindAsync(x => x.NurseShiftMapId == firstMap.NurseShiftMapId, transaction);
                var dateTimeDate = new DateTime(firstMap.FromDate.Year, firstMap.FromDate.Month, firstMap.FromDate.Day, 0, 0, 0);
                if (DateTime.Compare(map.FromDate, dateTimeDate) != 0 || DateTime.Compare(map.ToDate, dateTimeDate) != 0)
                {
                    map.ModifiedBy = model.CreatedBy;
                    map.ModifiedDate = DateTime.Now;
                    if (DateTime.Compare(map.FromDate, dateTimeDate) == 0)
                    {
                        // First Date
                        map.FromDate = dateTimeDate.AddDays(1);
                    }
                    else if (DateTime.Compare(map.ToDate, dateTimeDate) == 0)
                    {
                        // Last Date
                        map.ToDate = dateTimeDate.AddDays(-1);
                    }
                    else
                    {
                        // Middle Date
                        var cloneMap = new NurseShiftMap
                        {
                            CreatedDate = map.CreatedDate,
                            CreatedBy = map.CreatedBy,
                            IsShiftActive = map.IsShiftActive,
                            ModifiedBy = map.ModifiedBy,
                            ModifiedDate = map.ModifiedDate,
                            NurseAccountId = map.NurseAccountId,
                            ShiftId = map.ShiftId,
                            ToDate = map.ToDate,
                            FromDate = dateTimeDate.AddDays(1)
                        };
                        var cloneMapId = await this.unitOfWork.NurseShiftMap.InsertAsync(cloneMap, transaction);
                        if (cloneMapId <= 0)
                        {
                            transaction.Rollback();
                            return -1;
                        }

                        var mapBeds = await this.unitOfWork.NurseShiftBedMap.FindAllAsync(x => x.NurseShiftMapId == firstMap.NurseShiftMapId, transaction);
                        var cloneMapBeds = mapBeds.Select(x => new NurseShiftBedMap
                        {
                            BedId = x.BedId,
                            CreatedBy = x.CreatedBy,
                            CreatedDate = x.CreatedDate,
                            NurseShiftMapId = cloneMapId
                        });
                        var cloneMapBedIds = await this.unitOfWork.NurseShiftBedMap.BulkInsertAsync(cloneMapBeds, transaction);
                        if (cloneMapBedIds <= 0)
                        {
                            transaction.Rollback();
                            return -1;
                        }

                        map.ToDate = dateTimeDate.AddDays(-1);
                    }

                    var updateResponse = await this.unitOfWork.NurseShiftMap.UpdateAsync(map, transaction);
                    if (updateResponse <= 0)
                    {
                        transaction.Rollback();
                        return -1;
                    }

                    if (!model.IsRemove)
                    {
                        var newMap = new NurseShiftMap
                        {
                            CreatedDate = DateTime.Now,
                            CreatedBy = model.CreatedBy,
                            IsShiftActive = true,
                            NurseAccountId = model.NurseId,
                            ShiftId = firstMap.ShiftId,
                            ToDate = dateTimeDate,
                            FromDate = dateTimeDate
                        };
                        var newMapId = await this.unitOfWork.NurseShiftMap.InsertAsync(newMap, transaction);
                        if (newMapId <= 0)
                        {
                            transaction.Rollback();
                            return -1;
                        }

                        var newBedIds = await this.unitOfWork.NurseShiftBedMap.BulkInsertAsync(firstMap.BedIds.Select(x => new NurseShiftBedMap
                        {
                            BedId = x,
                            CreatedBy = model.CreatedBy,
                            CreatedDate = DateTime.Now,
                            NurseShiftMapId = newMapId
                        }), transaction);

                        if (newBedIds <= 0)
                        {
                            transaction.Rollback();
                            return -1;
                        }
                    }

                    transaction.Commit();
                    return 1;
                }
            }

            foreach (var item in model.Shifts)
            {

                if (item.NurseShiftMapId == null)
                {
                    // Add
                    var nurseShiftMapId = await this.unitOfWork.NurseShiftMap.InsertAsync(new NurseShiftMap
                    {
                        CreatedBy = model.CreatedBy,
                        CreatedDate = DateTime.Now,
                        FromDate = new DateTime(item.FromDate.Year, item.FromDate.Month, item.FromDate.Day, 0, 0, 0),
                        ToDate = new DateTime(item.ToDate.Year, item.ToDate.Month, item.ToDate.Day, 0, 0, 0),
                        IsShiftActive = true,
                        NurseAccountId = model.NurseId,
                        ShiftId = item.ShiftId
                    }, transaction);

                    if (nurseShiftMapId <= 0)
                    {
                        transaction.Rollback();
                        return -1;
                    }

                    var nurseShiftBedMap = await this.unitOfWork.NurseShiftBedMap.BulkInsertAsync(item.BedIds.Select(x => new NurseShiftBedMap
                    {
                        BedId = x,
                        CreatedBy = model.CreatedBy,
                        CreatedDate = DateTime.Now,
                        NurseShiftMapId = nurseShiftMapId
                    }), transaction);

                    if (nurseShiftBedMap <= 0)
                    {
                        transaction.Rollback();
                        return -1;
                    }
                }
                else if (item.IsRemove)
                {
                    // Remove
                    var deleteResponse = await this.unitOfWork.NurseShiftMap.DeleteAsync(x => x.NurseShiftMapId == item.NurseShiftMapId, transaction);
                    if (!deleteResponse)
                    {
                        transaction.Rollback();
                        return -1;
                    }
                }
                else
                {
                    // Edit
                    var record = await this.unitOfWork.NurseShiftMap.FindAsync(x => x.NurseShiftMapId == item.NurseShiftMapId, transaction);
                    record.ModifiedBy = model.CreatedBy;
                    record.ModifiedDate = DateTime.Now;
                    record.ShiftId = item.ShiftId;
                    record.FromDate = new DateTime(item.FromDate.Year, item.FromDate.Month, item.FromDate.Day, 0, 0, 0);
                    record.ToDate = new DateTime(item.ToDate.Year, item.ToDate.Month, item.ToDate.Day, 0, 0, 0);

                    var updateResponse = await this.unitOfWork.NurseShiftMap.UpdateAsync(record, transaction);

                    if (updateResponse <= 0)
                    {
                        transaction.Rollback();
                        return -1;
                    }

                    if (item.AddedBedIds.Count > 0)
                    {
                        var nurseShiftBedMap = await this.unitOfWork.NurseShiftBedMap.BulkInsertAsync(item.AddedBedIds.Select(x => new NurseShiftBedMap
                        {
                            BedId = x,
                            CreatedBy = model.CreatedBy,
                            CreatedDate = DateTime.Now,
                            NurseShiftMapId = item.NurseShiftMapId ?? 0
                        }), transaction);

                        if (nurseShiftBedMap <= 0)
                        {
                            transaction.Rollback();
                            return -1;
                        }
                    }

                    if (item.RemovedBedIds.Count > 0)
                    {
                        foreach (var subItem in item.RemovedBedIds)
                        {
                            var deleteResponse = await this.unitOfWork.NurseShiftBedMap.DeleteAsync(x => x.NurseShiftMapId == item.NurseShiftMapId && x.BedId == subItem, transaction);
                            if (!deleteResponse)
                            {
                                transaction.Rollback();
                                return -1;
                            }
                        }
                    }

                }
            }

            transaction.Commit();
            return 1;
        }

    }
}