using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.SRFPlay.Api;
using Jellyfin.Plugin.SRFPlay.Api.Models;
using Microsoft.Extensions.Logging;

namespace Jellyfin.Plugin.SRFPlay.Services;

/// <summary>
/// Service for managing topic/category data and filtering.
/// </summary>
public class CategoryService
{
    private readonly ILogger _logger;
    private readonly ILoggerFactory _loggerFactory;
    private readonly TimeSpan _topicsCacheDuration = TimeSpan.FromHours(24);
    private Dictionary<string, PlayV3Topic>? _topicsCache;
    private DateTime _topicsCacheExpiry = DateTime.MinValue;

    /// <summary>
    /// Initializes a new instance of the <see cref="CategoryService"/> class.
    /// </summary>
    /// <param name="loggerFactory">The logger factory.</param>
    public CategoryService(ILoggerFactory loggerFactory)
    {
        _loggerFactory = loggerFactory;
        _logger = loggerFactory.CreateLogger<CategoryService>();
    }

    /// <summary>
    /// Gets all topics for a business unit.
    /// </summary>
    /// <param name="businessUnit">The business unit.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>List of topics.</returns>
    public async Task<List<PlayV3Topic>> GetTopicsAsync(string businessUnit, CancellationToken cancellationToken = default)
    {
        // Return cached topics if still valid
        if (_topicsCache != null && DateTime.UtcNow < _topicsCacheExpiry)
        {
            _logger.LogDebug("Returning cached topics for business unit: {BusinessUnit}", businessUnit);
            return _topicsCache.Values.ToList();
        }

        _logger.LogInformation("Fetching topics for business unit: {BusinessUnit}", businessUnit);
        using var apiClient = new SRFApiClient(_loggerFactory);
        var topics = await apiClient.GetAllTopicsAsync(businessUnit, cancellationToken).ConfigureAwait(false);

        if (topics != null && topics.Count > 0)
        {
            // Cache topics by ID for quick lookups
            _topicsCache = topics
                .Where(t => !string.IsNullOrEmpty(t.Id))
                .ToDictionary(t => t.Id!, t => t);
            _topicsCacheExpiry = DateTime.UtcNow.Add(_topicsCacheDuration);

            _logger.LogInformation("Cached {Count} topics for business unit: {BusinessUnit}", _topicsCache.Count, businessUnit);
        }

        return topics ?? new List<PlayV3Topic>();
    }

    /// <summary>
    /// Gets a topic by ID.
    /// </summary>
    /// <param name="topicId">The topic ID.</param>
    /// <param name="businessUnit">The business unit.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>The topic, or null if not found.</returns>
    public async Task<PlayV3Topic?> GetTopicByIdAsync(string topicId, string businessUnit, CancellationToken cancellationToken = default)
    {
        // Ensure topics are loaded
        if (_topicsCache == null || DateTime.UtcNow >= _topicsCacheExpiry)
        {
            await GetTopicsAsync(businessUnit, cancellationToken).ConfigureAwait(false);
        }

        return _topicsCache?.GetValueOrDefault(topicId);
    }

    /// <summary>
    /// Filters shows by topic ID.
    /// </summary>
    /// <param name="shows">The shows to filter.</param>
    /// <param name="topicId">The topic ID to filter by.</param>
    /// <returns>Filtered list of shows.</returns>
    public IReadOnlyList<PlayV3Show> FilterShowsByTopic(IReadOnlyList<PlayV3Show> shows, string topicId)
    {
        if (string.IsNullOrEmpty(topicId))
        {
            return shows;
        }

        return shows
            .Where(s => s.TopicList != null && s.TopicList.Contains(topicId))
            .ToList();
    }

    /// <summary>
    /// Groups shows by their topics.
    /// </summary>
    /// <param name="shows">The shows to group.</param>
    /// <returns>Dictionary mapping topic IDs to shows.</returns>
    public IReadOnlyDictionary<string, List<PlayV3Show>> GroupShowsByTopics(IReadOnlyList<PlayV3Show> shows)
    {
        var groupedShows = new Dictionary<string, List<PlayV3Show>>();

        foreach (var show in shows)
        {
            if (show.TopicList == null || show.TopicList.Count == 0)
            {
                continue;
            }

            foreach (var topicId in show.TopicList)
            {
                if (!groupedShows.TryGetValue(topicId, out var showList))
                {
                    showList = new List<PlayV3Show>();
                    groupedShows[topicId] = showList;
                }

                showList.Add(show);
            }
        }

        return groupedShows;
    }

    /// <summary>
    /// Gets shows for a specific topic, sorted by number of episodes.
    /// </summary>
    /// <param name="topicId">The topic ID.</param>
    /// <param name="businessUnit">The business unit.</param>
    /// <param name="maxResults">Maximum number of results to return.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>List of shows for the topic.</returns>
    public async Task<List<PlayV3Show>> GetShowsByTopicAsync(
        string topicId,
        string businessUnit,
        int maxResults = 50,
        CancellationToken cancellationToken = default)
    {
        using var apiClient = new SRFApiClient(_loggerFactory);
        var allShows = await apiClient.GetAllShowsAsync(businessUnit, cancellationToken).ConfigureAwait(false);

        if (allShows == null || allShows.Count == 0)
        {
            _logger.LogWarning("No shows available for business unit: {BusinessUnit}", businessUnit);
            return new List<PlayV3Show>();
        }

        var filteredShows = FilterShowsByTopic(allShows, topicId)
            .Where(s => s.NumberOfEpisodes > 0)
            .OrderByDescending(s => s.NumberOfEpisodes)
            .Take(maxResults)
            .ToList();

        _logger.LogDebug("Found {Count} shows for topic {TopicId}", filteredShows.Count, topicId);
        return filteredShows;
    }

    /// <summary>
    /// Gets video count for each topic.
    /// </summary>
    /// <param name="shows">The shows to analyze.</param>
    /// <returns>Dictionary mapping topic IDs to video counts.</returns>
    public IReadOnlyDictionary<string, int> GetVideoCountByTopic(IReadOnlyList<PlayV3Show> shows)
    {
        var topicCounts = new Dictionary<string, int>();

        foreach (var show in shows)
        {
            if (show.TopicList == null || show.TopicList.Count == 0)
            {
                continue;
            }

            foreach (var topicId in show.TopicList)
            {
                if (!topicCounts.TryGetValue(topicId, out var count))
                {
                    count = 0;
                }

                topicCounts[topicId] = count + show.NumberOfEpisodes;
            }
        }

        return topicCounts;
    }

    /// <summary>
    /// Clears the topics cache.
    /// </summary>
    public void ClearCache()
    {
        _topicsCache = null;
        _topicsCacheExpiry = DateTime.MinValue;
        _logger.LogInformation("Topics cache cleared");
    }
}
