namespace Hims.Infrastructure.Services
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;

    using Dapper;

    using Domain.Repositories.UnitOfWork;
    using Domain.Services;
    using Hims.Domain.Configurations;
    using Hims.Domain.Entities;
    using Hims.Shared.UserModels;
    using Hims.Shared.UserModels.Common;
    using Hims.Shared.UserModels.NurseShift.BedView;
    using Hims.Shared.UserModels.NurseShift.Module;
    using Hims.Shared.UserModels.NurseShift.Shift;
    using Shared.EntityModels;

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

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

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

        /// <inheritdoc />
        public async Task<IEnumerable<NurseModel>> FetchAsync(NurseFilterModel model)
        {
            var isNurse = false;

            if (model.NurseId != null)
            {
                var account = await this.unitOfWork.Accounts.FindAsync(x => x.AccountId == model.NurseId);
                var role = await this.unitOfWork.Roles.FindAsync(x => x.RoleId == account.RoleId);
                isNurse = role.RoleConcept == "N";
            }

            var nurseLink = isNurse ? $@"AND A.""AccountId"" = {model.NurseId}" : string.Empty;
            var query = $@"SELECT A.""GeneralDate"", A.""ShiftName"", A.""ShiftId"", A.""NurseShiftMapId"", A.""AccountId"", A.""BedIds"", A.""FullName"", A.""RoleName"", A.""ThumbnailUrl"" FROM (SELECT
	                                generate_series ( M.""FromDate"", M.""ToDate"", '1 day' ) :: DATE AS ""GeneralDate"",
	                                M.""NurseShiftMapId"",
	                                M.""FromDate"",
	                                M.""ToDate"",
	                                M.""ShiftId"",
	                                a.""AccountId"",
                                   l.""LocationId"",
	                                s.""Name"" AS ""ShiftName"",
	                                M.""NurseAccountId"",
                                    ARRAY ( 
                                        SELECT bm.""BedId"" FROM ""NurseShiftBedMap"" bm
                                        JOIN ""Bed"" ab on ab.""BedId"" = bm.""BedId""
										JOIN ""Room"" ar on ar.""RoomId"" = ab.""RoomId""
										JOIN ""Ward"" aw on aw.""WardId"" = ar.""WardId""
                                        JOIN ""Floor"" af on af.""FloorId"" = aw.""FloorId""
                                        WHERE bm.""NurseShiftMapId"" = m.""NurseShiftMapId"" AND af.""LocationId"" = {model.LocationId}
                                    ) AS ""BedIds"",
	                                a.""FullName"",
	                                r.""RoleName"",
                                    (CASE WHEN u.""ThumbnailUrl"" IS NOT NULL THEN CONCAT('{this.configuration.BucketURL}', u.""Guid"", '/', u.""ThumbnailUrl"") ELSE NULL END) AS ""ThumbnailUrl""
                                FROM
	                                ""NurseShiftMap"" M
	                                JOIN ""Account"" a on a.""AccountId"" = m.""NurseAccountId""
                                    JOIN ""LocationAccountMap"" lm on lm.""AccountId"" = a.""AccountId""
                                    JOIN ""Location"" l on l.""LocationId"" = lm.""LocationId""
                                    JOIN ""User"" u on u.""UserId"" = a.""ReferenceId"" 
	                                JOIN ""Shift"" s on s.""ShiftId"" = m.""ShiftId""
	                                JOIN ""Role"" r on r.""RoleId"" = a.""RoleId"") A
	                                WHERE A.""GeneralDate""::DATE >= '{new DateTime(model.FromDate.Year, model.FromDate.Month, model.FromDate.Day, 0, 0, 0):yyyy-MM-dd}'::DATE 
                                        AND A.""GeneralDate""::DATE <= '{new DateTime(model.ToDate.Year, model.ToDate.Month, model.ToDate.Day, 0, 0, 0):yyyy-MM-dd}'::DATE 
                                        {nurseLink} AND A.""LocationId"" = {model.LocationId}
                                    ORDER BY a.""AccountId"" ";

            var result = await this.unitOfWork.Current.QueryAsync<NurseModel>(query);
            var records = result.ToList();
            query = $@"	SELECT A
		                    .""FullName"",
		                    a.""AccountId"",
		                    r.""RoleName"",
                            (CASE WHEN u.""ThumbnailUrl"" IS NOT NULL THEN CONCAT('{this.configuration.BucketURL}', u.""Guid"", '/', u.""ThumbnailUrl"") ELSE NULL END) AS ""ThumbnailUrl""
	                    FROM
		                    ""Account"" a
                            JOIN ""LocationAccountMap"" lm on lm.""AccountId"" = a.""AccountId""
                            JOIN ""Location"" l on l.""LocationId"" = lm.""LocationId""
                            JOIN ""User"" u on u.""UserId"" = a.""ReferenceId""
		                    JOIN ""Role"" r ON r.""RoleId"" = A.""RoleId""
		                    WHERE r.""RoleConcept"" = 'N' {nurseLink} AND l.""LocationId"" = { model.LocationId}";
            var unassigned = await this.unitOfWork.Current.QueryAsync<NurseModel>(query);
            var assignedIds = records.Select(x => x.AccountId).ToArray();
            unassigned = unassigned.Where(x => !assignedIds.Contains(x.AccountId));
            records.AddRange(unassigned);
            records = records.OrderByDescending(x => x.AccountId).ToList();
            return records;
        }

        /// <inheritdoc />
        public Task<IEnumerable<ShiftSlotModel>> FetchSlotsAsync()
        {
            var query = $@"SELECT * from ""ShiftSlot""";
            var result = this.unitOfWork.Current.QueryAsync<ShiftSlotModel>(query);
            return result;
        }

        /// <inheritdoc />
        public async Task<int> InsertSlotsAsync(ShiftSlotModel model)
        {

            var slot = new ShiftSlot
            {
                StartTime = model.StartTime,
                EndTime = model.EndTime,
                Active = true
            };
            return await this.unitOfWork.ShiftSlots.InsertAsync(slot);
        }

        /// <inheritdoc />
        public async Task<GenericResponse> InsertShiftAsync(ShiftModel model)
        {

            var query = $@"SELECT ""ShiftId"" from ""Shift"" WHERE UPPER(TRIM(""Name"")) = UPPER(TRIM('{model.Name}'))";
            var isExists = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<int>(query);
            if (isExists > 0)
            {
                return new GenericResponse
                {
                    Status = GenericStatus.Info
                };
            }

            var transaction = this.unitOfWork.BeginTransaction();
            var shift = new Shift
            {
                CreatedBy = (int)model.CreatedBy,
                CreatedDate = DateTime.Now,
                Name = model.Name,
                Active = true
            };

            var shiftId = await this.unitOfWork.Shifts.InsertAsync(shift, transaction);
            if (shiftId <= 0)
            {
                transaction.Rollback();
                return new GenericResponse
                {
                    Status = GenericStatus.Error
                };
            }

            if (model.Slots.Count > 0)
            {
                var shiftslotMaps = model.Slots.Select(x => new ShiftSlotMap()
                {
                    ShiftId = shiftId,
                    ShiftSlotId = x
                });

                var insertResponse = await this.unitOfWork.ShiftSlotMap.BulkInsertAsync(shiftslotMaps, transaction);
                if (insertResponse <= 0)
                {
                    transaction.Rollback();
                    return new GenericResponse
                    {
                        Status = GenericStatus.Error
                    };
                }
            }

            transaction.Commit();
            return new GenericResponse
            {
                Status = GenericStatus.Success
            };
        }

        public async Task<GenericResponse> UpdateShiftAsync(ShiftModel model)
        {
            var query = $@"SELECT ""ShiftId"" from ""Shift"" WHERE ""ShiftId"" = {model.ShiftId}";
            var isExists = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<int>(query);

            var transaction = this.unitOfWork.BeginTransaction();
            if (isExists > 0)
            {
                var shift = await this.unitOfWork.Shifts.FindAsync(x => x.ShiftId == model.ShiftId, transaction);

                shift.ModifiedBy = model.ModifiedBy;
                shift.ModifiedDate = DateTime.Now;
                shift.Name = model.Name;

                var updateResponse = await this.unitOfWork.Shifts.UpdateAsync(shift, transaction);

                if (updateResponse <= 0)
                {
                    transaction.Rollback();
                    return new GenericResponse
                    {
                        Status = GenericStatus.Error
                    };
                }

                if (model.Slots.Count > 0)
                {
                    await this.unitOfWork.ShiftSlotMap.DeleteAsync(x => x.ShiftId == model.ShiftId);

                    var shiftslotMaps = model.Slots.Select(x => new ShiftSlotMap()
                    {
                        ShiftId = (int)model.ShiftId,
                        ShiftSlotId = x
                    });

                    var insertResponse = await this.unitOfWork.ShiftSlotMap.BulkInsertAsync(shiftslotMaps, transaction);
                    if (insertResponse <= 0)
                    {
                        transaction.Rollback();
                        return new GenericResponse
                        {
                            Status = GenericStatus.Error
                        };
                    }
                }
            }

            transaction.Commit();
            return new GenericResponse
            {
                Status = GenericStatus.Success
            };
        }

        /// <inheritdoc />
        public Task<IEnumerable<ShiftsModel>> FetchShiftAsync(ShiftsFilterModel model)
        {
            model.PageIndex -= 1;
            var pagination = " LIMIT " + model.PageSize + " offset " + (model.PageIndex * model.PageSize);
            var query = $@"SELECT A
	                                .*,
	                                ss.""StartTime"",
	                                ss.""EndTime"",
                                    ss.""ShiftSlotId"",
	                                M.""ShiftSlotMapId"" 
                                
                                FROM
	                                ( SELECT COUNT ( s.""ShiftId"" ) OVER ( ) ""TotalItems"", s.""Name"", s.""ShiftId"",s.""Active"" FROM ""Shift"" s
                                    order by s.""ShiftId"" desc 
                                    {pagination} )
	                                A LEFT JOIN ""ShiftSlotMap"" M ON M.""ShiftId"" = A.""ShiftId""
	                                LEFT JOIN ""ShiftSlot"" ss ON ss.""ShiftSlotId"" = M.""ShiftSlotId"" ";

            var result = this.unitOfWork.Current.QueryAsync<ShiftsModel>(query);

            return result;
        }
        
        public Task<IEnumerable<ShiftsBasicsModel>> FetchShiftsBasicsAsync()
        { 
            var query = $@"select DISTINCT ON(A.""Name"") A.""Name"", ss.""StartTime""::TEXT, ss.""EndTime""::TEXT from ""Shift"" A
                LEFT JOIN ""ShiftSlotMap"" M ON M.""ShiftId"" = A.""ShiftId""
                LEFT JOIN ""ShiftSlot"" ss ON ss.""ShiftSlotId"" = M.""ShiftSlotId"" WHERE a.""Active"" IS TRUE ";

            return this.unitOfWork.Current.QueryAsync<ShiftsBasicsModel>(query);
        }

        public async Task<int> ModifyStatusAsync(ShiftModel model)
        {
            var query = $@"UPDATE ""Shift"" SET ""ModifiedBy""={model.CreatedBy}, ""ModifiedDate""=now(), ""Active""= {model.Active}
	                           WHERE ""ShiftId""= {model.ShiftId}";
            return await this.unitOfWork.Current.ExecuteAsync(query);
        }

        public async Task<IEnumerable<AvailableNurseModel>> GetAvailableNurseAsync(AvailableNursePayloadModel model)
        {
            var query = $@"select 
                            a.""AccountId"", Lower(a.""FullName"") ""FullName"", Lower(r.""RoleName"") ""RoleName""
                            from ""Account"" a
                            JOIN ""LocationAccountMap"" lm on lm.""AccountId"" = a.""AccountId""
                            JOIN ""Role"" r on r.""RoleId"" = a.""RoleId""
                            WHERE lm.""LocationId"" IN ({model.LocationId})
                            AND r.""RoleConcept"" = 'N'
                            AND a.""AccountId"" NOT IN (
	                            SELECT nm.""NurseAccountId"" FROM
	                            ""NurseShiftMap"" nm
	                            JOIN ""Shift"" s on s.""ShiftId"" = nm.""ShiftId""
	                            JOIN ""NurseShiftBedMap"" bm on bm.""NurseShiftMapId"" = nm.""NurseShiftMapId""
	                            WHERE (make_date({model.Date.Year}, {model.Date.Month}, {model.Date.Day})::DATE BETWEEN nm.""FromDate"" AND nm.""ToDate"")
	                            AND bm.""BedId"" IN ({model.BedId})
	                            AND s.""Name"" IN ('{model.ShiftName}')
                            )";
            return await this.unitOfWork.Current.QueryAsync<AvailableNurseModel>(query);
        }

        public async Task<IEnumerable<BedViewFetchModel>> FetchBedWiseAsync(BedViewFilterModel model)
        {
            var where = @$" WHERE (la.""LocationId"" = {model.LocationId} OR la.""LocationId"" IS NULL) AND (f.""LocationId"" = {model.LocationId}) ";

            if(model.FromDate != null && model.ToDate == null)
            {
                where += $@" AND (('{new DateTime(model.FromDate.Year, model.FromDate.Month, model.FromDate.Day, 0, 0, 0):yyyy-MM-dd}'::DATE 
                    BETWEEN to_char(nf.""FromDate"", 'YYYY-MM-DD' )::DATE AND to_char(nf.""ToDate"", 'YYYY-MM-DD' )::DATE) OR nf.""FromDate"" IS NULL)";
            }

            if(model.ToDate != null)
            {
                where += $@" AND (";
                var currentDate = new DateTime(model.FromDate.Year, model.FromDate.Month, model.FromDate.Day, 0, 0, 0);
                var endDate = new DateTime(model.ToDate.Year, model.ToDate.Month, model.ToDate.Day, 0, 0, 0);
                var itirator = 0;
                while (currentDate <= endDate)
                {
                    if(itirator == 0)
                    {
                        where += "(";
                    } else
                    {
                        where += " OR ";
                    }

                    where += $@" ('{new DateTime(currentDate.Year, currentDate.Month, currentDate.Day, 0, 0, 0):yyyy-MM-dd}'::DATE 
                                    BETWEEN to_char(nf.""FromDate"", 'YYYY-MM-DD' )::DATE AND to_char(nf.""ToDate"", 'YYYY-MM-DD' )::DATE)";

                    ++itirator;
                    currentDate = currentDate.AddDays(1);

                    if(itirator == 7)
                    {
                        where += @") OR nf.""FromDate"" IS NULL )";
                    }
                }
            }

            if (!string.IsNullOrEmpty(model.FloorName))
            {
                where += $@" AND f.""FloorName"" = '{model.FloorName}' ";
            }
            if (!string.IsNullOrEmpty(model.WardName))
            {
                where += $@" AND w.""WardName"" = '{model.WardName}' ";
            }
            if (!string.IsNullOrEmpty(model.RoomName))
            {
                where += $@" AND r.""RoomName"" = '{model.RoomName}' ";
            }

            var query = $@"SELECT a.""BedId"", a.""BedName"",
                            a.""RoomName"",
                            a.""WardName"",
                            a.""FloorName"",
                            a.""ChargeCategoryName"",
							STRING_AGG (a.""ShiftDetails"" || '%' || a.""FromDate"" || '|' || a.""ToDate"", '^') AS ""ShiftDetails""
							FROM ( select b.""BedId"", b.""BedNumber"" ""BedName"",
                            r.""RoomName"",
                            w.""WardName"",
                            f.""FloorName"",
                            cc.""ChargeCategoryName"",
                            nf.""FromDate"",
							nf.""ToDate"",
                            STRING_AGG (n.""FullName"" || '|' || s.""Name"" || '|' || ss.""StartTime"" || '|' || ss.""EndTime"", ',') AS ""ShiftDetails""
                            from ""Bed"" b
                            JOIN ""Room"" r on r.""RoomId"" = b.""RoomId""
                            JOIN ""Ward"" w on w.""WardId"" = r.""WardId""
                            JOIN ""Floor"" f on f.""FloorId"" = w.""FloorId""
                            JOIN ""ChargeCategory"" cc on cc.""ChargeCategoryId"" = r.""ChargeCategoryId""
                            LEFT JOIN ""NurseShiftBedMap"" nfb on nfb.""BedId"" = b.""BedId""
                            LEFT JOIN ""NurseShiftMap"" nf on nf.""NurseShiftMapId"" = nfb.""NurseShiftMapId""
                            LEFT JOIN ""Account"" n on n.""AccountId"" = nf.""NurseAccountId""
                            LEFT JOIN ""LocationAccountMap"" la on la.""AccountId"" = n.""AccountId""
                            LEFT JOIN ""Shift"" s on s.""ShiftId"" = nf.""ShiftId""
                            LEFT JOIN ""ShiftSlotMap"" ssm on ssm.""ShiftId"" = s.""ShiftId""
                            LEFT JOIN ""ShiftSlot"" ss on ss.""ShiftSlotId"" = ssm.""ShiftSlotId""
                            {where}
                            GROUP BY b.""BedId"", b.""BedNumber"",
                            r.""RoomName"",
                            w.""WardName"",
                            f.""FloorName"",
                            nf.""FromDate"",
							nf.""ToDate"",
                            cc.""ChargeCategoryName"") A
							GROUP BY a.""BedId"", a.""BedName"",
                            a.""RoomName"",
                            a.""WardName"",
                            a.""FloorName"",
                            a.""ChargeCategoryName"" ";
            return await this.unitOfWork.Current.QueryAsync<BedViewFetchModel>(query);
        }
    }
}