using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.SRFPlay.Services;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;

namespace Jellyfin.Plugin.SRFPlay.Channels;

/// <summary>
/// SRF Play channel for browsing and playing content.
/// </summary>
public class SRFPlayChannel : IChannel, IHasCacheKey
{
    private readonly ILogger<SRFPlayChannel> _logger;
    private readonly ILoggerFactory _loggerFactory;
    private readonly ContentRefreshService _contentRefreshService;
    private readonly StreamUrlResolver _streamResolver;
    private readonly StreamProxyService _proxyService;
    private readonly CategoryService? _categoryService;
    private readonly IServerApplicationHost _appHost;

    /// <summary>
    /// Initializes a new instance of the <see cref="SRFPlayChannel"/> class.
    /// </summary>
    /// <param name="loggerFactory">The logger factory.</param>
    /// <param name="contentRefreshService">The content refresh service.</param>
    /// <param name="streamResolver">The stream resolver.</param>
    /// <param name="proxyService">The stream proxy service.</param>
    /// <param name="appHost">The server application host.</param>
    /// <param name="categoryService">The category service (optional).</param>
    public SRFPlayChannel(
        ILoggerFactory loggerFactory,
        ContentRefreshService contentRefreshService,
        StreamUrlResolver streamResolver,
        StreamProxyService proxyService,
        IServerApplicationHost appHost,
        CategoryService? categoryService = null)
    {
        _loggerFactory = loggerFactory;
        _logger = loggerFactory.CreateLogger<SRFPlayChannel>();
        _contentRefreshService = contentRefreshService;
        _streamResolver = streamResolver;
        _proxyService = proxyService;
        _appHost = appHost;
        _categoryService = categoryService;

        if (_categoryService == null)
        {
            _logger.LogWarning("CategoryService not available - category folders will be disabled");
        }

        _logger.LogInformation("=== SRFPlayChannel constructor called! Channel is being instantiated ===");
    }

    /// <inheritdoc />
    public string Name => "SRF Play";

    /// <inheritdoc />
    public string Description => "Swiss Radio and Television video-on-demand content";

    /// <inheritdoc />
    public string DataVersion => "2.0"; // Back to authenticating at channel refresh with auto-refresh for fresh tokens

    /// <inheritdoc />
    public string HomePageUrl => "https://www.srf.ch/play";

    /// <inheritdoc />
    public ChannelParentalRating ParentalRating => ChannelParentalRating.GeneralAudience;

    /// <inheritdoc />
    public InternalChannelFeatures GetChannelFeatures()
    {
        _logger.LogInformation("=== GetChannelFeatures called for SRF Play channel ===");

        return new InternalChannelFeatures
        {
            ContentTypes = new List<ChannelMediaContentType>
            {
                ChannelMediaContentType.Episode,
                ChannelMediaContentType.Movie
            },
            MediaTypes = new List<ChannelMediaType>
            {
                ChannelMediaType.Video
            },
            SupportsSortOrderToggle = false,
            DefaultSortFields = new List<ChannelItemSortField>
            {
                ChannelItemSortField.DateCreated,
                ChannelItemSortField.Name
            },
            MaxPageSize = 50
        };
    }

    /// <inheritdoc />
    public Task<DynamicImageResponse> GetChannelImage(ImageType type, CancellationToken cancellationToken)
    {
        // Could provide a channel logo here
        return Task.FromResult(new DynamicImageResponse
        {
            HasImage = false
        });
    }

    /// <inheritdoc />
    public IEnumerable<ImageType> GetSupportedChannelImages()
    {
        return new List<ImageType>
        {
            ImageType.Primary,
            ImageType.Thumb
        };
    }

    /// <inheritdoc />
    public async Task<ChannelItemResult> GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken)
    {
        _logger.LogInformation("=== GetChannelItems called! FolderId: {FolderId} ===", query.FolderId);

        var items = new List<ChannelItemInfo>();
        var config = Plugin.Instance?.Configuration;

        try
        {
            // Root level - show categories
            if (string.IsNullOrEmpty(query.FolderId))
            {
                items.Add(new ChannelItemInfo
                {
                    Id = "latest",
                    Name = "Latest Videos",
                    Type = ChannelItemType.Folder,
                    FolderType = ChannelFolderType.Container,
                    ImageUrl = null
                });

                items.Add(new ChannelItemInfo
                {
                    Id = "trending",
                    Name = "Trending Videos",
                    Type = ChannelItemType.Folder,
                    FolderType = ChannelFolderType.Container,
                    ImageUrl = null
                });

                items.Add(new ChannelItemInfo
                {
                    Id = "live_sports",
                    Name = "Live Sports & Events",
                    Type = ChannelItemType.Folder,
                    FolderType = ChannelFolderType.Container,
                    ImageUrl = null
                });

                // Add category folders if enabled and CategoryService is available
                if (config?.EnableCategoryFolders == true && _categoryService != null)
                {
                    try
                    {
                        var businessUnit = config.BusinessUnit.ToString().ToLowerInvariant();
                        var topics = await _categoryService.GetTopicsAsync(businessUnit, cancellationToken).ConfigureAwait(false);

                        foreach (var topic in topics.Where(t => !string.IsNullOrEmpty(t.Id)))
                        {
                            // Filter by enabled topics if configured
                            if (config.EnabledTopics != null && config.EnabledTopics.Count > 0 && !config.EnabledTopics.Contains(topic.Id!))
                            {
                                continue;
                            }

                            items.Add(new ChannelItemInfo
                            {
                                Id = $"category_{topic.Id}",
                                Name = topic.Title ?? topic.Id!,
                                Type = ChannelItemType.Folder,
                                FolderType = ChannelFolderType.Container,
                                ImageUrl = null,
                                Overview = topic.Lead
                            });
                        }

                        _logger.LogInformation("Added {Count} category folders", topics.Count);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Failed to load category folders - continuing without categories");
                    }
                }

                return new ChannelItemResult
                {
                    Items = items,
                    TotalRecordCount = items.Count
                };
            }

            // Latest videos
            if (query.FolderId == "latest")
            {
                var urns = await _contentRefreshService.RefreshLatestContentAsync(cancellationToken).ConfigureAwait(false);
                items = await ConvertUrnsToChannelItems(urns, cancellationToken).ConfigureAwait(false);
            }

            // Trending videos
            else if (query.FolderId == "trending")
            {
                var urns = await _contentRefreshService.RefreshTrendingContentAsync(cancellationToken).ConfigureAwait(false);
                items = await ConvertUrnsToChannelItems(urns, cancellationToken).ConfigureAwait(false);
            }

            // Live Sports & Events
            else if (query.FolderId == "live_sports")
            {
                try
                {
                    var businessUnit = config?.BusinessUnit.ToString().ToLowerInvariant() ?? "srf";

                    using var apiClient = new Api.SRFApiClient(_loggerFactory);
                    var scheduledLivestreams = await apiClient.GetScheduledLivestreamsAsync(businessUnit, "SPORT", cancellationToken).ConfigureAwait(false);

                    if (scheduledLivestreams != null)
                    {
                        // Filter for upcoming/current events (within next 7 days) that have URNs
                        var now = DateTime.UtcNow;
                        var weekFromNow = now.AddDays(7);

                        var upcomingEvents = scheduledLivestreams
                            .Where(p => p.Urn != null &&
                                       !string.IsNullOrEmpty(p.Title) &&
                                       p.ValidFrom != null &&
                                       p.ValidFrom.Value.ToUniversalTime() <= weekFromNow &&
                                       (p.ValidTo == null || p.ValidTo.Value.ToUniversalTime() > now))
                            .OrderBy(p => p.ValidFrom)
                            .ToList();

                        _logger.LogInformation("Found {Count} scheduled live sports events", upcomingEvents.Count);

                        var urns = upcomingEvents.Select(e => e.Urn!).ToList();
                        items = await ConvertUrnsToChannelItems(urns, cancellationToken).ConfigureAwait(false);

                        // Enhance items with scheduled time information
                        foreach (var item in items)
                        {
                            var matchingEvent = upcomingEvents.FirstOrDefault(e => item.ProviderIds.ContainsKey("SRF") && item.ProviderIds["SRF"] == e.Urn);
                            if (matchingEvent?.ValidFrom != null)
                            {
                                var eventTime = matchingEvent.ValidFrom.Value;
                                item.Name = $"[{eventTime:dd.MM HH:mm}] {matchingEvent.Title}";
                                item.PremiereDate = eventTime;
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to load live sports events");
                }
            }

            // Category folder - show videos for this category
            else if (query.FolderId?.StartsWith("category_", StringComparison.Ordinal) == true)
            {
                if (_categoryService == null)
                {
                    _logger.LogWarning("CategoryService not available - cannot display category folder");
                }
                else
                {
                    try
                    {
                        var topicId = query.FolderId.Substring("category_".Length);
                        var businessUnit = config?.BusinessUnit.ToString().ToLowerInvariant() ?? "srf";

                        var shows = await _categoryService.GetShowsByTopicAsync(topicId, businessUnit, 20, cancellationToken).ConfigureAwait(false);
                        var urns = new List<string>();

                        using var apiClient = new Api.SRFApiClient(_loggerFactory);

                        foreach (var show in shows)
                        {
                            if (show.Id == null)
                            {
                                continue;
                            }

                            try
                            {
                                var videos = await apiClient.GetVideosForShowAsync(businessUnit, show.Id, cancellationToken).ConfigureAwait(false);
                                if (videos != null && videos.Count > 0)
                                {
                                    _logger.LogDebug("Category {TopicId}, Show {Show} ({ShowId}): Found {Count} videos", topicId, show.Title, show.Id, videos.Count);

                                    // Filter to videos that are actually published and not expired
                                    var now = DateTime.UtcNow;
                                    var availableVideos = videos.Where(v =>
                                        (v.ValidFrom == null || v.ValidFrom.Value.ToUniversalTime() <= now) &&
                                        (v.ValidTo == null || v.ValidTo.Value.ToUniversalTime() > now)).ToList();

                                    _logger.LogDebug("Category {TopicId}, Show {Show}: {AvailableCount} available out of {TotalCount} videos", topicId, show.Title, availableVideos.Count, videos.Count);

                                    if (availableVideos.Count > 0)
                                    {
                                        // Get most recent available video from this show
                                        var latestVideo = availableVideos.OrderByDescending(v => v.Date).FirstOrDefault();
                                        if (latestVideo?.Urn != null)
                                        {
                                            urns.Add(latestVideo.Urn);
                                            _logger.LogInformation(
                                                "Category {TopicId}: Added video from show {Show}: {Title} (URN: {Urn}, Date: {Date}, ValidFrom: {ValidFrom}, ValidTo: {ValidTo})",
                                                topicId,
                                                show.Title,
                                                latestVideo.Title,
                                                latestVideo.Urn,
                                                latestVideo.Date,
                                                latestVideo.ValidFrom,
                                                latestVideo.ValidTo);
                                        }
                                        else
                                        {
                                            _logger.LogWarning("Category {TopicId}, Show {Show}: Latest available video has null URN", topicId, show.Title);
                                        }
                                    }
                                    else
                                    {
                                        _logger.LogDebug("Category {TopicId}, Show {Show}: No available videos (all expired or not yet published)", topicId, show.Title);
                                    }
                                }
                                else
                                {
                                    _logger.LogDebug("Category {TopicId}, Show {Show} ({ShowId}): No videos returned from API", topicId, show.Title, show.Id);
                                }
                            }
                            catch (Exception ex)
                            {
                                _logger.LogWarning(ex, "Error fetching videos for show {ShowId} in category {TopicId}", show.Id, topicId);
                            }

                            if (cancellationToken.IsCancellationRequested)
                            {
                                break;
                            }
                        }

                        items = await ConvertUrnsToChannelItems(urns, cancellationToken).ConfigureAwait(false);
                        _logger.LogInformation("Found {Count} videos for category {TopicId}", items.Count, topicId);
                    }
                    catch (Exception ex)
                    {
                        _logger.LogError(ex, "Failed to load category videos");
                    }
                }
            }

            _logger.LogInformation("Returning {Count} channel items for folder {FolderId}", items.Count, query.FolderId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting channel items for folder {FolderId}", query.FolderId);
        }

        return new ChannelItemResult
        {
            Items = items,
            TotalRecordCount = items.Count
        };
    }

    /// <inheritdoc />
    public string? GetCacheKey(string? userId)
    {
        var config = Plugin.Instance?.Configuration;
        var enabledTopics = config?.EnabledTopics != null && config.EnabledTopics.Count > 0
            ? string.Join(",", config.EnabledTopics)
            : "all";
        var date = DateTime.Now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
        return $"{config?.BusinessUnit}_{config?.EnableLatestContent}_{config?.EnableTrendingContent}_{config?.EnableCategoryFolders}_{enabledTopics}_{date}";
    }

    private async Task<List<ChannelItemInfo>> ConvertUrnsToChannelItems(List<string> urns, CancellationToken cancellationToken)
    {
        var items = new List<ChannelItemInfo>();
        var config = Plugin.Instance?.Configuration;

        if (config == null)
        {
            _logger.LogWarning("Plugin configuration is null");
            return items;
        }

        _logger.LogInformation("Converting {Count} URNs to channel items", urns.Count);

        using var apiClient = new Api.SRFApiClient(_loggerFactory);
        int successCount = 0;
        int failedCount = 0;
        int expiredCount = 0;
        int noStreamCount = 0;

        foreach (var urn in urns.Take(50)) // Limit to 50 items per request
        {
            try
            {
                _logger.LogDebug("Processing URN: {Urn}", urn);
                var mediaComposition = await apiClient.GetMediaCompositionByUrnAsync(urn, cancellationToken).ConfigureAwait(false);

                if (mediaComposition?.ChapterList == null || mediaComposition.ChapterList.Count == 0)
                {
                    _logger.LogWarning("URN {Urn}: No media composition or chapters found", urn);
                    failedCount++;
                    continue;
                }

                var chapter = mediaComposition.ChapterList[0];

                // Check if content is expired
                if (_streamResolver.IsContentExpired(chapter))
                {
                    _logger.LogDebug("URN {Urn}: Content expired (ValidTo: {ValidTo})", urn, chapter.ValidTo);
                    expiredCount++;
                    continue;
                }

                // Check if content has playable streams
                if (!_streamResolver.HasPlayableContent(chapter))
                {
                    _logger.LogWarning("URN {Urn}: No playable content (likely DRM protected)", urn);
                    noStreamCount++;
                    continue;
                }

                // Generate deterministic GUID from URN
                var itemId = UrnToGuid(urn);

                // Get stream URL and authenticate it
                var streamUrl = _streamResolver.GetStreamUrl(chapter, config.QualityPreference);

                // Skip scheduled livestreams that haven't started yet (no stream URL available)
                if (chapter.Type == "SCHEDULED_LIVESTREAM" && string.IsNullOrEmpty(streamUrl))
                {
                    _logger.LogDebug(
                        "URN {Urn}: Skipping upcoming livestream '{Title}' - stream not yet available (starts at {ValidFrom})",
                        urn,
                        chapter.Title,
                        chapter.ValidFrom);
                    continue;
                }

                // Authenticate the stream URL with fresh token
                if (!string.IsNullOrEmpty(streamUrl))
                {
                    streamUrl = await _streamResolver.GetAuthenticatedStreamUrlAsync(streamUrl, cancellationToken).ConfigureAwait(false);
                }

                // Skip items without a valid stream URL
                if (string.IsNullOrEmpty(streamUrl))
                {
                    _logger.LogWarning(
                        "URN {Urn}: Skipping '{Title}' - no valid stream URL available",
                        urn,
                        chapter.Title);
                    noStreamCount++;
                    continue;
                }

                // Register stream with proxy service
                _proxyService.RegisterStream(itemId, streamUrl);

                // Get the server URL for proxy - prefer configured public URL for remote clients
                var serverUrl = !string.IsNullOrWhiteSpace(config.PublicServerUrl)
                    ? config.PublicServerUrl.TrimEnd('/') // Use configured public URL (important for Android/remote clients)
                    : _appHost.GetSmartApiUrl(string.Empty); // Fall back to Jellyfin's smart URL resolution

                // Create proxy URL as absolute HTTP URL (required for ffmpeg)
                // Use the actual server URL so remote clients can access it
                var proxyUrl = $"{serverUrl}/Plugins/SRFPlay/Proxy/{itemId}/master.m3u8";

                // Build overview
                var overview = chapter.Description ?? chapter.Lead;

                // Get image URL - prefer chapter image, fall back to show image if available
                var imageUrl = chapter.ImageUrl;
                if (string.IsNullOrEmpty(imageUrl) && mediaComposition.Show != null)
                {
                    imageUrl = mediaComposition.Show.ImageUrl;
                    _logger.LogDebug("URN {Urn}: Using show image as fallback", urn);
                }

                if (string.IsNullOrEmpty(imageUrl))
                {
                    _logger.LogWarning("URN {Urn}: No image URL available for '{Title}'", urn, chapter.Title);
                }

                // Use ValidFrom for premiere date if this is a scheduled livestream, otherwise use Date
                var premiereDate = chapter.Type == "SCHEDULED_LIVESTREAM" ? chapter.ValidFrom?.ToUniversalTime() : chapter.Date?.ToUniversalTime();

                // Store authenticated URL - tokens refresh automatically via scheduled channel scans
                var item = new ChannelItemInfo
                {
                    Id = itemId,
                    Name = chapter.Title,
                    Overview = overview,
                    ImageUrl = imageUrl,
                    Type = ChannelItemType.Media,
                    ContentType = ChannelMediaContentType.Episode,
                    MediaType = ChannelMediaType.Video,
                    DateCreated = chapter.Date?.ToUniversalTime(),
                    PremiereDate = premiereDate,
                    ProductionYear = chapter.Date?.Year,
                    RunTimeTicks = chapter.Duration > 0 ? TimeSpan.FromMilliseconds(chapter.Duration).Ticks : null,
                    ProviderIds = new Dictionary<string, string>
                    {
                        { "SRF", urn }
                    },
                    MediaSources = new List<MediaSourceInfo>
                    {
                        new MediaSourceInfo
                        {
                            Id = itemId,
                            Name = chapter.Title,
                            Path = proxyUrl, // Proxy URL instead of direct Akamai URL
                            Protocol = MediaBrowser.Model.MediaInfo.MediaProtocol.Http,
                            Container = "hls",
                            SupportsDirectStream = true,
                            SupportsDirectPlay = true, // ✅ Enabled! Proxy handles auth
                            SupportsTranscoding = true,
                            IsRemote = false, // False because it's a local proxy endpoint
                            Type = MediaBrowser.Model.Dto.MediaSourceType.Default,
                            VideoType = VideoType.VideoFile,
                            RequiresOpening = false,
                            RequiresClosing = false,
                            SupportsProbing = false, // Disable probing for proxy URLs
                            ReadAtNativeFramerate = false,
                            MediaStreams = new List<MediaBrowser.Model.Entities.MediaStream>
                            {
                                new MediaBrowser.Model.Entities.MediaStream
                                {
                                    Type = MediaBrowser.Model.Entities.MediaStreamType.Video,
                                    Codec = "h264",
                                    Profile = "high",
                                    IsInterlaced = false,
                                    IsDefault = true,
                                    Index = 0
                                },
                                new MediaBrowser.Model.Entities.MediaStream
                                {
                                    Type = MediaBrowser.Model.Entities.MediaStreamType.Audio,
                                    Codec = "aac",
                                    IsDefault = true,
                                    Index = 1
                                }
                            }
                        }
                    }
                };

                // Add series info if available
                if (mediaComposition.Show != null)
                {
                    item.SeriesName = mediaComposition.Show.Title;
                }

                items.Add(item);
                successCount++;
                _logger.LogInformation("URN {Urn}: Successfully converted to channel item - {Title}", urn, chapter.Title);
                _logger.LogInformation(
                    "URN {Urn}: MediaSource configured - DirectStream={DirectStream}, DirectPlay={DirectPlay}, Transcoding={Transcoding}, Container={Container}",
                    urn,
                    true,
                    true,
                    true,
                    "hls");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error converting URN {Urn} to channel item", urn);
                failedCount++;
            }
        }

        _logger.LogInformation(
            "Conversion complete: {Success} successful, {Failed} failed, {Expired} expired, {NoStream} no stream",
            successCount,
            failedCount,
            expiredCount,
            noStreamCount);

        return items;
    }

    /// <summary>
    /// Generates a deterministic GUID from a URN.
    /// This ensures the same URN always produces the same GUID.
    /// MD5 is used for non-cryptographic purposes only (generating IDs).
    /// </summary>
    /// <param name="urn">The URN to convert.</param>
    /// <returns>A deterministic GUID.</returns>
#pragma warning disable CA5351 // MD5 is used for non-cryptographic purposes (ID generation)
    private static string UrnToGuid(string urn)
    {
        // Use MD5 to generate a deterministic hash from the URN
        var hash = MD5.HashData(Encoding.UTF8.GetBytes(urn));

        // Convert the first 16 bytes to a GUID
        var guid = new Guid(hash);
        return guid.ToString();
    }
#pragma warning restore CA5351

    /// <inheritdoc />
    public bool IsEnabledFor(string userId)
    {
        return true;
    }
}
