using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.SRFPlay.Api;
using Jellyfin.Plugin.SRFPlay.Services;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;

namespace Jellyfin.Plugin.SRFPlay.Providers;

/// <summary>
/// Provides media sources (playback URLs) for SRF Play content.
/// </summary>
public class SRFMediaProvider : IMediaSourceProvider
{
    private readonly ILogger<SRFMediaProvider> _logger;
    private readonly ILoggerFactory _loggerFactory;
    private readonly MetadataCache _metadataCache;
    private readonly StreamUrlResolver _streamResolver;
    private readonly StreamProxyService _proxyService;
    private readonly IServerApplicationHost _appHost;

    /// <summary>
    /// Initializes a new instance of the <see cref="SRFMediaProvider"/> class.
    /// </summary>
    /// <param name="loggerFactory">The logger factory.</param>
    /// <param name="metadataCache">The metadata cache.</param>
    /// <param name="streamResolver">The stream URL resolver.</param>
    /// <param name="proxyService">The stream proxy service.</param>
    /// <param name="appHost">The server application host.</param>
    public SRFMediaProvider(
        ILoggerFactory loggerFactory,
        MetadataCache metadataCache,
        StreamUrlResolver streamResolver,
        StreamProxyService proxyService,
        IServerApplicationHost appHost)
    {
        _loggerFactory = loggerFactory;
        _logger = loggerFactory.CreateLogger<SRFMediaProvider>();
        _metadataCache = metadataCache;
        _streamResolver = streamResolver;
        _proxyService = proxyService;
        _appHost = appHost;
    }

    /// <summary>
    /// Gets the provider name.
    /// </summary>
    public string Name => "SRF Play";

    /// <summary>
    /// Gets media sources for the specified item.
    /// </summary>
    /// <param name="item">The item.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>List of media sources.</returns>
    public async Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
    {
        var sources = new List<MediaSourceInfo>();

        try
        {
            // Log detailed information about the request
            var stackTrace = new System.Diagnostics.StackTrace(true);
            var callingMethod = stackTrace.GetFrame(1)?.GetMethod();
            _logger.LogInformation(
                "GetMediaSources called - Item: {ItemName}, Type: {ItemType}, Id: {ItemId}, CalledBy: {CallingMethod}",
                item.Name,
                item.GetType().Name,
                item.Id,
                callingMethod?.DeclaringType?.Name + "." + callingMethod?.Name);

            // Check if this is an SRF item
            if (!item.ProviderIds.TryGetValue("SRF", out var urn) || string.IsNullOrEmpty(urn))
            {
                _logger.LogDebug("Item {ItemName} is not an SRF item, returning empty sources", item.Name);
                return sources;
            }

            _logger.LogInformation("Getting media sources for URN: {Urn}, Item: {ItemName}", urn, item.Name);

            var config = Plugin.Instance?.Configuration;
            if (config == null)
            {
                return sources;
            }

            // For scheduled livestreams, use shorter cache TTL (5 minutes) so they refresh when they go live
            // For regular content, use configured cache duration
            var cacheDuration = urn.Contains("scheduled_livestream", StringComparison.OrdinalIgnoreCase)
                ? 5
                : config.CacheDurationMinutes;

            // Try cache first
            var mediaComposition = _metadataCache.GetMediaComposition(urn, cacheDuration);

            // If not in cache, fetch from API
            if (mediaComposition == null)
            {
                using var apiClient = new SRFApiClient(_loggerFactory);
                mediaComposition = await apiClient.GetMediaCompositionByUrnAsync(urn, cancellationToken).ConfigureAwait(false);

                if (mediaComposition != null)
                {
                    _metadataCache.SetMediaComposition(urn, mediaComposition);
                }
            }

            if (mediaComposition?.ChapterList == null || mediaComposition.ChapterList.Count == 0)
            {
                _logger.LogWarning("No chapters found for URN: {Urn}", urn);
                return sources;
            }

            // Get the first chapter (main video)
            var chapter = mediaComposition.ChapterList[0];

            // Check if content is expired
            if (_streamResolver.IsContentExpired(chapter))
            {
                _logger.LogWarning("Content expired for URN: {Urn}, ValidTo: {ValidTo}", urn, chapter.ValidTo);
                return sources;
            }

            // Check if content has playable streams
            if (!_streamResolver.HasPlayableContent(chapter))
            {
                _logger.LogWarning("No playable content found for URN: {Urn}", urn);
                return sources;
            }

            // Get stream URL based on quality preference
            var streamUrl = _streamResolver.GetStreamUrl(chapter, config.QualityPreference);

            // For scheduled livestreams, always fetch fresh data to ensure stream URL is current
            if (chapter.Type == "SCHEDULED_LIVESTREAM" && string.IsNullOrEmpty(streamUrl))
            {
                _logger.LogDebug("URN {Urn}: Scheduled livestream has no stream URL, fetching fresh data", urn);

                using var freshApiClient = new SRFApiClient(_loggerFactory);
                var freshMediaComposition = await freshApiClient.GetMediaCompositionByUrnAsync(urn, cancellationToken).ConfigureAwait(false);

                if (freshMediaComposition?.ChapterList != null && freshMediaComposition.ChapterList.Count > 0)
                {
                    var freshChapter = freshMediaComposition.ChapterList[0];
                    streamUrl = _streamResolver.GetStreamUrl(freshChapter, config.QualityPreference);

                    if (!string.IsNullOrEmpty(streamUrl))
                    {
                        // Update cache with fresh data
                        _metadataCache.SetMediaComposition(urn, freshMediaComposition);
                        chapter = freshChapter;
                        _logger.LogInformation("URN {Urn}: Got fresh stream URL for scheduled livestream", urn);
                    }
                }
            }

            if (string.IsNullOrEmpty(streamUrl))
            {
                _logger.LogWarning("Could not resolve stream URL for URN: {Urn}", urn);
                return sources;
            }

            // Authenticate the stream URL (required for all SRF streams, especially livestreams)
            if (!string.IsNullOrEmpty(streamUrl))
            {
                streamUrl = await _streamResolver.GetAuthenticatedStreamUrlAsync(streamUrl, cancellationToken).ConfigureAwait(false);
                _logger.LogDebug("Authenticated stream URL for URN: {Urn}", urn);
            }

            // Register stream with proxy service
            var itemIdStr = item.Id.ToString("N"); // Use hex format without dashes
            _proxyService.RegisterStream(itemIdStr, 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
            // Include item ID as query parameter to preserve it during transcoding
            var proxyUrl = $"{serverUrl}/Plugins/SRFPlay/Proxy/{itemIdStr}/master.m3u8?itemId={itemIdStr}";

            _logger.LogInformation(
                "Using proxy URL for item {ItemId}: {ProxyUrl} (PublicServerUrl configured: {IsPublicConfigured})",
                itemIdStr,
                proxyUrl,
                !string.IsNullOrWhiteSpace(config.PublicServerUrl));

            // Detect if this is a live stream
            var isLiveStream = chapter.Type == "SCHEDULED_LIVESTREAM" || urn.Contains("livestream", StringComparison.OrdinalIgnoreCase);

            // Create media source using proxy URL - enables DirectPlay!
            var mediaSource = new MediaSourceInfo
            {
                Id = itemIdStr, // Must match the ID used in proxy URL registration
                Name = chapter.Title,
                Path = proxyUrl, // Proxy URL instead of direct Akamai URL
                Protocol = 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 = MediaSourceType.Default,
                RunTimeTicks = chapter.Duration > 0 ? TimeSpan.FromMilliseconds(chapter.Duration).Ticks : null,
                VideoType = VideoType.VideoFile,
                IsInfiniteStream = isLiveStream, // True for live streams!
                RequiresOpening = false,
                RequiresClosing = false,
                SupportsProbing = false, // Disable probing for proxy URLs
                ReadAtNativeFramerate = isLiveStream, // Read at native framerate for live streams
                MediaStreams = new List<MediaBrowser.Model.Entities.MediaStream>
                {
                    new MediaBrowser.Model.Entities.MediaStream
                    {
                        Type = MediaStreamType.Video,
                        Codec = "h264",
                        Profile = "high",
                        IsInterlaced = false,
                        IsDefault = true,
                        Index = 0
                    },
                    new MediaBrowser.Model.Entities.MediaStream
                    {
                        Type = MediaStreamType.Audio,
                        Codec = "aac",
                        IsDefault = true,
                        Index = 1
                    }
                }
            };

            sources.Add(mediaSource);
            _logger.LogInformation("Resolved stream URL for {Title}: {Url}", chapter.Title, streamUrl);
            _logger.LogInformation(
                "MediaSource created - Id={Id}, DirectStream={DirectStream}, DirectPlay={DirectPlay}, Probing={Probing}, Container={Container}, Protocol={Protocol}, IsRemote={IsRemote}, IsLiveStream={IsLiveStream}",
                mediaSource.Id,
                mediaSource.SupportsDirectStream,
                mediaSource.SupportsDirectPlay,
                mediaSource.SupportsProbing,
                mediaSource.Container,
                mediaSource.Protocol,
                mediaSource.IsRemote,
                isLiveStream);
            _logger.LogInformation(
                "MediaSource capabilities - SupportsTranscoding={Transcoding}, RequiresOpening={RequiresOpening}, RequiresClosing={RequiresClosing}, Type={Type}, IsInfiniteStream={IsInfiniteStream}",
                mediaSource.SupportsTranscoding,
                mediaSource.RequiresOpening,
                mediaSource.RequiresClosing,
                mediaSource.Type,
                mediaSource.IsInfiniteStream);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting media sources for item: {Name}", item.Name);
        }

        return sources;
    }

    /// <summary>
    /// Gets direct stream provider by unique ID.
    /// </summary>
    /// <param name="uniqueId">The unique ID.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>The direct stream provider.</returns>
    public Task<IDirectStreamProvider?> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
    {
        _logger.LogInformation("GetDirectStreamProviderByUniqueId called with uniqueId: {UniqueId}", uniqueId);
        // Not needed for HTTP streams
        return Task.FromResult<IDirectStreamProvider?>(null);
    }

    /// <inheritdoc />
    public Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
    {
        _logger.LogWarning("OpenMediaSource called with openToken: {OpenToken} - This should not be called for HTTP streams!", openToken);
        // Not needed for static HTTP streams
        throw new NotImplementedException();
    }
}
