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

    /// <summary>
    /// The timeline service.
    /// </summary>
    public class TimelineService : ITimelineService
    {
        /// <summary>
        /// The unit of work.
        /// </summary>
        private readonly IUnitOfWork unitOfWork;

        private readonly IRunningEnvironment runningEnvironment;

        /// <inheritdoc cref="ITimelineService" />
        public TimelineService(IUnitOfWork unitOfWork, IRunningEnvironment runningEnvironment)
        {
            this.unitOfWork = unitOfWork;
            this.runningEnvironment = runningEnvironment;
        }

        /// <inheritdoc />
        public async Task<int> LogAsync(TimelineModel model)
        {
            try
            {
                var data = new Timeline()
                {
                    AppointmentId = model.AppointmentId,
                    AdmissionId = model.AdmissionId,
                    CreatedBy = model.CreatedBy,
                    CreatedDate = DateTime.Now,
                    Description = model.Description,
                    PatientId = model.PatientId,
                    TimelineActionId = model.TimelineActionId
                };
                var timelineId = await this.unitOfWork.Timeline.InsertAsync(data);

                if (model.Ids != null && model.Ids.Count > 0)
                {
                    var list = model.Ids.Select(id => new TimelineData { Id = id, TimelineId = timelineId }).ToList();
                    await this.unitOfWork.TimelineData.BulkInsertAsync(list);
                }
                else if (!string.IsNullOrEmpty(model.Data))
                {
                    await this.unitOfWork.TimelineData.InsertAsync(new TimelineData
                    {
                        Data = model.Data,
                        TimelineId = timelineId
                    });
                }

                return timelineId;
            }
            catch (Exception)
            {
                return -1;
            }
        }

        /// <inheritdoc />
        public async Task<IEnumerable<TimelineViewModel>> FetchAsync(TimelineRequestModel model)
        {
            try
            {
                var patientId = model.IsAdmission
                   ? (await this.unitOfWork.Admission.FindAsync(x => x.AdmissionId == model.AppointmentId)).PatientId
                   : (await this.unitOfWork.Appointments.FindAsync(x => x.AppointmentId == model.AppointmentId)).PatientId;
                var where = $@" WHERE t.""PatientId"" = {patientId}";
                if(model.TimelineActionId != 0)
                {
                    where += $@" and  t.""TimelineActionId"" = {model.TimelineActionId}";
                }

                var query = $@"SELECT COUNT(T.""TimelineId"") OVER() AS ""TotalItems"", t.""TimelineActionId"", t.""TimelineId"", t.""CreatedDate"", ta.""Name"", t.""Description"", a.""AppointmentNo"", p.""FullName"", ttd.""TimelineDataId"", ttd.""Data"",
                            Pr.""FullName"" as ""ProviderName"", S.""SpecializationName"", V.""VisitorName"", AC.""FullName"",PD.""DocumentName"", PD.""DocumentType"",A.""AppointmentDate""
                            FROM ""Timeline""
	                        T JOIN ""TimelineAction"" ta ON ta.""TimelineActionId"" = T.""TimelineActionId""
	                        LEFT JOIN ""Appointment"" A ON A.""AppointmentId"" = T.""AppointmentId""
							LEFT JOIN ""Provider"" Pr on Pr.""ProviderId""=A.""ProviderId""
							LEFT JOIN ""Specialization"" S on S.""SpecializationId""=A.""SpecializationId""
	                        LEFT JOIN ""Patient"" P ON P.""PatientId"" = T.""PatientId""
							LEFT JOIN ""VisitType"" V ON V.""VisitTypeId""=A.""VisitTypeId""
							LEFT JOIN ""Account"" AC ON AC.""AccountId""=T.""CreatedBy""
							Left Join ""PatientDocument"" PD ON PD.""PatientId""=T.""PatientId""
                            LEFT JOIN LATERAL
                             (SELECT ""TimelineDataId"", ""Data""
                              FROM ""TimelineData"" td
                              WHERE t.""TimelineId"" = td.""TimelineId""
                              FETCH FIRST 1 ROW ONLY
                             ) ttd
                             ON true
                           {where}
                            ORDER BY T.""TimelineId"" DESC ";
                query += " LIMIT " + model.Take + " offset " + model.Skip;
                return await this.unitOfWork.Current.QueryAsync<TimelineViewModel>(query);
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// The fetch view async.
        /// </summary>
        /// <param name="id">
        /// The id.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<TimelineDataViewModel> FetchViewAsync(int id)
        {
            try
            {
                var timeline = await this.unitOfWork.Timeline.FindAsync(x => x.TimelineId == id);

                IEnumerable<TimelineData> timelineData;
                List<TimelineData> timelineDataList;
                switch (timeline.TimelineActionId)
                {
                    case TimelineAction.DocumentAdded:
                    case TimelineAction.DocumentEdited:

                        timelineData = await this.unitOfWork.TimelineData.FindAllAsync(x => x.TimelineId == id);
                        var documents = new List<PatientDocumentModel>();
                        timelineDataList = timelineData.ToList();
                        if (!timelineDataList.Any())
                        {
                            return new TimelineDataViewModel
                            {
                                Documents = documents
                            };
                        }

                        var documentIds = string.Join(',', timelineDataList.Select(x => x.Id).ToArray());
                        var query = $@"SELECT COUNT(*) OVER () AS ""TotalItems"", prd.""PatientId"", prd.""PatientDocumentId"", prd.""DocumentType"", prd.""DocumentName"", CASE WHEN prd.""Description"" IS NULL THEN '' ELSE prd.""Description"" END ""Description"", prd.""ContentType"", prd.""Size"",prd.""IsRead"",
                                prd.""UploadedDate"", prd.""UploadedBy"", act.""FullName"" AS ""UploadedByName"", rl.""RoleName"" AS ""UploadedByRole"", pat.""FullName"" AS ""PatientName"",
                                (CASE WHEN prd.""ThumbnailUrl"" IS NOT NULL THEN CONCAT('{this.runningEnvironment.CurrentEnvironment}', prd.""ThumbnailUrl"") ELSE NULL END) AS ""ThumbnailUrl"",
                                (CASE WHEN prd.""DocumentUrl"" IS NOT NULL THEN CONCAT('{this.runningEnvironment.CurrentEnvironment}', '/', pat.""Guid"",'/' ,prd.""DocumentType"" , '/', prd.""DocumentUrl"") ELSE NULL END) AS ""DocumentUrl""
                                FROM ""PatientDocument"" prd
                                JOIN ""Patient"" pat ON pat.""PatientId"" = prd.""PatientId"" AND pat.""Active"" IS TRUE
                                JOIN ""Account"" act ON act.""AccountId"" = prd.""UploadedBy"" AND act.""Active"" IS TRUE
                                JOIN ""Role"" rl ON rl.""RoleId"" = act.""RoleId"" AND rl.""Active"" IS TRUE
                                WHERE prd.""PatientDocumentId"" In ({documentIds}) AND prd.""PatientId"" = {timeline.PatientId} ORDER BY prd.""PatientDocumentId"" DESC";

                        documents = (await this.unitOfWork.Current.QueryAsync<PatientDocumentModel>(query)).ToList();

                        return new TimelineDataViewModel
                        {
                            Documents = documents
                        };
                    case TimelineAction.FollowUpAdded:
                    case TimelineAction.FollowUpUpdated:
                    case TimelineAction.NotesAdded:
                    case TimelineAction.NotesUpdated:
                    case TimelineAction.MedicationsAdded:
                    case TimelineAction.MedicationsUpdated:

                        timelineData = await this.unitOfWork.TimelineData.FindAllAsync(x => x.TimelineId == id);
                        timelineDataList = timelineData.ToList();
                        if (!timelineDataList.Any())
                        {
                            return new TimelineDataViewModel();
                        }

                        return new TimelineDataViewModel
                        {
                            Data = timelineDataList.First().Data
                        };
                    default:
                        return new TimelineDataViewModel();
                }
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}
