using System;
using System.Collections.Generic;
using System.Linq;
using Quasar.Server.Models;
using Quasar.Server.Networking;

namespace Quasar.Server.Statistics
{
    public static class ClientStatisticsService
    {
        private const int DefaultHistoryDays = 14;
        private const int MaxPieSegments = 7;
        private const int MaxTagRows = 10;

        public static ClientStatisticsSnapshot CreateSnapshot(IEnumerable<Client> clients, IEnumerable<MockClient> mockClients = null)
        {
            try
            {
                var list = clients?.ToList() ?? new List<Client>();
                var mockList = mockClients?.ToList() ?? new List<MockClient>();
                
                var nowUtc = DateTime.UtcNow;
                var total = list.Count + mockList.Count;
                var online = total; // All clients in the list are online

                var countryStats = BuildCategoryListWithMocks(list, mockList, 
                    c => Normalize(c.Value?.Country), 
                    m => Normalize(m.Value?.Country), 
                    total, MaxPieSegments);
                    
                var osStats = BuildCategoryListWithMocks(list, mockList, 
                    c => Normalize(c.Value?.OperatingSystem), 
                    m => Normalize(m.Value?.OperatingSystem), 
                    total, MaxPieSegments);
                    
                var tagStats = BuildCategoryListWithMocks(list, mockList, 
                    c => string.IsNullOrWhiteSpace(c.Value?.Tag) ? "(none)" : c.Value.Tag, 
                    m => string.IsNullOrWhiteSpace(m.Value?.Tag) ? "(none)" : m.Value.Tag, 
                    total, MaxTagRows);

                return new ClientStatisticsSnapshot(
                    total,
                    online,
                    0,
                    countryStats,
                    osStats,
                    tagStats,
                    nowUtc);
            }
            catch (Exception ex)
            {
                return ClientStatisticsSnapshot.CreateError(ex.Message);
            }
        }

        private static IReadOnlyList<CategoryCount> BuildCategoryListWithMocks(
            IEnumerable<Client> clients, 
            IEnumerable<MockClient> mockClients,
            Func<Client, string> clientSelector, 
            Func<MockClient, string> mockSelector,
            int total, 
            int maxItems)
        {
            // Combine both real and mock clients into a single grouped list
            var clientGroups = clients
                .GroupBy(clientSelector)
                .Select(g => new { Label = string.IsNullOrWhiteSpace(g.Key) ? "Unknown" : g.Key.Trim(), Count = g.Count() });
                
            var mockGroups = mockClients
                .GroupBy(mockSelector)
                .Select(g => new { Label = string.IsNullOrWhiteSpace(g.Key) ? "Unknown" : g.Key.Trim(), Count = g.Count() });
            
            var grouped = clientGroups
                .Concat(mockGroups)
                .GroupBy(g => g.Label)
                .Select(g => new { Label = g.Key, Count = g.Sum(x => x.Count) })
                .OrderByDescending(g => g.Count)
                .ThenBy(g => g.Label)
                .ToList();

            if (grouped.Count == 0)
            {
                return new CategoryCount[0];
            }

            if (maxItems <= 0 || grouped.Count <= maxItems)
            {
                return grouped
                    .Select(g => new CategoryCount(g.Label, g.Count, total > 0 ? (double)g.Count / total : 0))
                    .ToList();
            }

            var top = grouped.Take(maxItems - 1).ToList();
            var otherCount = grouped.Skip(maxItems - 1).Sum(g => g.Count);

            var result = top
                .Select(g => new CategoryCount(g.Label, g.Count, total > 0 ? (double)g.Count / total : 0))
                .ToList();

            if (otherCount > 0)
            {
                result.Add(new CategoryCount("Other", otherCount, total > 0 ? (double)otherCount / total : 0));
            }

            return result;
        }
        
        private static IReadOnlyList<CategoryCount> BuildCategoryList(IEnumerable<Client> source, Func<Client, string> selector, int total, int maxItems)
        {
            var grouped = source
                .GroupBy(selector)
                .Select(g => new { Label = string.IsNullOrWhiteSpace(g.Key) ? "Unknown" : g.Key.Trim(), Count = g.Count() })
                .OrderByDescending(g => g.Count)
                .ThenBy(g => g.Label)
                .ToList();

            if (grouped.Count == 0)
            {
                return new CategoryCount[0];
            }

            if (maxItems <= 0 || grouped.Count <= maxItems)
            {
                return grouped
                    .Select(g => new CategoryCount(g.Label, g.Count, total > 0 ? (double)g.Count / total : 0))
                    .ToList();
            }

            var top = grouped.Take(maxItems - 1).ToList();
            var otherCount = grouped.Skip(maxItems - 1).Sum(g => g.Count);

            var result = top
                .Select(g => new CategoryCount(g.Label, g.Count, total > 0 ? (double)g.Count / total : 0))
                .ToList();

            if (otherCount > 0)
            {
                result.Add(new CategoryCount("Other", otherCount, total > 0 ? (double)otherCount / total : 0));
            }

            return result;
        }

        private static string Normalize(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return "Unknown";
            }

            return value.Trim();
        }
    }
}
