using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Data.Abstractions;
using Data.Models;
using Data.Models.Client;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using WebfrontCore.QueryHelpers.Models;

namespace WebfrontCore.QueryHelpers;

public class BanInfoResourceQueryHelper : IResourceQueryHelper<BanInfoRequest, BanInfo>
{
    private readonly IDatabaseContextFactory _contextFactory;

    public BanInfoResourceQueryHelper(IDatabaseContextFactory contextFactory)
    {
        _contextFactory = contextFactory;
    }

    public async Task<ResourceQueryHelperResult<BanInfo>> QueryResource(BanInfoRequest query)
    {
        if (query.Count > 10)
        {
            query.Count = 10;
        }

        await using var context = _contextFactory.CreateContext(false);
        
        var iqMatchingClients = context.Clients.Where(client => client.Level == EFClient.Permission.Banned);
        iqMatchingClients = SetupSearchArgs(query, iqMatchingClients);
        
        if (string.IsNullOrEmpty(query.ClientName) && string.IsNullOrEmpty(query.ClientGuid) &&
            query.ClientId is null && string.IsNullOrEmpty(query.ClientIP))
        {
            return new ResourceQueryHelperResult<BanInfo>
            {
                Results = Enumerable.Empty<BanInfo>()
            };
        }

        var matchingClients = await iqMatchingClients
            .OrderByDescending(client => client.LastConnection)
            .Skip(query.Offset)
            .Take(query.Count)
            .Select(client => new
            {
                client.CurrentAlias.Name,
                client.NetworkId,
                client.AliasLinkId,
                client.ClientId,
                client.CurrentAlias.IPAddress,
                client.GameName
            }).ToListAsync();

        var results = new List<BanInfo>();
        var matchedClientIds = new List<int?>();
        var lateDateTime = DateTime.Now.AddYears(100);

        // would prefer not to loop this, but unfortunately due to the data design 
        // we can't properly group on ip and alias link
        foreach (var matchingClient in matchingClients)
        {
            var usedIps = await context.Aliases
                .Where(alias => matchingClient.AliasLinkId == alias.LinkId)
                .Where(alias => alias.IPAddress != null)
                .Select(alias => new { alias.IPAddress, alias.LinkId })
                .ToListAsync();

            var searchingNetworkId = matchingClient.NetworkId;
            var searchingIps = usedIps.Select(ip => ip.IPAddress).Distinct();

            var matchedPenalties = await context.PenaltyIdentifiers.Where(identifier =>
                    identifier.NetworkId == searchingNetworkId ||
                    searchingIps.Contains(identifier.IPv4Address))
                .Where(identifier => identifier.Penalty.Expires == null || identifier.Penalty.Expires > lateDateTime)
                .Select(penalty => new
                {
                    penalty.CreatedDateTime,
                    PunisherName = penalty.Penalty.Punisher.CurrentAlias.Name,
                    OffenderName = penalty.Penalty.Offender.CurrentAlias.Name,
                    Offense = string.IsNullOrEmpty(penalty.Penalty.AutomatedOffense)
                        ? penalty.Penalty.Offense
                        : Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_BAN_INFO_AC_DETECTION"],
                    LinkId = penalty.Penalty.Offender.AliasLinkId,
                    penalty.Penalty.OffenderId,
                    penalty.Penalty.PunisherId,
                    penalty.IPv4Address,
                    penalty.Penalty.Offender.NetworkId
                })
                .ToListAsync();

            if (!matchedPenalties.Any())
            {
                var linkIds = (await context.Aliases
                    .Where(alias => alias.IPAddress != null && searchingIps.Contains(alias.IPAddress))
                    .Select(alias => alias.LinkId)
                    .ToListAsync()).Distinct();

                matchedPenalties = await context.Penalties.Where(penalty => penalty.Type == EFPenalty.PenaltyType.Ban)
                    .Where(penalty => penalty.Expires == null || penalty.Expires > lateDateTime)
                    .Where(penalty => penalty.LinkId != null && linkIds.Contains(penalty.LinkId.Value))
                    .OrderByDescending(penalty => penalty.When)
                    .Select(penalty => new
                    {
                        CreatedDateTime = penalty.When,
                        PunisherName = penalty.Punisher.CurrentAlias.Name,
                        OffenderName = penalty.Offender.CurrentAlias.Name,
                        Offense = string.IsNullOrEmpty(penalty.AutomatedOffense)
                            ? penalty.Offense
                            : Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_BAN_INFO_AC_DETECTION"],
                        LinkId = penalty.Offender.AliasLinkId,
                        penalty.OffenderId,
                        penalty.PunisherId,
                        IPv4Address = penalty.Offender.CurrentAlias.IPAddress,
                        penalty.Offender.NetworkId
                    }).ToListAsync();
            }

            var allPenalties = matchedPenalties.Select(penalty => new PenaltyInfo
            {
                DateTime = penalty.CreatedDateTime,
                Offense = penalty.Offense,
                PunisherInfo = new RelatedClientInfo
                {
                    ClientName = penalty.PunisherName.StripColors(),
                    ClientId = penalty.PunisherId,
                },
                OffenderInfo = new RelatedClientInfo
                {
                    ClientName = penalty.OffenderName.StripColors(),
                    ClientId = penalty.OffenderId,
                    IPAddress = penalty.IPv4Address,
                    NetworkId = penalty.NetworkId
                }
            }).ToList();


            if (matchedClientIds.Contains(matchingClient.ClientId))
            {
                continue;
            }

            matchedClientIds.Add(matchingClient.ClientId);
            var relatedEntities =
                allPenalties.Where(penalty => penalty.OffenderInfo.ClientId != matchingClient.ClientId);

            matchedClientIds.AddRange(relatedEntities.Select(client => client.OffenderInfo.ClientId));

            results.Add(new BanInfo
            {
                ClientName = matchingClient.Name.StripColors(),
                ClientId = matchingClient.ClientId,
                NetworkId = matchingClient.NetworkId,
                IPAddress = matchingClient.IPAddress,
                Game = matchingClient.GameName,

                AssociatedPenalties = relatedEntities,
                AttachedPenalty = allPenalties.FirstOrDefault(penalty =>
                    penalty.OffenderInfo.ClientId == matchingClient.ClientId)
            });
        }

        return new ResourceQueryHelperResult<BanInfo>
        {
            RetrievedResultCount = results.Count,
            TotalResultCount = results.Count,
            Results = results
        };
    }

    private IQueryable<EFClient> SetupSearchArgs(BanInfoRequest query, IQueryable<EFClient> source)
    {
        if (!string.IsNullOrEmpty(query.ClientName))
        {
            var nameToSearch = query.ClientName.Trim().ToLower();
            source = source.Where(client =>
                EF.Functions.Like(client.CurrentAlias.SearchableName ?? client.CurrentAlias.Name.ToLower(),
                    $"%{nameToSearch}%"));
        }

        if (!string.IsNullOrEmpty(query.ClientGuid))
        {
            long? parsedGuid = null;
            if (!long.TryParse(query.ClientGuid, NumberStyles.HexNumber, null, out var guid))
            {
                if (!long.TryParse(query.ClientGuid, out var guid2))
                {
                }
                else
                {
                    parsedGuid = guid2;
                }
            }
            else
            {
                parsedGuid = guid;
            }

            if (parsedGuid is not null)
            {
                source = source.Where(client => client.NetworkId == parsedGuid);
            }
        }

        if (query.ClientId is not null)
        {
            source = source.Where(client => client.ClientId == query.ClientId);
        }

        if (string.IsNullOrEmpty(query.ClientIP))
        {
            return source;
        }

        var parsedIp = query.ClientIP.ConvertToIP();
        if (parsedIp is not null)
        {
            source = source.Where(client => client.CurrentAlias.IPAddress == parsedIp);
        }
        else
        {
            query.ClientIP = null;
        }

        return source;
    }
}