namespace Hims.Infrastructure.Services
{
    using System;
    using System.Threading.Tasks;

    using Dapper;

    using Hims.Domain.Configurations;
    using Hims.Domain.Entities;
    using Hims.Domain.Repositories.UnitOfWork;
    using Hims.Domain.Services;
    using Hims.Shared.EntityModels;
    using Hims.Shared.Library;
    using Hims.Shared.Library.Enums;
    using Hims.Shared.Library.Helpers;
    using Hims.Shared.UserModels;

    /// <summary>
    /// The dental encounter services.
    /// </summary>
    public class DentalEncounterServices : IDentalEncounterService
    {
        /// <summary>
        /// The unit of work.
        /// </summary>
        private readonly IUnitOfWork unitOfWork;

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

        /// <summary>
        /// Initializes a new instance of the <see cref="DentalEncounterServices"/> class.
        /// </summary>
        /// <param name="unitOfWork">
        /// The unit of work.
        /// </param>
        /// <param name="amazonS3Configuration">
        /// The amazon S 3 Configuration.
        /// </param>
        public DentalEncounterServices(IUnitOfWork unitOfWork, IAmazonS3Configuration amazonS3Configuration)
        {
            this.unitOfWork = unitOfWork;
            this.amazonS3Configuration = amazonS3Configuration;
        }

        /// <summary>
        /// The find async.
        /// </summary>
        /// <param name="appointmentId">
        /// The dental encounter id.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public Task<DentalEncounterModel> FindAsync(int appointmentId) => this.unitOfWork.Current.QueryFirstOrDefaultAsync<DentalEncounterModel>($@"SELECT A.""AppointmentId"", cat.*, ATS.""Symptoms"" FROM ""Appointment"" A LEFT JOIN ""DentalEncounter"" cat ON cat.""AppointmentId"" = A.""AppointmentId"" LEFT JOIN ""AppointmentSymptom"" ATS ON  ATS.""AppointmentId"" = A.""AppointmentId"" WHERE A.""AppointmentId"" = '{appointmentId}' AND A.""Active"" IS TRUE");

        /// <summary>
        /// The fetch async.
        /// </summary>
        /// <param name="appointmentId">
        /// The appointment id.
        /// </param>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<DentalEncounterResource> FetchAsync(int appointmentId, DentalEncounterType type)
        {
            var dentalEncounter = await this.unitOfWork.DentalEncounter.FindAsync(m => m.AppointmentId == appointmentId);

            var dentalEncounterResource = new DentalEncounterResource()
            {
                AppointmentId = appointmentId,
                DentalEncounterId = null,
                JsonString = null
            };

            if (dentalEncounter != null)
            {
                dentalEncounterResource.DentalEncounterId = dentalEncounter.DentalEncounterId;
                switch (type)
                {
                    case DentalEncounterType.ChiefComplaints:
                        dentalEncounterResource.JsonString = dentalEncounter.ChiefComplaints;
                        break;
                    case DentalEncounterType.MedicalHistory:
                        dentalEncounterResource.JsonString = dentalEncounter.MedicalHistory;
                        break;
                    case DentalEncounterType.DentalHistory:
                        dentalEncounterResource.JsonString = dentalEncounter.DentalHistory;
                        break;
                    case DentalEncounterType.FamilyHistory:
                        dentalEncounterResource.JsonString = dentalEncounter.FamilyHistory;
                        break;
                    case DentalEncounterType.PersonalHabits:
                        dentalEncounterResource.JsonString = dentalEncounter.PersonalHabits;
                        break;
                    case DentalEncounterType.OralHygiene:
                        dentalEncounterResource.JsonString = dentalEncounter.OralHygiene;
                        break;
                    case DentalEncounterType.ClinicalFindings:
                        dentalEncounterResource.JsonString = dentalEncounter.ClinicalFindings;
                        break;
                    case DentalEncounterType.Diagnosis:
                        dentalEncounterResource.JsonString = dentalEncounter.Diagnosis;
                        break;
                    case DentalEncounterType.Medications:
                        dentalEncounterResource.JsonString = dentalEncounter.Medications;
                        dentalEncounterResource.MedicationComment = dentalEncounter.MedicationComment;
                        break;
                }
            }

            return dentalEncounterResource;
        }

        /// <summary>
        /// The add async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<int> AddAsync(DentalEncounterModifyModel model)
        {
            var checkIf = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<int>(
                              $@"SELECT COUNT(""DentalEncounterId"") FROM ""DentalEncounter"" WHERE ""AppointmentId"" = {model.AppointmentId} AND ""DentalEncounterId"" <> {model.DentalEncounterId}");
            if (checkIf > 0)
            {
                return -1;
            }

            var encounter = new DentalEncounter()
            {
                Active = true,
                AppointmentId = model.AppointmentId,
                DentalEncounterDate = DateTime.UtcNow,
                CreatedBy = model.ModifiedBy,
                CreatedDate = DateTime.UtcNow
            };

            switch (model.Type)
            {
                case DentalEncounterType.ChiefComplaints:
                    encounter.ChiefComplaints = model.JsonString;
                    break;
                case DentalEncounterType.MedicalHistory:
                    encounter.MedicalHistory = model.JsonString;
                    break;
                case DentalEncounterType.DentalHistory:
                    encounter.DentalHistory = model.JsonString;
                    break;
                case DentalEncounterType.FamilyHistory:
                    encounter.FamilyHistory = model.JsonString;
                    break;
                case DentalEncounterType.PersonalHabits:
                    encounter.PersonalHabits = model.JsonString;
                    break;
                case DentalEncounterType.OralHygiene:
                    encounter.OralHygiene = model.JsonString;
                    break;
                case DentalEncounterType.ClinicalFindings:
                    encounter.ClinicalFindings = model.JsonString;
                    break;
                case DentalEncounterType.Diagnosis:
                    encounter.Diagnosis = model.JsonString;
                    break;
                case DentalEncounterType.Medications:
                    encounter.Medications = model.JsonString;
                    break;
                case DentalEncounterType.MedicationComment:
                    encounter.MedicationComment = model.JsonString;
                    break;
            }

            return await this.unitOfWork.DentalEncounter.InsertAsync(encounter);
        }

        /// <summary>
        /// The add async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<CommonResponse> AddAltAsync(DentalEncounterModifyModel model)
        {
            var response = new CommonResponse { Status = 1 };
            var checkIf = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<int>(
                              $@"SELECT COUNT(""DentalEncounterId"") FROM ""DentalEncounter"" WHERE ""AppointmentId"" = {model.AppointmentId} AND ""DentalEncounterId"" <> {model.DentalEncounterId}");
            if (checkIf > 0)
            {
                response.Response = -1;
                return response;
            }

            var encounter = new DentalEncounter()
            {
                Active = true,
                AppointmentId = model.AppointmentId,
                DentalEncounterDate = DateTime.UtcNow,
                CreatedBy = model.ModifiedBy,
                CreatedDate = DateTime.UtcNow
            };

            switch (model.Type)
            {
                case DentalEncounterType.ChiefComplaints:
                    encounter.ChiefComplaints = model.JsonString;
                    break;
                case DentalEncounterType.MedicalHistory:
                    encounter.MedicalHistory = model.JsonString;
                    break;
                case DentalEncounterType.DentalHistory:
                    encounter.DentalHistory = model.JsonString;
                    break;
                case DentalEncounterType.FamilyHistory:
                    encounter.FamilyHistory = model.JsonString;
                    break;
                case DentalEncounterType.PersonalHabits:
                    encounter.PersonalHabits = model.JsonString;
                    break;
                case DentalEncounterType.OralHygiene:
                    encounter.OralHygiene = model.JsonString;
                    break;
                case DentalEncounterType.ClinicalFindings:
                    encounter.ClinicalFindings = model.JsonString;
                    break;
                case DentalEncounterType.Diagnosis:
                    encounter.Diagnosis = model.JsonString;
                    break;
                case DentalEncounterType.Medications:
                    encounter.Medications = model.JsonString;
                    response.Status = 2;
                    break;
                case DentalEncounterType.MedicationComment:
                    encounter.MedicationComment = model.JsonString;
                    break;
            }

            response.Response = await this.unitOfWork.DentalEncounter.InsertAsync(encounter);
            return response;
        }

        /// <summary>
        /// The update async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<int> UpdateAsync(DentalEncounterModifyModel model)
        {
            var dentalEncounter = await this.unitOfWork.DentalEncounter.FindAsync(m => m.DentalEncounterId == model.DentalEncounterId);
            dentalEncounter.ModifiedBy = model.ModifiedBy;
            dentalEncounter.ModifiedDate = DateTime.UtcNow;

            switch (model.Type)
            {
                case DentalEncounterType.ChiefComplaints:
                    dentalEncounter.ChiefComplaints = model.JsonString;
                    break;
                case DentalEncounterType.MedicalHistory:
                    dentalEncounter.MedicalHistory = model.JsonString;
                    break;
                case DentalEncounterType.DentalHistory:
                    dentalEncounter.DentalHistory = model.JsonString;
                    break;
                case DentalEncounterType.FamilyHistory:
                    dentalEncounter.FamilyHistory = model.JsonString;
                    break;
                case DentalEncounterType.PersonalHabits:
                    dentalEncounter.PersonalHabits = model.JsonString;
                    break;
                case DentalEncounterType.OralHygiene:
                    dentalEncounter.OralHygiene = model.JsonString;
                    break;
                case DentalEncounterType.ClinicalFindings:
                    dentalEncounter.ClinicalFindings = model.JsonString;
                    break;
                case DentalEncounterType.Diagnosis:
                    dentalEncounter.Diagnosis = model.JsonString;
                    break;
                case DentalEncounterType.Medications:
                    dentalEncounter.Medications = model.JsonString;
                    break;
                case DentalEncounterType.MedicationComment:
                    dentalEncounter.MedicationComment = model.JsonString;
                    break;
            }

            var updated = await this.unitOfWork.DentalEncounter.UpdateAsync(dentalEncounter);
            return updated > 0 ? dentalEncounter.DentalEncounterId : 0;
        }
        
        /// <summary>
        /// The update async.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        public async Task<CommonResponse> UpdateAltAsync(DentalEncounterModifyModel model)
        {
            var response = new CommonResponse { Status = 1 };
            var dentalEncounter = await this.unitOfWork.DentalEncounter.FindAsync(m => m.DentalEncounterId == model.DentalEncounterId);
            dentalEncounter.ModifiedBy = model.ModifiedBy;
            dentalEncounter.ModifiedDate = DateTime.UtcNow;

            switch (model.Type)
            {
                case DentalEncounterType.ChiefComplaints:
                    dentalEncounter.ChiefComplaints = model.JsonString;
                    break;
                case DentalEncounterType.MedicalHistory:
                    dentalEncounter.MedicalHistory = model.JsonString;
                    break;
                case DentalEncounterType.DentalHistory:
                    dentalEncounter.DentalHistory = model.JsonString;
                    break;
                case DentalEncounterType.FamilyHistory:
                    dentalEncounter.FamilyHistory = model.JsonString;
                    break;
                case DentalEncounterType.PersonalHabits:
                    dentalEncounter.PersonalHabits = model.JsonString;
                    break;
                case DentalEncounterType.OralHygiene:
                    dentalEncounter.OralHygiene = model.JsonString;
                    break;
                case DentalEncounterType.ClinicalFindings:
                    dentalEncounter.ClinicalFindings = model.JsonString;
                    break;
                case DentalEncounterType.Diagnosis:
                    dentalEncounter.Diagnosis = model.JsonString;
                    break;
                case DentalEncounterType.Medications:
                    dentalEncounter.Medications = model.JsonString;
                    response.Status = 2;
                    break;
                case DentalEncounterType.MedicationComment:
                    dentalEncounter.MedicationComment = model.JsonString;
                    break;
            }

            var updated = await this.unitOfWork.DentalEncounter.UpdateAsync(dentalEncounter);
            response.Response = updated > 0 ? dentalEncounter.DentalEncounterId : 0;
            return response;
        }

        /// <inheritdoc />
        public Task<int> DeleteAsync(int dentalEncounterId) => this.unitOfWork.Current.ExecuteAsync($@"DELETE FROM ""DentalEncounter"" WHERE ""DentalEncounterId""= {dentalEncounterId}");

        /// <inheritdoc />
        public Task<DentalFullTranscriptModel> FindFullTranscriptAsync(int appointmentId)
        {
            var query = $@"SELECT A.""AppointmentId"",A.""AppointmentNo"",A.""AppointmentDate"",A.""AppointmentTime"", 
                            (case when A.""PatientFamilyId"" is not null then PF.""FullName"" else P.""FullName"" end) AS ""PatientName"",
                            (case when A.""PatientFamilyId"" is not null then PF.""Gender"" else P.""Gender"" end) AS ""PatientGender"",C.""CountryCode"" AS ""PatientCountryCode"",
							P.""Mobile"" AS ""PatientMobile"", 
                            (case when A.""PatientFamilyId"" is not null then PF.""Age"" else P.""Age"" end) AS ""PatientAge"", P.""DateOfBirth"" AS ""PatientDateOfBirth"",
                            CONCAT(Pr.""Salutation"",' ', Pr.""FullName"") AS ""ProviderName"",
							(CASE WHEN P.""ThumbnailUrl"" IS NOT NULL THEN CONCAT('{this.amazonS3Configuration.BucketURL}', P.""Guid"", '/', P.""ThumbnailUrl"") ELSE NULL END) AS ""PatientThumbnailUrl"",
                               ""DentalEncounterId"", ""MedicalHistory"", ""DentalHistory"", ""FamilyHistory"", ""PersonalHabits"", ""OralHygiene"", ""ClinicalFindings"", ""Diagnosis"", ""Medications"", ""ChiefComplaints"", ""MedicationComment"", ATS.""Symptoms"" FROM ""Appointment"" A
							LEFT JOIN ""DentalEncounter"" hm ON A.""AppointmentId""= hm.""AppointmentId""
							LEFT JOIN ""AppointmentSymptom"" ATS ON  ATS.""AppointmentId"" = A.""AppointmentId""
                            LEFT JOIN ""Provider"" pr on pr.""ProviderId""=A.""ProviderId""
							JOIN ""Patient"" P ON P.""PatientId"" = A.""PatientId""
                            Left join ""PatientFamily"" PF on PF.""PatientFamilyId"" = A.""PatientFamilyId""
							LEFT JOIN ""Country"" C ON C.""CountryId""=P.""CountryId""
                            where A.""AppointmentId"" = {appointmentId} AND A.""Active"" IS TRUE";
            return this.unitOfWork.Current.QueryFirstOrDefaultAsync<DentalFullTranscriptModel>(query);
        }

        /// <inheritdoc />
        public Task<DentalPrescriptionModel> FindPrescriptionAsync(int appointmentId)
        {
            var query = $@"SELECT A.""AppointmentId"", A.""AppointmentNo"",Pr.""Educations"", A.""AppointmentDate"", A.""AppointmentTime"", ec.""DentalEncounterId"", ec.""Medications"",  ec.""MedicationComment"", '' as ""FollowUp"",
							CONCAT(Pr.""Salutation"",' ', Pr.""FullName"") AS ""ProviderName"",Pr.""ProviderNo"",string_agg(S.""SpecializationName"",', ') ""SpecializationName"",P.""FullName"" AS ""PracticeName"",
							CONCAT(PrL.""City"", ', ', PrL.""State"", ' - ', PrL.""Zipcode"") AS ""PracticeLocation"",
							(case when A.""PatientFamilyId"" is not null then PF.""FullName"" else Pt.""FullName"" end) AS ""PatientName"",
                            (case when A.""PatientFamilyId"" is not null then PF.""Age"" else Pt.""Age"" end) AS ""PatientAge"",
                            (case when A.""PatientFamilyId"" is not null then PF.""Gender"" else Pt.""Gender"" end) AS ""PatientGender"",
							pt.""DateOfBirth"" AS ""PatientDateOfBirth"",CASE WHEN pt.""Mobile"" NOTNULL THEN CONCAT('(+', ct.""CountryCode"", ') ', pt.""Mobile"") ELSE NULL END AS ""PatientMobile"" FROM ""Appointment"" A
							JOIN ""DentalEncounter"" ec ON ec.""AppointmentId""=A.""AppointmentId""
							JOIN ""Provider"" Pr ON Pr.""ProviderId""=A.""ProviderId""
							JOIN ""Specialization"" S ON S.""SpecializationId"" =ANY(Pr.""Specializations"")
							JOIN ""ProviderLocation"" PL ON PL.""ProviderLocationId"" = A.""ProviderLocationId""
							JOIN ""PracticeLocation"" PrL ON PrL.""PracticeLocationId"" =PL.""PracticeLocationId""
							JOIN ""Practice"" P ON P.""PracticeId"" = PrL.""PracticeId""
							JOIN ""Patient"" pt ON pt.""PatientId""=A.""PatientId""
                            left join ""PatientFamily"" PF on PF.""PatientFamilyId"" = A.""PatientFamilyId""
							LEFT JOIN ""Country"" ct ON ct.""CountryId""=pt.""CountryId""
                            where ec.""AppointmentId"" = {appointmentId} AND ec.""Active"" IS TRUE
							GROUP BY A.""AppointmentId"",A.""AppointmentDate"",A.""AppointmentTime"", ec.""DentalEncounterId"", 
							ec.""Medications"", ""ProviderName"",Pr.""ProviderNo"",""PracticeName"",""PracticeLocation"",
							""PatientName"",""PatientAge"",""PatientGender"",""PatientDateOfBirth"",""PatientMobile"",Pr.""Educations"" ";
            return this.unitOfWork.Current.QueryFirstOrDefaultAsync<DentalPrescriptionModel>(query);
        }
    }
}