using System;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.SRFPlay.Api.Models;
using Jellyfin.Plugin.SRFPlay.Configuration;
using Jellyfin.Plugin.SRFPlay.Constants;
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;

namespace Jellyfin.Plugin.SRFPlay.Services;

/// <summary>
/// Service for resolving stream URLs from media composition resources.
/// </summary>
public class StreamUrlResolver : IStreamUrlResolver
{
    private readonly ILogger<StreamUrlResolver> _logger;
    private readonly IHttpClientFactory _httpClientFactory;

    /// <summary>
    /// Initializes a new instance of the <see cref="StreamUrlResolver"/> class.
    /// </summary>
    /// <param name="logger">The logger instance.</param>
    /// <param name="httpClientFactory">The HTTP client factory.</param>
    public StreamUrlResolver(ILogger<StreamUrlResolver> logger, IHttpClientFactory httpClientFactory)
    {
        _logger = logger;
        _httpClientFactory = httpClientFactory;
    }

    /// <summary>
    /// Gets the best stream URL from a chapter based on quality preference.
    /// </summary>
    /// <param name="chapter">The chapter containing resources.</param>
    /// <param name="qualityPreference">The quality preference.</param>
    /// <returns>The stream URL, or null if no suitable stream found.</returns>
    public string? GetStreamUrl(Chapter chapter, QualityPreference qualityPreference)
    {
        if (chapter?.ResourceList == null || chapter.ResourceList.Count == 0)
        {
            _logger.LogWarning("No resources found for chapter: {ChapterId}", chapter?.Id);
            return null;
        }

        _logger.LogInformation(
            "Processing chapter {ChapterId} with {ResourceCount} resources",
            chapter.Id,
            chapter.ResourceList.Count);

        // Filter out DRM-protected content
        var nonDrmResources = chapter.ResourceList
            .Where(r => r.IsPlayable)
            .ToList();

        _logger.LogInformation(
            "Chapter {ChapterId}: Total resources={Total}, Non-DRM resources={NonDrm}",
            chapter.Id,
            chapter.ResourceList.Count,
            nonDrmResources.Count);

        if (nonDrmResources.Count == 0)
        {
            _logger.LogWarning("All resources for chapter {ChapterId} require DRM", chapter.Id);
            // Log what DRM types are present
            foreach (var resource in chapter.ResourceList)
            {
                _logger.LogDebug(
                    "DRM resource: Protocol={Protocol}, Streaming={Streaming}, DRM={Drm}",
                    resource.Protocol,
                    resource.Streaming,
                    resource.DrmList);
            }

            return null;
        }

        // Prefer HLS protocol
        var hlsResources = nonDrmResources
            .Where(r => string.Equals(r.Protocol, "HLS", StringComparison.OrdinalIgnoreCase) ||
                       string.Equals(r.Streaming, "HLS", StringComparison.OrdinalIgnoreCase) ||
                       r.Url.Contains(".m3u8", StringComparison.OrdinalIgnoreCase))
            .ToList();

        _logger.LogInformation(
            "Chapter {ChapterId}: HLS resources found={HlsCount}",
            chapter.Id,
            hlsResources.Count);

        // Log all HLS resources with their quality info to help debug quality selection
        foreach (var resource in hlsResources)
        {
            _logger.LogInformation(
                "Available HLS resource - Quality={Quality}, Protocol={Protocol}, Streaming={Streaming}, URL={Url}",
                resource.Quality ?? "NULL",
                resource.Protocol ?? "NULL",
                resource.Streaming ?? "NULL",
                resource.Url);
        }

        if (hlsResources.Count == 0)
        {
            _logger.LogWarning("No HLS resources found for chapter: {ChapterId}", chapter.Id);
            // Log available protocols
            foreach (var resource in nonDrmResources)
            {
                _logger.LogDebug(
                    "Non-HLS resource: Protocol={Protocol}, Streaming={Streaming}, URL={Url}",
                    resource.Protocol,
                    resource.Streaming,
                    resource.Url);
            }

            // Fallback to any available non-DRM resource
            var fallbackResource = nonDrmResources.FirstOrDefault();
            if (fallbackResource != null)
            {
                _logger.LogInformation(
                    "Using fallback resource for chapter {ChapterId}: {Url}",
                    chapter.Id,
                    fallbackResource.Url);
            }

            return fallbackResource?.Url;
        }

        // Select based on quality preference
        _logger.LogInformation(
            "Selecting stream with quality preference: {QualityPreference}",
            qualityPreference);

        Resource? selectedResource = qualityPreference switch
        {
            QualityPreference.HD => SelectHDResource(hlsResources) ?? SelectBestAvailableResource(hlsResources),
            QualityPreference.SD => SelectSDResource(hlsResources) ?? SelectBestAvailableResource(hlsResources),
            QualityPreference.Auto => hlsResources.FirstOrDefault(),
            _ => hlsResources.FirstOrDefault()
        };

        if (selectedResource != null)
        {
            _logger.LogDebug(
                "Selected stream for chapter {ChapterId}: Quality={Quality}, Protocol={Protocol}, URL={Url}",
                chapter.Id,
                selectedResource.Quality ?? "NULL",
                selectedResource.Protocol,
                selectedResource.Url);
            return selectedResource.Url;
        }

        _logger.LogWarning("Could not select appropriate stream for chapter: {ChapterId}", chapter.Id);
        return null;
    }

    /// <summary>
    /// Checks if a chapter has non-DRM playable content.
    /// </summary>
    /// <param name="chapter">The chapter to check.</param>
    /// <returns>True if playable content is available.</returns>
    public bool HasPlayableContent(Chapter chapter)
    {
        if (chapter?.ResourceList == null || chapter.ResourceList.Count == 0)
        {
            _logger.LogWarning(
                "Chapter {ChapterId}: ResourceList is null or empty. ResourceList count: {Count}, ResourceList type: {Type}",
                chapter?.Id ?? "null",
                chapter?.ResourceList?.Count ?? -1,
                chapter?.ResourceList?.GetType().Name ?? "null");
            return false;
        }

        _logger.LogInformation(
            "Chapter {ChapterId}: Found {ResourceCount} resources",
            chapter.Id,
            chapter.ResourceList.Count);

        foreach (var resource in chapter.ResourceList)
        {
            var urlPreview = resource.Url == null ? "null" : resource.Url.AsSpan(0, Math.Min(60, resource.Url.Length)).ToString() + "...";
            _logger.LogDebug(
                "Resource - URL: {Url}, Quality: {Quality}, DrmList: {DrmList}, DrmList type: {DrmListType}",
                urlPreview,
                resource.Quality,
                resource.DrmList?.ToString() ?? "null",
                resource.DrmList?.GetType().Name ?? "null");
        }

        var hasPlayable = chapter.ResourceList.Any(r => r.IsPlayable);
        _logger.LogInformation("Chapter {ChapterId}: Has playable content: {HasPlayable}", chapter.Id, hasPlayable);
        return hasPlayable;
    }

    /// <summary>
    /// Checks if content is expired based on ValidTo date.
    /// </summary>
    /// <param name="chapter">The chapter to check.</param>
    /// <returns>True if the content is expired.</returns>
    public bool IsContentExpired(Chapter chapter)
    {
        if (chapter?.ValidTo == null)
        {
            return false;
        }

        return DateTime.UtcNow > chapter.ValidTo.Value.ToUniversalTime();
    }

    private Resource? SelectHDResource(System.Collections.Generic.List<Resource> resources)
    {
        return resources.FirstOrDefault(r =>
            string.Equals(r.Quality, "HD", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(r.Quality, "1080", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(r.Quality, "720", StringComparison.OrdinalIgnoreCase));
    }

    private Resource? SelectSDResource(System.Collections.Generic.List<Resource> resources)
    {
        return resources.FirstOrDefault(r =>
            string.Equals(r.Quality, "SD", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(r.Quality, "480", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(r.Quality, "360", StringComparison.OrdinalIgnoreCase));
    }

    private Resource? SelectBestAvailableResource(System.Collections.Generic.List<Resource> resources)
    {
        // Try HD first
        var hdResource = SelectHDResource(resources);
        if (hdResource != null)
        {
            return hdResource;
        }

        // Fall back to SD
        var sdResource = SelectSDResource(resources);
        if (sdResource != null)
        {
            return sdResource;
        }

        // Return first available
        return resources.FirstOrDefault();
    }

    /// <summary>
    /// Authenticates a stream URL by fetching an Akamai token.
    /// Based on the Kodi addon implementation.
    /// </summary>
    /// <param name="streamUrl">The unauthenticated stream URL.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>The authenticated stream URL with token.</returns>
    public async Task<string> GetAuthenticatedStreamUrlAsync(string streamUrl, CancellationToken cancellationToken = default)
    {
        try
        {
            // Parse the stream URL to extract path components
            var uri = new Uri(streamUrl);
            var pathSegments = uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);

            if (pathSegments.Length < 2)
            {
                _logger.LogWarning("Stream URL has insufficient path segments for authentication: {Url}", streamUrl);
                return streamUrl;
            }

            // Build ACL path: /{segment1}/{segment2}/*
            var aclPath = $"/{pathSegments[0]}/{pathSegments[1]}/*";
            var tokenUrl = $"{ApiEndpoints.AkamaiTokenEndpoint}?acl={Uri.EscapeDataString(aclPath)}";

            _logger.LogDebug("Fetching auth token from: {TokenUrl}", tokenUrl);

            using var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
            var response = await httpClient.GetAsync(tokenUrl, cancellationToken).ConfigureAwait(false);
            response.EnsureSuccessStatusCode();

            var jsonContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
            var tokenResponse = JsonSerializer.Deserialize<JsonElement>(jsonContent);

            // Extract authparams from response
            if (tokenResponse.TryGetProperty("token", out var token) &&
                token.TryGetProperty("authparams", out var authParams))
            {
                var authParamsValue = authParams.GetString();
                if (!string.IsNullOrEmpty(authParamsValue))
                {
                    // Append auth params to original URL
                    var separator = streamUrl.Contains('?', StringComparison.Ordinal) ? "&" : "?";
                    var authenticatedUrl = $"{streamUrl}{separator}{authParamsValue}";

                    _logger.LogInformation("Successfully authenticated stream URL");
                    return authenticatedUrl;
                }
            }

            _logger.LogWarning("No auth params found in token response, returning original URL");
            return streamUrl;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to authenticate stream URL: {Url}", streamUrl);
            return streamUrl; // Return original URL as fallback
        }
    }
}
