private void CreateRaidsFromScanResult(PogoAfoMapping map, PogoAfoResult pogoAfoResult, long channel)
        {
            var raidParticipationCollection = DB.GetCollection <RaidParticipation>();
            var raidsList = raidParticipationCollection.FindAll().ToArray();

            foreach (var entry in pogoAfoResult.raids ?? new Dictionary <string, PogoAfoRaidInfo>())
            {
                // TODO: Should be part of the query already, why ask for items then discard them?
                if (entry.Value.raid_level < 4 && !entry.Value.ex_trigger)
                {
                    _log.Trace($"{SourceID} - Ignoring raid {entry.Key} @{entry.Value.raid_battle} level: {entry.Value.raid_level} / trigger: {entry.Value.ex_trigger} / pokémon: {entry.Value.raid_pokemon_id} / gym: {entry.Value.name} / url: {entry.Value.url}");
                    continue;
                }

                if (map.NorthEastCorner != null && map.SouthWestCorner != null && entry.Value.latitude.HasValue && entry.Value.longitude.HasValue)
                {
                    if (!(Between(entry.Value.latitude.Value, map.NorthEastCorner.Latitude, map.SouthWestCorner.Latitude) && Between(entry.Value.longitude.Value, map.NorthEastCorner.Longitude, map.SouthWestCorner.Longitude)))
                    {
                        _log.Trace($"{SourceID} - Ignoring raid {entry.Key} @{entry.Value.raid_battle} based on location, it's outside my area of interest.");
                        continue;
                    }
                }

                _log.Trace($"{SourceID} - Incoming raid {entry.Key} @{entry.Value.raid_battle} / pokémon: {entry.Value.raid_pokemon_id} / gym: {entry.Value.name} / url: {entry.Value.url}");

                DateTimeOffset raidStartTime = entry.Value.raid_battle.HasValue ? DateTimeOffset.FromUnixTimeSeconds(entry.Value.raid_battle ?? 0) : DateTimeOffset.MinValue;
                DateTimeOffset raidEndTime   = entry.Value.raid_end.HasValue ? DateTimeOffset.FromUnixTimeSeconds(entry.Value.raid_end ?? 0) : DateTimeOffset.MinValue;

                // Look for this exact raid
                var raidStruct = raidsList.Where(x => (x.Raid.Sources != null) && (x.Raid.Sources.Where(s => s.SourceID == SourceID && s.ExternalID == entry.Key).Any())).FirstOrDefault();
                if (null != raidStruct)
                {
                    _log.Trace($"Found existing raid with same external ID: pokémon: {entry.Value.raid_pokemon_id} / gym: {entry.Value.name} / url: {entry.Value.url} === id: {raidStruct.PublicID} / {raidStruct.Raid.Raid} / {raidStruct.Raid.Gym}");
                }

                // search for a PUBLISHED raid that's similar in location and time
                if (null == raidStruct)
                {
                    raidStruct = raidsList.Where(rp =>
                    {
                        if (rp.Raid != null && rp.Raid.Location != null && raidEndTime != null && rp.Raid.RaidEndTime != null)
                        {
                            var loc1     = new GeoCoordinate(rp.Raid.Location.Latitude, rp.Raid.Location.Longitude);
                            var loc2     = new GeoCoordinate(entry.Value.latitude ?? 0, entry.Value.longitude ?? 0);
                            var distance = loc1.GetDistanceTo(loc2);
                            return((distance <= 50) && (Math.Abs((raidEndTime.UtcDateTime - rp.Raid.RaidEndTime).TotalSeconds) <= 300) && rp.IsPublished);
                        }
                        else
                        {
                            return(false);
                        }
                    }).FirstOrDefault();

                    if (null != raidStruct)
                    {
                        _log.Trace($"Merging raid pokémon: {entry.Value.raid_pokemon_id} / gym: {entry.Value.name} / url: {entry.Value.url} with existing raid id: {raidStruct.PublicID} / {raidStruct.Raid.Raid} / {raidStruct.Raid.Gym}");
                    }
                }

                string currentValues;
                // Create a new raid
                if (null == raidStruct)
                {
                    _log.Trace($"{SourceID} - Adding new raid {entry.Key} @{entry.Value.raid_battle} / pokémon: {entry.Value.raid_pokemon_id} / gym: {entry.Value.name} / url: {entry.Value.url}");
                    raidStruct = new RaidParticipation
                    {
                        Raid = new RaidDescription
                        {
                            Alignment = Team.Unknown,
                            Sources   = new List <ExternalSource>()
                            {
                                new ExternalSource
                                {
                                    ExternalID = entry.Key,
                                    SourceID   = SourceID,
                                    URL        = entry.Value.url
                                }
                            },
                            User = new Botje.Messaging.Models.User {
                                IsBot = true, ID = -1, FirstName = SourceID, LastName = SourceID, Username = SourceID
                            },
                            UpdateCount = 1,
                        }
                    };
                    raidParticipationCollection.Insert(raidStruct);
                    currentValues = "";
                }
                else
                {
                    currentValues = raidStruct.AllValuesAsString();
                }

                // update raidStruct
                if (entry.Value.raid_pokemon_id != null)
                {
                    var pokedexEntry = Pokedex.All.Where(x => x.id == entry.Value.raid_pokemon_id).FirstOrDefault();
                    if (null == pokedexEntry)
                    {
                        raidStruct.Raid.Raid = $"Pokémon {entry.Value.raid_pokemon_id} (level {entry.Value.raid_level})";
                    }
                    else
                    {
                        raidStruct.Raid.Raid = $"{pokedexEntry.name} (level {entry.Value.raid_level})";
                    }
                }
                else
                {
                    raidStruct.Raid.Raid = $"Level {entry.Value.raid_level} raid";
                }

                if (raidStruct.Raid.RaidUnlockTime != raidStartTime.UtcDateTime)
                {
                    raidStruct.Raid.RaidUnlockTime = raidStartTime.UtcDateTime;
                }
                if (raidStruct.Raid.RaidEndTime != raidEndTime.UtcDateTime)
                {
                    raidStruct.Raid.RaidEndTime = raidEndTime.UtcDateTime;
                }
                if (raidStruct.Raid.Gym != entry.Value.name)
                {
                    raidStruct.Raid.Gym = entry.Value.name;
                }
                if (entry.Value.ex_trigger && string.IsNullOrEmpty(raidStruct.Raid.Remarks))
                {
                    raidStruct.Raid.Remarks = $"EX Raid Trigger";
                }

                if (raidStruct.Raid.Publications == null)
                {
                    raidStruct.Raid.Publications = new List <PublicationEntry>();
                }


                if (entry.Value.longitude.HasValue && entry.Value.latitude.HasValue && (null == raidStruct.Raid.Location || raidStruct.Raid.Location.Latitude != entry.Value.latitude.Value || raidStruct.Raid.Location.Longitude != entry.Value.longitude.Value))
                {
                    raidStruct.Raid.Location = new Location
                    {
                        Latitude  = entry.Value.latitude.Value,
                        Longitude = entry.Value.longitude.Value
                    };
                }

                if (string.IsNullOrEmpty(raidStruct.Raid.Address))
                {
                    UpdateAddress(raidParticipationCollection, raidStruct, raidStruct.Raid.Location);
                }

                var newValues = raidStruct.AllValuesAsString();
                if (newValues != currentValues)
                {
                    if (!raidStruct.Raid.Publications.Where(x => x.ChannelID == channel).Any())
                    {
                        _log.Trace($"{SourceID} - Publishing {entry.Key} @{entry.Value.raid_battle} / pokémon: {entry.Value.raid_pokemon_id} / gym: {entry.Value.name} / url: {entry.Value.url}");
                        var message = RaidEventHandler.ShareRaidToChat(raidStruct, channel);
                        if (channel == Settings.PublicationChannel)
                        {
                            raidStruct.Raid.TelegramMessageID = message?.MessageID;
                            raidStruct.IsPublished            = true;
                        }
                        raidStruct.Raid.Publications.Add(new PublicationEntry
                        {
                            ChannelID         = channel,
                            TelegramMessageID = message?.MessageID ?? long.MaxValue
                        });
                    }

                    raidStruct.LastModificationTime = DateTime.UtcNow;
                    raidParticipationCollection.Update(raidStruct);
                }
                else
                {
                    _log.Trace($"Nothing changed for raid {raidStruct.PublicID}, not updating anything.");
                }
            }
        }
        private void CreateRaidsFromScanResult(PogoAfoMapping map, PogoAfoResult pogoAfoResult, long channel)
        {
            var raidParticipationCollection = DB.GetCollection <RaidParticipation>();
            var raidsList = raidParticipationCollection.Find(x => x.Raid.RaidEndTime >= DateTime.UtcNow).ToArray();

            foreach (var entry in pogoAfoResult.raids ?? new Dictionary <string, PogoAfoRaidInfo>())
            {
                _log.Trace($"{SourceID} - Incoming raid {entry.Key} @{entry.Value.raid_battle} / pokémon: {entry.Value.raid_pokemon_id} / gym: {entry.Value.name} / url: {entry.Value.url}");

                DateTimeOffset raidStartTime = entry.Value.raid_battle.HasValue ? DateTimeOffset.FromUnixTimeSeconds(entry.Value.raid_battle ?? 0) : DateTimeOffset.MinValue;
                DateTimeOffset raidEndTime   = entry.Value.raid_end.HasValue ? DateTimeOffset.FromUnixTimeSeconds(entry.Value.raid_end ?? 0) : DateTimeOffset.MinValue;

                // Look for this exact raid
                var raidStruct = raidsList.Where(x => (x.Raid.Sources != null) && (x.Raid.Sources.Where(s => s.SourceID == SourceID && s.ExternalID == entry.Key).Any())).FirstOrDefault();
                if (null != raidStruct)
                {
                    _log.Trace($"Found existing raid with same external ID: pokémon: {entry.Value.raid_pokemon_id} / gym: {entry.Value.name} / url: {entry.Value.url} === id: {raidStruct.PublicID} / {raidStruct.Raid.Raid} / {raidStruct.Raid.Gym}");
                }

                // search for a PUBLISHED raid that's similar in location and time
                if (null == raidStruct)
                {
                    raidStruct = raidsList.Where(rp =>
                    {
                        if (rp.Raid != null && rp.Raid.Location != null && raidEndTime != null && rp.Raid.RaidEndTime != null)
                        {
                            var loc1     = new GeoCoordinate(rp.Raid.Location.Latitude, rp.Raid.Location.Longitude);
                            var loc2     = new GeoCoordinate(entry.Value.latitude ?? 0, entry.Value.longitude ?? 0);
                            var distance = loc1.GetDistanceTo(loc2);
                            return((distance <= 50) && (Math.Abs((raidEndTime.UtcDateTime - rp.Raid.RaidEndTime).TotalSeconds) <= 300) && rp.IsPublished);
                        }
                        else
                        {
                            return(false);
                        }
                    }).FirstOrDefault();

                    if (null != raidStruct)
                    {
                        _log.Trace($"Merging raid pokémon: {entry.Value.raid_pokemon_id} / gym: {entry.Value.name} / url: {entry.Value.url} with existing raid id: {raidStruct.PublicID} / {raidStruct.Raid.Raid} / {raidStruct.Raid.Gym}");
                    }
                }

                string currentValues;
                // Create a new raid
                if (null == raidStruct)
                {
                    _log.Trace($"{SourceID} - Adding new raid {entry.Key} @{entry.Value.raid_battle} / pokémon: {entry.Value.raid_pokemon_id} / gym: {entry.Value.name} / url: {entry.Value.url}");
                    raidStruct = new RaidParticipation
                    {
                        Raid = new RaidDescription
                        {
                            Alignment = Team.Unknown,
                            Sources   = new List <ExternalSource>()
                            {
                                new ExternalSource
                                {
                                    ExternalID = entry.Key,
                                    SourceID   = SourceID,
                                    URL        = entry.Value.url
                                }
                            },
                            User = new Botje.Messaging.Models.User {
                                IsBot = true, ID = -1, FirstName = SourceID, LastName = SourceID, Username = SourceID
                            },
                            UpdateCount = 1,
                        }
                    };
                    raidParticipationCollection.Insert(raidStruct);
                    currentValues = "";
                }
                else
                {
                    currentValues = raidStruct.AllValuesAsString();
                }

                // update raidStruct
                if (entry.Value.raid_pokemon_id != null)
                {
                    var pokedexEntry = Pokedex.All.Where(x => x.id == entry.Value.raid_pokemon_id).FirstOrDefault();
                    if (null == pokedexEntry)
                    {
                        raidStruct.Raid.Raid = $"Pokémon {entry.Value.raid_pokemon_id} (level {entry.Value.raid_level})";
                    }
                    else
                    {
                        raidStruct.Raid.Raid = $"{pokedexEntry.name} (level {entry.Value.raid_level})";
                    }
                }
                else
                {
                    raidStruct.Raid.Raid = $"Level {entry.Value.raid_level} raid";
                }

                if (raidStruct.Raid.RaidUnlockTime != raidStartTime.UtcDateTime)
                {
                    raidStruct.Raid.RaidUnlockTime = raidStartTime.UtcDateTime;
                }
                if (raidStruct.Raid.RaidEndTime != raidEndTime.UtcDateTime)
                {
                    raidStruct.Raid.RaidEndTime = raidEndTime.UtcDateTime;
                }
                if (raidStruct.Raid.Gym != entry.Value.name)
                {
                    raidStruct.Raid.Gym = entry.Value.name;
                }
                if (entry.Value.ex_trigger && string.IsNullOrEmpty(raidStruct.Raid.Remarks))
                {
                    raidStruct.Raid.Remarks = $"EX Raid Trigger";
                }

                if (entry.Value.longitude.HasValue && entry.Value.latitude.HasValue && (null == raidStruct.Raid.Location || raidStruct.Raid.Location.Latitude != entry.Value.latitude.Value || raidStruct.Raid.Location.Longitude != entry.Value.longitude.Value))
                {
                    raidStruct.Raid.Location = new Location
                    {
                        Latitude  = entry.Value.latitude.Value,
                        Longitude = entry.Value.longitude.Value
                    };
                }

                var newValues = raidStruct.AllValuesAsString();
                if (newValues != currentValues)
                {
                    // TODO: REWRITE THIS TO MARK THE MESSAGE FOR PUBLICATION AND THE PUBLISH IT IN A SEPARATE LOOP
                    PublishRaidToPrimaryChannelIfNeeded(channel, entry, raidStruct);

                    if (null == raidStruct.Raid.Publications)
                    {
                        raidStruct.Raid.Publications = new List <PublicationEntry>();
                    }
                    if (null != map.Targets && map.Targets.Count > 0)
                    {
                        CreatePublicationEntriesForAlternativeTargets(map.Targets, entry, raidStruct);
                    }

                    raidStruct.LastModificationTime = DateTime.UtcNow;
                    raidParticipationCollection.Update(raidStruct);
                }
                else
                {
                    _log.Trace($"Nothing changed for raid {raidStruct.PublicID}, not updating anything.");
                }

                if (string.IsNullOrEmpty(raidStruct.Raid.Address))
                {
                    UpdateAddress(raidParticipationCollection, raidStruct, raidStruct.Raid.Location);
                }
            }
        }