using EonaCat.Logger.LogServer.Hubs;
using EonaCat.Logger.Server.Data;
using EonaCat.Logger.Server.Models;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Text.Json;

namespace EonaCat.Logger.Server.Services;

public class LogService : ILogService
{
    private readonly LoggerDbContext _context;
    private readonly IMemoryCache _cache;
    private readonly IHubContext<LogHub> _hub;

    public LogService(LoggerDbContext context, IHubContext<LogHub> hub, IMemoryCache cache)
    {
        _context = context;
        _hub = hub;
        _cache = cache;
    }

    public async Task AddLogsAsync(List<LogEntry> entries)
    {
        if (entries == null || entries.Count == 0)
        {
            return;
        }

        // Generate Unique ids
        foreach (var log in entries)
        {
            if (string.IsNullOrWhiteSpace(log.Id) || _context.LogEntries.Any(e => e.Id == log.Id))
            {
                log.Id = Guid.NewGuid().ToString();
            }
        }

        // Save logs
        await _context.LogEntries.AddRangeAsync(entries);
        await _context.SaveChangesAsync();

        // Invalidate stats cache
        _cache.Remove("stats");

        // Generate payload for SignalR
        var payload = entries.Select(log => new
        {
            id = log.Id,
            level = log.Level,
            message = log.Message,
            applicationName = log.ApplicationName,
            environment = log.Environment,
            timestamp = log.Timestamp,
            machineName = log.MachineName,
            correlationId = log.CorrelationId
        });

        // // Send via SignalR
        _ = Task.Run(() =>
            _hub.Clients.All.SendAsync("NewLogBatch", payload)
        );
    }

    public async Task<LogEntry?> GetLogByIdAsync(string id)
    {
        return await _context.LogEntries.FindAsync(id);
    }

    public async Task<PagedResult<LogEntry>> GetLogsAsync(LogQueryParams queryParams)
    {
        var query = _context.LogEntries.AsQueryable();

        if (!string.IsNullOrEmpty(queryParams.Search))
        {
            var search = queryParams.Search.ToLower();
            query = query.Where(l =>
                l.Message.ToLower().Contains(search) ||
                (l.Exception != null && l.Exception.ToLower().Contains(search)) ||
                l.Category.ToLower().Contains(search) ||
                l.ApplicationName.ToLower().Contains(search));
        }

        if (!string.IsNullOrEmpty(queryParams.Application))
            query = query.Where(l => l.ApplicationName == queryParams.Application);

        if (!string.IsNullOrEmpty(queryParams.Environment))
            query = query.Where(l => l.Environment == queryParams.Environment);

        if (!string.IsNullOrEmpty(queryParams.Category))
            query = query.Where(l => l.Category == queryParams.Category);

        if (queryParams.Level.HasValue)
            query = query.Where(l => l.Level == queryParams.Level.Value);

        if (queryParams.StartDate.HasValue)
            query = query.Where(l => l.Timestamp >= queryParams.StartDate.Value);

        if (queryParams.EndDate.HasValue)
            query = query.Where(l => l.Timestamp <= queryParams.EndDate.Value);

        if (!string.IsNullOrEmpty(queryParams.UserId))
            query = query.Where(l => l.UserId == queryParams.UserId);

        if (!string.IsNullOrEmpty(queryParams.CorrelationId))
            query = query.Where(l => l.CorrelationId == queryParams.CorrelationId);

        var totalCount = await query.CountAsync();

        query = queryParams.SortOrder?.ToLower() == "asc"
            ? query.OrderBy(l => l.Timestamp)
            : query.OrderByDescending(l => l.Timestamp);

        var items = await query
            .Skip((queryParams.Page - 1) * queryParams.PageSize)
            .Take(queryParams.PageSize)
            .ToListAsync();

        return new PagedResult<LogEntry>
        {
            Items = items,
            TotalCount = totalCount,
            Page = queryParams.Page,
            PageSize = queryParams.PageSize
        };
    }

    public async Task<Dictionary<string, object>> GetStatsAsync(string? app, string? env, bool skipCache = false)
    {
        var cacheKey = $"stats_{app}_{env}";

        if (!skipCache && _cache.TryGetValue(cacheKey, out Dictionary<string, object>? cachedStats))
            return cachedStats!;

        var query = _context.LogEntries.AsQueryable();

        if (!string.IsNullOrEmpty(app))
            query = query.Where(l => l.ApplicationName == app);

        if (!string.IsNullOrEmpty(env))
            query = query.Where(l => l.Environment == env);

        var now = DateTime.UtcNow;
        var last24Hours = now.AddHours(-24);
        var lastHour = now.AddHours(-1);

        var counts = await query
            .GroupBy(l => 1)
            .Select(g => new
            {
                Total = g.Count(),
                Last24Hours = g.Count(l => l.Timestamp >= last24Hours),
                LastHour = g.Count(l => l.Timestamp >= lastHour),
                ErrorCount = g.Count(l => l.Level >= 4),
                WarningCount = g.Count(l => l.Level == 3),
                CriticalCount = g.Count(l => l.Level == 5),
                SecurityCount = g.Count(l => l.Level == 6)
            })
            .FirstOrDefaultAsync() ?? new { Total = 0, Last24Hours = 0, LastHour = 0, ErrorCount = 0, WarningCount = 0, CriticalCount = 0, SecurityCount = 0 };

        var applicationsCount = await _context.LogEntries
            .Select(l => l.ApplicationName)
            .Distinct()
            .CountAsync();

        var byLevel = await query
            .GroupBy(l => l.Level)
            .Select(g => new { Level = g.Key, Count = g.Count() })
            .ToListAsync();

        var topErrors = await query
            .Where(l => l.Level >= 4 && l.Timestamp >= last24Hours)
            .GroupBy(l => l.Message)
            .Select(g => new { Message = g.Key, Count = g.Count() })
            .OrderByDescending(x => x.Count)
            .Take(5)
            .ToListAsync();

        var topApplications = await query
            .Where(l => l.Timestamp >= last24Hours)
            .GroupBy(l => l.ApplicationName)
            .Select(g => new { Application = g.Key, Count = g.Count() })
            .OrderByDescending(x => x.Count)
            .Take(5)
            .ToListAsync();

        var stats = new Dictionary<string, object>
        {
            ["totalLogs"] = counts.Total,
            ["last24Hours"] = counts.Last24Hours,
            ["lastHour"] = counts.LastHour,
            ["errorCount"] = counts.ErrorCount,
            ["warningCount"] = counts.WarningCount,
            ["criticalCount"] = counts.CriticalCount,
            ["securityCount"] = counts.SecurityCount,
            ["applications"] = applicationsCount,
            ["byLevel"] = byLevel,
            ["topErrors"] = topErrors,
            ["topApplications"] = topApplications
        };

        _cache.Set(cacheKey, stats, TimeSpan.FromSeconds(30));
        return stats;
    }



    public async Task<bool> ValidateApiKeyAsync(string apiKey)
    {
        return await _context.Applications.AnyAsync(a => a.ApiKey == apiKey && a.IsActive);
    }

    public async Task<List<string>> GetApplicationsAsync()
    {
        return await _context.LogEntries
            .Select(l => l.ApplicationName)
            .Distinct()
            .OrderBy(a => a)
            .ToListAsync();
    }

    public async Task<List<string>> GetEnvironmentsAsync()
    {
        return await _context.LogEntries
            .Select(l => l.Environment)
            .Distinct()
            .OrderBy(e => e)
            .ToListAsync();
    }

    public async Task<List<TrendPoint>> GetLogTrendAsync(int hours)
    {
        var startTime = DateTime.UtcNow.AddHours(-hours);
        var interval = hours > 24 ? 60 : hours > 6 ? 30 : 15;

        var logs = await _context.LogEntries
            .Where(l => l.Timestamp >= startTime)
            .ToListAsync();

        var trend = logs
            .GroupBy(l => new DateTime(
                l.Timestamp.Year,
                l.Timestamp.Month,
                l.Timestamp.Day,
                l.Timestamp.Hour,
                (l.Timestamp.Minute / interval) * interval,
                0))
            .Select(g => new TrendPoint
            {
                Timestamp = g.Key,
                Count = g.Count(),
                ByLevel = g.GroupBy(l => l.Level)
                    .ToDictionary(lg => lg.Key, lg => lg.Count())
            })
            .OrderBy(t => t.Timestamp)
            .ToList();

        return trend;
    }
}