using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.SRFPlay.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace Jellyfin.Plugin.SRFPlay.Controllers;

/// <summary>
/// Controller for proxying SRF Play streams.
/// </summary>
[ApiController]
[Route("Plugins/SRFPlay/Proxy")]
public class StreamProxyController : ControllerBase
{
    private readonly ILogger<StreamProxyController> _logger;
    private readonly StreamProxyService _proxyService;

    /// <summary>
    /// Initializes a new instance of the <see cref="StreamProxyController"/> class.
    /// </summary>
    /// <param name="logger">The logger.</param>
    /// <param name="proxyService">The proxy service.</param>
    public StreamProxyController(ILogger<StreamProxyController> logger, StreamProxyService proxyService)
    {
        _logger = logger;
        _proxyService = proxyService;
    }

    /// <summary>
    /// Proxies HLS master manifest requests.
    /// </summary>
    /// <param name="itemId">The item ID from URL path.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>The HLS manifest with rewritten URLs.</returns>
    [HttpGet("{itemId}/master.m3u8")]
    [AllowAnonymous] // Allow anonymous since Jellyfin handles auth upstream
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetMasterManifest(
        [FromRoute] string itemId,
        CancellationToken cancellationToken)
    {
        _logger.LogInformation("Proxy request for master manifest - ItemId: {ItemId}", itemId);

        // Try to resolve the actual item ID (path ID might be a session ID during transcoding)
        var actualItemId = ResolveItemId(itemId);

        try
        {
            // Build the base proxy URL for this item (use original itemId from path to maintain URL structure)
            // Always include the actualItemId as a query parameter to ensure proper resolution during transcoding
            var baseProxyUrl = $"{Request.Scheme}://{Request.Host}/Plugins/SRFPlay/Proxy/{itemId}?itemId={actualItemId}";

            if (actualItemId != itemId)
            {
                _logger.LogDebug("Path itemId {PathId} differs from resolved itemId {ResolvedId}, adding query parameter", itemId, actualItemId);
            }

            var manifestContent = await _proxyService.GetRewrittenManifestAsync(actualItemId, baseProxyUrl, cancellationToken).ConfigureAwait(false);

            if (manifestContent == null)
            {
                _logger.LogWarning("Manifest not found for item {ItemId}", itemId);
                return NotFound();
            }

            _logger.LogDebug("Returning master manifest for item {ItemId} ({Length} bytes)", itemId, manifestContent.Length);
            return Content(manifestContent, "application/vnd.apple.mpegurl");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error proxying master manifest for item {ItemId}", itemId);
            return StatusCode(StatusCodes.Status500InternalServerError);
        }
    }

    /// <summary>
    /// Proxies HLS variant manifest requests.
    /// </summary>
    /// <param name="itemId">The item ID.</param>
    /// <param name="manifestPath">The manifest path (e.g., "index_0_av").</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>The HLS manifest with rewritten segment URLs.</returns>
    [HttpGet("{itemId}/{manifestPath}.m3u8")]
    [AllowAnonymous]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetVariantManifest(
        [FromRoute] string itemId,
        [FromRoute] string manifestPath,
        CancellationToken cancellationToken)
    {
        var fullPath = $"{manifestPath}.m3u8";
        _logger.LogInformation("Proxy request for variant manifest - ItemId: {ItemId}, Path: {Path}", itemId, fullPath);

        // Try to resolve the actual item ID
        var actualItemId = ResolveItemId(itemId);

        try
        {
            // Fetch the variant manifest as a segment
            var manifestData = await _proxyService.GetSegmentAsync(actualItemId, fullPath, cancellationToken).ConfigureAwait(false);

            if (manifestData == null)
            {
                _logger.LogWarning("Variant manifest not found - ItemId: {ItemId}, Path: {Path}", itemId, fullPath);
                return NotFound();
            }

            // Convert to string and rewrite URLs
            var manifestContent = System.Text.Encoding.UTF8.GetString(manifestData);
            var baseProxyUrl = $"{Request.Scheme}://{Request.Host}/Plugins/SRFPlay/Proxy/{itemId}";
            var rewrittenContent = RewriteSegmentUrls(manifestContent, baseProxyUrl);

            _logger.LogDebug("Returning variant manifest for item {ItemId} ({Length} bytes)", itemId, rewrittenContent.Length);
            return Content(rewrittenContent, "application/vnd.apple.mpegurl");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error proxying variant manifest - ItemId: {ItemId}, Path: {Path}", itemId, fullPath);
            return StatusCode(StatusCodes.Status500InternalServerError);
        }
    }

    /// <summary>
    /// Proxies HLS segment requests (.ts, .mp4, .m4s, .aac files).
    /// </summary>
    /// <param name="itemId">The item ID.</param>
    /// <param name="segmentPath">The segment path.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>The segment data.</returns>
    [HttpGet("{itemId}/{*segmentPath}")]
    [AllowAnonymous]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetSegment(
        [FromRoute] string itemId,
        [FromRoute] string segmentPath,
        CancellationToken cancellationToken)
    {
        _logger.LogDebug("Proxy request for segment - ItemId: {ItemId}, Path: {SegmentPath}", itemId, segmentPath);

        // Try to resolve the actual item ID
        var actualItemId = ResolveItemId(itemId);

        try
        {
            var segmentData = await _proxyService.GetSegmentAsync(actualItemId, segmentPath, cancellationToken).ConfigureAwait(false);

            if (segmentData == null)
            {
                _logger.LogWarning("Segment not found - ItemId: {ItemId}, Path: {SegmentPath}", itemId, segmentPath);
                return NotFound();
            }

            // Determine content type based on file extension
            var contentType = GetContentType(segmentPath);

            _logger.LogDebug("Returning segment {SegmentPath} ({Length} bytes, {ContentType})", segmentPath, segmentData.Length, contentType);
            return File(segmentData, contentType);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error proxying segment - ItemId: {ItemId}, Path: {SegmentPath}", itemId, segmentPath);
            return StatusCode(StatusCodes.Status500InternalServerError);
        }
    }

    /// <summary>
    /// Resolves the actual item ID from the request.
    /// </summary>
    /// <param name="pathItemId">The item ID from the URL path.</param>
    /// <returns>The resolved item ID.</returns>
    private string ResolveItemId(string pathItemId)
    {
        // Check if there's an itemId query parameter (fallback for transcoding sessions)
        if (Request.Query.TryGetValue("itemId", out var queryItemId) && !string.IsNullOrEmpty(queryItemId))
        {
            _logger.LogDebug("Using itemId from query parameter: {QueryItemId} (path had: {PathItemId})", queryItemId.ToString(), pathItemId);
            return queryItemId.ToString();
        }

        // If path ID and query ID don't match, it's likely a transcoding session
        // Try to use the proxy service fallback to find the correct stream
        _logger.LogDebug("No itemId query parameter found, using path ID as-is: {PathItemId}", pathItemId);
        return pathItemId;
    }

    /// <summary>
    /// Rewrites segment URLs in a manifest to point to proxy.
    /// </summary>
    /// <param name="manifestContent">The manifest content.</param>
    /// <param name="baseProxyUrl">The base proxy URL.</param>
    /// <returns>The rewritten manifest.</returns>
    private string RewriteSegmentUrls(string manifestContent, string baseProxyUrl)
    {
        // Extract the itemId query parameter from the current request to propagate it
        var itemIdParam = Request.Query.TryGetValue("itemId", out var itemId) && !string.IsNullOrEmpty(itemId)
            ? $"?itemId={itemId}"
            : string.Empty;

        var lines = manifestContent.Split('\n');
        var result = new System.Text.StringBuilder();

        foreach (var line in lines)
        {
            if (line.StartsWith('#') || string.IsNullOrWhiteSpace(line))
            {
                // Keep metadata and blank lines as-is
                result.AppendLine(line);
            }
            else if (line.Contains("://", StringComparison.Ordinal))
            {
                // Absolute URL - extract the path and rewrite
                var uri = new Uri(line.Trim());
                var segments = uri.AbsolutePath.Split('/');
                var fileName = segments[^1];
                result.AppendLine(CultureInfo.InvariantCulture, $"{baseProxyUrl}/{fileName}{itemIdParam}");
            }
            else
            {
                // Relative URL - rewrite to proxy
                result.AppendLine(CultureInfo.InvariantCulture, $"{baseProxyUrl}/{line.Trim()}{itemIdParam}");
            }
        }

        return result.ToString();
    }

    /// <summary>
    /// Gets the content type for a segment based on file extension.
    /// </summary>
    /// <param name="path">The segment path.</param>
    /// <returns>The MIME content type.</returns>
    private static string GetContentType(string path)
    {
        if (path.EndsWith(".ts", StringComparison.OrdinalIgnoreCase))
        {
            return "video/MP2T";
        }

        if (path.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase) ||
            path.EndsWith(".m4s", StringComparison.OrdinalIgnoreCase))
        {
            return "video/mp4";
        }

        if (path.EndsWith(".aac", StringComparison.OrdinalIgnoreCase))
        {
            return "audio/aac";
        }

        return "application/octet-stream";
    }
}
