using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using LittleShop.Data;
using LittleShop.DTOs;
using LittleShop.Models;

namespace LittleShop.Services;

public class BotMetricsService : IBotMetricsService
{
    private readonly LittleShopContext _context;
    private readonly ILogger<BotMetricsService> _logger;

    public BotMetricsService(LittleShopContext context, ILogger<BotMetricsService> logger)
    {
        _context = context;
        _logger = logger;
    }

    // Metrics Methods
    public async Task<BotMetricDto> RecordMetricAsync(Guid botId, CreateBotMetricDto dto)
    {
        var metric = new BotMetric
        {
            Id = Guid.NewGuid(),
            BotId = botId,
            MetricType = dto.MetricType,
            Value = dto.Value,
            Category = dto.Category,
            Description = dto.Description,
            Metadata = JsonSerializer.Serialize(dto.Metadata),
            RecordedAt = DateTime.UtcNow
        };

        _context.BotMetrics.Add(metric);
        await _context.SaveChangesAsync();

        return MapMetricToDto(metric);
    }

    public async Task<bool> RecordMetricsBatchAsync(Guid botId, BotMetricsBatchDto dto)
    {
        try
        {
            var metrics = dto.Metrics.Select(m => new BotMetric
            {
                Id = Guid.NewGuid(),
                BotId = botId,
                MetricType = m.MetricType,
                Value = m.Value,
                Category = m.Category,
                Description = m.Description,
                Metadata = JsonSerializer.Serialize(m.Metadata),
                RecordedAt = DateTime.UtcNow
            }).ToList();

            _context.BotMetrics.AddRange(metrics);
            await _context.SaveChangesAsync();

            _logger.LogInformation("Recorded {Count} metrics for bot {BotId}", metrics.Count, botId);
            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to record metrics batch for bot {BotId}", botId);
            return false;
        }
    }

    public async Task<IEnumerable<BotMetricDto>> GetBotMetricsAsync(Guid botId, DateTime? startDate = null, DateTime? endDate = null)
    {
        var query = _context.BotMetrics.Where(m => m.BotId == botId);

        if (startDate.HasValue)
            query = query.Where(m => m.RecordedAt >= startDate.Value);

        if (endDate.HasValue)
            query = query.Where(m => m.RecordedAt <= endDate.Value);

        var metrics = await query
            .OrderByDescending(m => m.RecordedAt)
            .Take(1000) // Limit to prevent large results
            .ToListAsync();

        return metrics.Select(MapMetricToDto);
    }

    public async Task<BotMetricsSummaryDto> GetMetricsSummaryAsync(Guid botId, DateTime? startDate = null, DateTime? endDate = null)
    {
        var bot = await _context.Bots.FindAsync(botId);
        if (bot == null)
            return new BotMetricsSummaryDto { BotId = botId };

        var start = startDate ?? DateTime.UtcNow.AddDays(-30);
        var end = endDate ?? DateTime.UtcNow;

        var metrics = await _context.BotMetrics
            .Where(m => m.BotId == botId && m.RecordedAt >= start && m.RecordedAt <= end)
            .ToListAsync();

        var sessions = await _context.BotSessions
            .Where(s => s.BotId == botId && s.StartedAt >= start && s.StartedAt <= end)
            .ToListAsync();

        var summary = new BotMetricsSummaryDto
        {
            BotId = botId,
            BotName = bot.Name,
            PeriodStart = start,
            PeriodEnd = end,
            TotalSessions = sessions.Count,
            UniqueSessions = sessions.Select(s => s.SessionIdentifier).Distinct().Count(),
            TotalOrders = sessions.Sum(s => s.OrderCount),
            TotalRevenue = sessions.Sum(s => s.TotalSpent),
            TotalMessages = sessions.Sum(s => s.MessageCount),
            TotalErrors = (int)metrics.Where(m => m.MetricType == Enums.MetricType.Error).Sum(m => m.Value)
        };

        // Calculate average response time
        var responseTimeMetrics = metrics.Where(m => m.MetricType == Enums.MetricType.ResponseTime).ToList();
        if (responseTimeMetrics.Any())
            summary.AverageResponseTime = responseTimeMetrics.Average(m => m.Value);

        // Calculate uptime percentage
        var uptimeMetrics = metrics.Where(m => m.MetricType == Enums.MetricType.Uptime).ToList();
        if (uptimeMetrics.Any())
        {
            var totalPossibleUptime = (end - start).TotalMinutes;
            var actualUptime = uptimeMetrics.Sum(m => m.Value);
            summary.UptimePercentage = (actualUptime / (decimal)totalPossibleUptime) * 100;
        }

        // Group metrics by type
        summary.MetricsByType = metrics
            .GroupBy(m => m.MetricType.ToString())
            .ToDictionary(g => g.Key, g => g.Sum(m => m.Value));

        // Generate time series data (daily aggregation)
        summary.TimeSeries = GenerateTimeSeries(metrics, start, end);

        return summary;
    }

    // Session Methods
    public async Task<BotSessionDto> StartSessionAsync(Guid botId, CreateBotSessionDto dto)
    {
        var session = new BotSession
        {
            Id = Guid.NewGuid(),
            BotId = botId,
            SessionIdentifier = dto.SessionIdentifier,
            Platform = dto.Platform,
            StartedAt = DateTime.UtcNow,
            LastActivityAt = DateTime.UtcNow,
            Language = dto.Language,
            Country = dto.Country,
            IsAnonymous = dto.IsAnonymous,
            Metadata = JsonSerializer.Serialize(dto.Metadata),
            OrderCount = 0,
            MessageCount = 0,
            TotalSpent = 0
        };

        _context.BotSessions.Add(session);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Started session {SessionId} for bot {BotId}", session.Id, botId);

        return MapSessionToDto(session);
    }

    public async Task<bool> UpdateSessionAsync(Guid sessionId, UpdateBotSessionDto dto)
    {
        var session = await _context.BotSessions.FindAsync(sessionId);
        if (session == null)
            return false;

        session.LastActivityAt = DateTime.UtcNow;

        if (dto.OrderCount.HasValue)
            session.OrderCount = dto.OrderCount.Value;

        if (dto.MessageCount.HasValue)
            session.MessageCount = dto.MessageCount.Value;

        if (dto.TotalSpent.HasValue)
            session.TotalSpent = dto.TotalSpent.Value;

        if (dto.EndSession.HasValue && dto.EndSession.Value)
            session.EndedAt = DateTime.UtcNow;

        if (dto.Metadata != null)
            session.Metadata = JsonSerializer.Serialize(dto.Metadata);

        await _context.SaveChangesAsync();
        return true;
    }

    public async Task<BotSessionDto?> GetSessionAsync(Guid sessionId)
    {
        var session = await _context.BotSessions.FindAsync(sessionId);
        return session != null ? MapSessionToDto(session) : null;
    }

    public async Task<IEnumerable<BotSessionDto>> GetBotSessionsAsync(Guid botId, bool activeOnly = false)
    {
        var query = _context.BotSessions.Where(s => s.BotId == botId);

        if (activeOnly)
            query = query.Where(s => !s.EndedAt.HasValue);

        var sessions = await query
            .OrderByDescending(s => s.StartedAt)
            .Take(100)
            .ToListAsync();

        return sessions.Select(MapSessionToDto);
    }

    public async Task<BotSessionSummaryDto> GetSessionSummaryAsync(Guid botId, DateTime? startDate = null, DateTime? endDate = null)
    {
        var query = _context.BotSessions.Where(s => s.BotId == botId);

        if (startDate.HasValue)
            query = query.Where(s => s.StartedAt >= startDate.Value);

        if (endDate.HasValue)
            query = query.Where(s => s.StartedAt <= endDate.Value);

        var sessions = await query.ToListAsync();

        var summary = new BotSessionSummaryDto
        {
            TotalSessions = sessions.Count,
            ActiveSessions = sessions.Count(s => !s.EndedAt.HasValue),
            CompletedSessions = sessions.Count(s => s.EndedAt.HasValue)
        };

        if (sessions.Any())
        {
            var completedSessions = sessions.Where(s => s.EndedAt.HasValue).ToList();
            if (completedSessions.Any())
            {
                summary.AverageSessionDuration = (decimal)completedSessions
                    .Average(s => (s.EndedAt!.Value - s.StartedAt).TotalMinutes);
            }

            summary.AverageOrdersPerSession = (decimal)sessions.Average(s => s.OrderCount);
            summary.AverageSpendPerSession = sessions.Average(s => s.TotalSpent);

            summary.SessionsByPlatform = sessions
                .GroupBy(s => s.Platform)
                .ToDictionary(g => g.Key, g => g.Count());

            summary.SessionsByCountry = sessions
                .Where(s => !string.IsNullOrEmpty(s.Country))
                .GroupBy(s => s.Country)
                .ToDictionary(g => g.Key, g => g.Count());

            summary.SessionsByLanguage = sessions
                .GroupBy(s => s.Language)
                .ToDictionary(g => g.Key, g => g.Count());
        }

        return summary;
    }

    public async Task<bool> EndSessionAsync(Guid sessionId)
    {
        var session = await _context.BotSessions.FindAsync(sessionId);
        if (session == null || session.EndedAt.HasValue)
            return false;

        session.EndedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        _logger.LogInformation("Ended session {SessionId}", sessionId);
        return true;
    }

    public async Task<int> CleanupInactiveSessionsAsync(int inactiveMinutes = 30)
    {
        var cutoffTime = DateTime.UtcNow.AddMinutes(-inactiveMinutes);
        
        var inactiveSessions = await _context.BotSessions
            .Where(s => !s.EndedAt.HasValue && s.LastActivityAt < cutoffTime)
            .ToListAsync();

        foreach (var session in inactiveSessions)
        {
            session.EndedAt = DateTime.UtcNow;
        }

        await _context.SaveChangesAsync();

        _logger.LogInformation("Cleaned up {Count} inactive sessions", inactiveSessions.Count);
        return inactiveSessions.Count;
    }

    // Helper Methods
    private BotMetricDto MapMetricToDto(BotMetric metric)
    {
        var metadata = new Dictionary<string, object>();
        try
        {
            metadata = JsonSerializer.Deserialize<Dictionary<string, object>>(metric.Metadata) 
                ?? new Dictionary<string, object>();
        }
        catch { }

        return new BotMetricDto
        {
            Id = metric.Id,
            BotId = metric.BotId,
            MetricType = metric.MetricType,
            Value = metric.Value,
            Category = metric.Category,
            Description = metric.Description,
            RecordedAt = metric.RecordedAt,
            Metadata = metadata
        };
    }

    private BotSessionDto MapSessionToDto(BotSession session)
    {
        var metadata = new Dictionary<string, object>();
        try
        {
            metadata = JsonSerializer.Deserialize<Dictionary<string, object>>(session.Metadata) 
                ?? new Dictionary<string, object>();
        }
        catch { }

        return new BotSessionDto
        {
            Id = session.Id,
            BotId = session.BotId,
            SessionIdentifier = session.SessionIdentifier,
            Platform = session.Platform,
            StartedAt = session.StartedAt,
            LastActivityAt = session.LastActivityAt,
            EndedAt = session.EndedAt,
            OrderCount = session.OrderCount,
            MessageCount = session.MessageCount,
            TotalSpent = session.TotalSpent,
            Language = session.Language,
            Country = session.Country,
            IsAnonymous = session.IsAnonymous,
            Metadata = metadata
        };
    }

    private List<TimeSeriesDataPoint> GenerateTimeSeries(List<BotMetric> metrics, DateTime start, DateTime end)
    {
        var dataPoints = new List<TimeSeriesDataPoint>();
        var currentDate = start.Date;

        while (currentDate <= end.Date)
        {
            var dayMetrics = metrics.Where(m => m.RecordedAt.Date == currentDate).ToList();
            
            if (dayMetrics.Any())
            {
                dataPoints.Add(new TimeSeriesDataPoint
                {
                    Timestamp = currentDate,
                    Label = currentDate.ToString("yyyy-MM-dd"),
                    Value = dayMetrics.Sum(m => m.Value)
                });
            }

            currentDate = currentDate.AddDays(1);
        }

        return dataPoints;
    }
}