/// <summary>
        /// Gets the SMS conversation history for a person alias ID. Includes the communication sent by Rock that the person may be responding to.
        /// </summary>
        /// <param name="personId">The person identifier.</param>
        /// <param name="relatedSmsFromDefinedValueId">The related SMS from defined value identifier.</param>
        /// <returns>List&lt;CommunicationRecipientResponse&gt;.</returns>
        public List <CommunicationRecipientResponse> GetCommunicationConversationForPerson(int personId, int relatedSmsFromDefinedValueId)
        {
            List <CommunicationRecipientResponse> communicationRecipientResponseList = new List <CommunicationRecipientResponse>();

            var smsMediumEntityTypeId = EntityTypeCache.GetId(SystemGuid.EntityType.COMMUNICATION_MEDIUM_SMS).Value;

            var personAliasIdQuery = new PersonAliasService(this.Context as RockContext).Queryable().Where(a => a.PersonId == personId).Select(a => a.Id);

            var communicationResponseQuery = this.Queryable()
                                             .Where(r => r.RelatedMediumEntityTypeId == smsMediumEntityTypeId &&
                                                    r.RelatedSmsFromDefinedValueId == relatedSmsFromDefinedValueId &&
                                                    r.FromPersonAliasId.HasValue &&
                                                    personAliasIdQuery.Contains(r.FromPersonAliasId.Value)
                                                    );

            var communicationResponseList = communicationResponseQuery.ToList();

            foreach (var communicationResponse in communicationResponseList)
            {
                var communicationRecipientResponse = new CommunicationRecipientResponse
                {
                    CreatedDateTime         = communicationResponse.CreatedDateTime,
                    PersonId                = communicationResponse?.FromPersonAlias?.PersonId,
                    FullName                = communicationResponse?.FromPersonAlias?.Person.FullName,
                    IsRead                  = communicationResponse.IsRead,
                    MessageKey              = communicationResponse.MessageKey,
                    IsOutbound              = false,
                    RecipientPersonAliasId  = communicationResponse.FromPersonAliasId,
                    SMSMessage              = communicationResponse.Response,
                    MessageStatus           = CommunicationRecipientStatus.Delivered, // We are just going to call these delivered because we have them. Setting this will tell the UI to not display the status.
                    CommunicationResponseId = communicationResponse.Id,
                };

                communicationRecipientResponseList.Add(communicationRecipientResponse);
            }

            var communicationRecipientQuery = new CommunicationRecipientService(this.Context as RockContext)
                                              .Queryable()
                                              .Where(r => r.MediumEntityTypeId == smsMediumEntityTypeId)
                                              .Where(r => r.Communication.SMSFromDefinedValueId == relatedSmsFromDefinedValueId)
                                              .Where(r => r.PersonAliasId.HasValue)
                                              .Where(r => personAliasIdQuery.Contains(r.PersonAliasId.Value))
                                              .Where(r => r.Status == CommunicationRecipientStatus.Delivered || r.Status == CommunicationRecipientStatus.Pending);

            var communicationRecipientList = communicationRecipientQuery.Include(a => a.PersonAlias.Person.PhoneNumbers).Select(a => new
            {
                a.CreatedDateTime,
                a.Communication.SenderPersonAlias.Person,
                a.Communication,
                PersonAliasId = a.Communication.SenderPersonAliasId,
                a.SentMessage,
                a.Status
            }).ToList();

            foreach (var communicationRecipient in communicationRecipientList)
            {
                var communicationRecipientResponse = new CommunicationRecipientResponse
                {
                    CreatedDateTime        = communicationRecipient.CreatedDateTime,
                    PersonId               = communicationRecipient.Person?.Id,
                    FullName               = communicationRecipient.Person?.FullName,
                    IsRead                 = true,
                    IsOutbound             = true,
                    RecipientPersonAliasId = communicationRecipient.PersonAliasId,
                    SMSMessage             = communicationRecipient.SentMessage,
                    MessageStatus          = communicationRecipient.Status,
                    CommunicationId        = communicationRecipient.Communication?.Id,
                };

                if (communicationRecipient.Person?.IsNameless() == true)
                {
                    // if the person is nameless, we'll need to know their number since we don't know their name
                    communicationRecipientResponse.MessageKey = communicationRecipient.Person?.PhoneNumbers.FirstOrDefault()?.Number;
                }
                else
                {
                    // If the Person is not nameless, we just need to show their name, not their number
                    communicationRecipientResponse.MessageKey = null;
                }

                communicationRecipientResponseList.Add(communicationRecipientResponse);
            }

            return(communicationRecipientResponseList.OrderBy(a => a.CreatedDateTime).ToList());
        }
        /// <summary>
        /// Gets the communications and response recipients.
        /// </summary>
        /// <param name="relatedSmsFromDefinedValueId">The related SMS from defined value identifier.</param>
        /// <param name="startDateTime">The start date time.</param>
        /// <param name="showReadMessages">if set to <c>true</c> [show read messages].</param>
        /// <param name="maxCount">The maximum count.</param>
        /// <param name="personId">The person identifier.</param>
        /// <returns></returns>
        public List <CommunicationRecipientResponse> GetCommunicationResponseRecipients(int relatedSmsFromDefinedValueId, DateTime startDateTime, bool showReadMessages, int maxCount, int?personId)
        {
            var smsMediumEntityTypeId = EntityTypeCache.GetId(SystemGuid.EntityType.COMMUNICATION_MEDIUM_SMS).Value;

            IQueryable <CommunicationResponse> communicationResponseQuery = this.Queryable()
                                                                            .Where(r => r.RelatedMediumEntityTypeId == smsMediumEntityTypeId && r.RelatedSmsFromDefinedValueId == relatedSmsFromDefinedValueId && r.CreatedDateTime >= startDateTime && r.FromPersonAliasId.HasValue);

            if (!showReadMessages)
            {
                communicationResponseQuery = communicationResponseQuery.Where(r => r.IsRead == false);
            }

            var personAliasQuery = personId == null
                ? new PersonAliasService(this.Context as RockContext).Queryable()
                : new PersonAliasService(this.Context as RockContext).Queryable().Where(p => p.PersonId == personId);

            // do an explicit LINQ inner join on PersonAlias to avoid performance issue where it would do an outer join instead
            var communicationResponseJoinQuery =
                from cr in communicationResponseQuery
                join pa in personAliasQuery on cr.FromPersonAliasId equals pa.Id
                select new { cr, pa };

            IQueryable <CommunicationResponse> mostRecentCommunicationResponseQuery = communicationResponseJoinQuery
                                                                                      .GroupBy(r => r.pa.PersonId)
                                                                                      .Select(a => a.OrderByDescending(x => x.cr.CreatedDateTime).FirstOrDefault())
                                                                                      .OrderByDescending(a => a.cr.CreatedDateTime).Select(a => a.cr);

            IQueryable <CommunicationRecipient> communicationRecipientQuery = new CommunicationRecipientService(this.Context as RockContext).Queryable()
                                                                              .Where(r =>
                                                                                     r.MediumEntityTypeId == smsMediumEntityTypeId &&
                                                                                     r.Communication.SMSFromDefinedValueId == relatedSmsFromDefinedValueId &&
                                                                                     r.CreatedDateTime >= startDateTime &&
                                                                                     r.Status == CommunicationRecipientStatus.Delivered);

            // do an explicit LINQ inner join on PersonAlias to avoid performance issue where it would do an outer join instead
            var communicationRecipientJoinQuery =
                from cr in communicationRecipientQuery.Where(a => a.PersonAliasId.HasValue)
                join pa in personAliasQuery on cr.PersonAliasId.Value equals pa.Id
                select new
            {
                PersonId        = pa.PersonId,
                Person          = pa.Person,
                CreatedDateTime = cr.CreatedDateTime,
                cr.Communication,
                cr.CommunicationId,
                CommunicationSMSMessage = cr.Communication.SMSMessage,
                SentMessage             = cr.SentMessage,
                PersonAliasId           = cr.PersonAliasId
            };

            var mostRecentCommunicationRecipientQuery = communicationRecipientJoinQuery
                                                        .GroupBy(r => r.PersonId)
                                                        .Select(a =>
                                                                a.Select(s => new
            {
                s.Person,
                s.CreatedDateTime,
                s.CommunicationSMSMessage,
                s.CommunicationId,
                s.Communication,
                s.SentMessage,
                s.PersonAliasId
            }).OrderByDescending(s => s.CreatedDateTime).FirstOrDefault()
                                                                );

            var mostRecentCommunicationResponseList = mostRecentCommunicationResponseQuery.Include(a => a.FromPersonAlias.Person).AsNoTracking().Take(maxCount).ToList();

            List <CommunicationRecipientResponse> communicationRecipientResponseList = new List <CommunicationRecipientResponse>();

            foreach (var mostRecentCommunicationResponse in mostRecentCommunicationResponseList)
            {
                var communicationRecipientResponse = new CommunicationRecipientResponse
                {
                    CreatedDateTime         = mostRecentCommunicationResponse.CreatedDateTime,
                    PersonId                = mostRecentCommunicationResponse?.FromPersonAlias.PersonId,
                    RecordTypeValueId       = mostRecentCommunicationResponse?.FromPersonAlias.Person.RecordTypeValueId,
                    FullName                = mostRecentCommunicationResponse?.FromPersonAlias.Person.FullName,
                    IsRead                  = mostRecentCommunicationResponse.IsRead,
                    MessageKey              = mostRecentCommunicationResponse.MessageKey,
                    IsOutbound              = false,
                    RecipientPersonAliasId  = mostRecentCommunicationResponse.FromPersonAliasId,
                    SMSMessage              = mostRecentCommunicationResponse.Response,
                    CommunicationResponseId = mostRecentCommunicationResponse.Id
                };

                communicationRecipientResponseList.Add(communicationRecipientResponse);
            }

            var mostRecentCommunicationRecipientList = mostRecentCommunicationRecipientQuery.Take(maxCount).ToList();

            foreach (var mostRecentCommunicationRecipient in mostRecentCommunicationRecipientList)
            {
                var communicationRecipientResponse = new CommunicationRecipientResponse
                {
                    CreatedDateTime        = mostRecentCommunicationRecipient.CreatedDateTime,
                    PersonId               = mostRecentCommunicationRecipient.Person.Id,
                    RecordTypeValueId      = mostRecentCommunicationRecipient.Person.RecordTypeValueId,
                    FullName               = mostRecentCommunicationRecipient.Person.FullName,
                    IsOutbound             = true,
                    IsRead                 = true,
                    MessageKey             = null, // communication recipients just need to show their name, not their number
                    RecipientPersonAliasId = mostRecentCommunicationRecipient.PersonAliasId,
                    SMSMessage             = mostRecentCommunicationRecipient.SentMessage.IsNullOrWhiteSpace() ? mostRecentCommunicationRecipient.CommunicationSMSMessage : mostRecentCommunicationRecipient.SentMessage,
                    CommunicationId        = mostRecentCommunicationRecipient.CommunicationId
                };

                if (mostRecentCommunicationRecipient?.Person.IsNameless() == true)
                {
                    // if the person is nameless, we'll need to know their number since we don't know their name
                    communicationRecipientResponse.MessageKey = mostRecentCommunicationRecipient.Person.PhoneNumbers.FirstOrDefault()?.Number;
                }
                else
                {
                    // If the Person is not nameless, we just need to show their name, not their number
                    communicationRecipientResponse.MessageKey = null;
                }

                communicationRecipientResponseList.Add(communicationRecipientResponse);
            }

            // NOTE: We actually have up to twice the max count at this point, because we are combining results from
            // CommunicationRecipient and CommunicationResponse, and we took the maxCount of each of those.
            // Now, we see what that combination ends up looking like when we sort it by CreatedDateTime
            communicationRecipientResponseList = communicationRecipientResponseList
                                                 .GroupBy(r => r.PersonId)
                                                 .Select(a => a.OrderByDescending(x => x.CreatedDateTime).FirstOrDefault())
                                                 .OrderByDescending(a => a.CreatedDateTime).Take(maxCount).ToList();

            return(communicationRecipientResponseList);
        }
        /// <summary>
        /// Gets the SMS conversation history for a person alias ID. Includes the communication sent by Rock that the person may be responding to.
        /// </summary>
        /// <param name="personAliasId">The person alias identifier.</param>
        /// <param name="relatedSmsFromDefinedValueId">The related SMS from defined value identifier.</param>
        /// <returns></returns>
        public List <CommunicationRecipientResponse> GetCommunicationConversation(int personAliasId, int relatedSmsFromDefinedValueId)
        {
            List <CommunicationRecipientResponse> communicationRecipientResponseList = new List <CommunicationRecipientResponse>();

            var smsMediumEntityTypeId = EntityTypeCache.GetId(SystemGuid.EntityType.COMMUNICATION_MEDIUM_SMS).Value;

            /*
             * 5/4/2021 MSB
             * When we include conversations we need to make sure we include conversations from all aliases related to the
             * person so that conversations from merged records still appear here.
             *
             * Reason: Merge People Conversations
             */

            var communicationResponseQuery = this.Queryable()
                                             .Where(r => r.RelatedMediumEntityTypeId == smsMediumEntityTypeId &&
                                                    r.RelatedSmsFromDefinedValueId == relatedSmsFromDefinedValueId &&
                                                    r.FromPersonAlias != null &&
                                                    r.FromPersonAlias.Person.Aliases.Any(fpa => fpa.Id == personAliasId));

            var communicationResponseList = communicationResponseQuery.ToList();

            foreach (var communicationResponse in communicationResponseList)
            {
                var communicationRecipientResponse = new CommunicationRecipientResponse
                {
                    CreatedDateTime        = communicationResponse.CreatedDateTime,
                    PersonId               = communicationResponse?.FromPersonAlias?.PersonId,
                    FullName               = communicationResponse?.FromPersonAlias?.Person.FullName,
                    IsRead                 = communicationResponse.IsRead,
                    MessageKey             = communicationResponse.MessageKey,
                    IsOutbound             = false,
                    RecipientPersonAliasId = communicationResponse.FromPersonAliasId,
                    SMSMessage             = communicationResponse.Response,
                    MessageStatus          = CommunicationRecipientStatus.Delivered, // We are just going to call these delivered because we have them. Setting this will tell the UI to not display the status.
                    BinaryFileGuids        = communicationResponse.Attachments?.Select(r => r.BinaryFile.Guid).ToList()
                };

                communicationRecipientResponseList.Add(communicationRecipientResponse);
            }

            var communicationRecipientQuery = new CommunicationRecipientService(this.Context as RockContext)
                                              .Queryable()
                                              .Where(r => r.MediumEntityTypeId == smsMediumEntityTypeId)
                                              .Where(r => r.Communication.SMSFromDefinedValueId == relatedSmsFromDefinedValueId)
                                              .Where(r => r.PersonAlias != null)
                                              .Where(r => r.PersonAlias.Person.Aliases.Any(fpa => fpa.Id == personAliasId))
                                              .Where(r => r.Status == CommunicationRecipientStatus.Delivered || r.Status == CommunicationRecipientStatus.Pending);

            var communicationRecipientList = communicationRecipientQuery.Include(a => a.PersonAlias.Person.PhoneNumbers).Select(a => new
            {
                a.CreatedDateTime,
                a.Communication.SenderPersonAlias.Person,
                a.Communication,
                PersonAliasId = a.Communication.SenderPersonAliasId,
                a.SentMessage,
                a.Status
            }).ToList();

            foreach (var communicationRecipient in communicationRecipientList)
            {
                var communicationRecipientResponse = new CommunicationRecipientResponse
                {
                    CreatedDateTime        = communicationRecipient.CreatedDateTime,
                    PersonId               = communicationRecipient.Person?.Id,
                    FullName               = communicationRecipient.Person?.FullName,
                    IsRead                 = true,
                    IsOutbound             = true,
                    RecipientPersonAliasId = communicationRecipient.PersonAliasId,
                    SMSMessage             = communicationRecipient.SentMessage,
                    MessageStatus          = communicationRecipient.Status,
                    BinaryFileGuids        = communicationRecipient.Communication.Attachments?.Select(c => c.BinaryFile.Guid).ToList()
                };

                if (communicationRecipient.Person?.IsNameless() == true)
                {
                    // if the person is nameless, we'll need to know their number since we don't know their name
                    communicationRecipientResponse.MessageKey = communicationRecipient.Person?.PhoneNumbers.FirstOrDefault()?.Number;
                }
                else
                {
                    // If the Person is not nameless, we just need to show their name, not their number
                    communicationRecipientResponse.MessageKey = null;
                }

                communicationRecipientResponseList.Add(communicationRecipientResponse);
            }

            return(communicationRecipientResponseList.OrderBy(a => a.CreatedDateTime).ToList());
        }