using System;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Plugin.SRFPlay.Api.Models;
using Jellyfin.Plugin.SRFPlay.Api.Models.PlayV3;
using Jellyfin.Plugin.SRFPlay.Configuration;
using Jellyfin.Plugin.SRFPlay.Constants;
using Microsoft.Extensions.Logging;

namespace Jellyfin.Plugin.SRFPlay.Api;

/// <summary>
/// HTTP client for interacting with the SRF Integration Layer API.
/// </summary>
public class SRFApiClient : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly HttpClient _playV3HttpClient;
    private readonly ILogger _logger;
    private readonly JsonSerializerOptions _jsonOptions;
    private readonly PluginConfiguration _configuration;

    /// <summary>
    /// Initializes a new instance of the <see cref="SRFApiClient"/> class.
    /// </summary>
    /// <param name="loggerFactory">The logger factory.</param>
    public SRFApiClient(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<SRFApiClient>();
        _configuration = Plugin.Instance?.Configuration ?? new PluginConfiguration();

        // Log proxy configuration status
        if (_configuration.UseProxy && !string.IsNullOrWhiteSpace(_configuration.ProxyAddress))
        {
            _logger.LogInformation(
                "SRFApiClient initializing with proxy enabled: {ProxyAddress}",
                _configuration.ProxyAddress);
        }
        else
        {
            _logger.LogInformation("SRFApiClient initializing without proxy");
        }

        _httpClient = CreateHttpClient(ApiEndpoints.IntegrationLayerBaseUrl);
        _playV3HttpClient = CreateHttpClient(null);

        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true,
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        };
    }

    /// <summary>
    /// Builds the Play v3 API base URL for a business unit. The host is normally
    /// www.&lt;unit&gt;.ch, but SWI's site lives at www.swissinfo.ch (www.swi.ch is an
    /// unrelated welding institute), while the API path segment stays the unit code.
    /// </summary>
    /// <param name="businessUnit">The lowercase business unit (e.g. "srf", "swi").</param>
    /// <returns>The Play v3 production base URL ending in a slash.</returns>
    private static string BuildPlayV3BaseUrl(string businessUnit)
    {
        var host = string.Equals(businessUnit, "swi", StringComparison.OrdinalIgnoreCase)
            ? "www.swissinfo.ch"
            : $"www.{businessUnit}.ch";
        return $"https://{host}/play/v3/api/{businessUnit}/production/";
    }

    /// <summary>
    /// Reads HTTP response content as UTF-8 string.
    /// </summary>
    /// <param name="content">The HTTP content to read.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>The content as a UTF-8 decoded string.</returns>
    private async Task<string> ReadAsUtf8StringAsync(HttpContent content, CancellationToken cancellationToken = default)
    {
        var bytes = await content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
        return Encoding.UTF8.GetString(bytes);
    }

    /// <summary>
    /// Creates an HttpClient with optional proxy configuration.
    /// </summary>
    /// <param name="baseAddress">The base address for the HTTP client.</param>
    /// <returns>Configured HttpClient.</returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA5399:HttpClient is created without enabling CheckCertificateRevocationList", Justification = "CRL check disabled for proxy compatibility")]
    private HttpClient CreateHttpClient(string? baseAddress)
    {
        HttpClientHandler handler = new HttpClientHandler
        {
            CheckCertificateRevocationList = false, // Disable CRL check for proxy compatibility
            AutomaticDecompression = System.Net.DecompressionMethods.All
        };

        // Configure proxy if enabled
        if (_configuration.UseProxy && !string.IsNullOrWhiteSpace(_configuration.ProxyAddress))
        {
            try
            {
                var proxy = new WebProxy(_configuration.ProxyAddress);

                // Add credentials if provided
                if (!string.IsNullOrWhiteSpace(_configuration.ProxyUsername))
                {
                    proxy.Credentials = new NetworkCredential(
                        _configuration.ProxyUsername,
                        _configuration.ProxyPassword);
                }

                handler.Proxy = proxy;
                handler.UseProxy = true;

                _logger.LogInformation(
                    "Proxy configured: {ProxyAddress} (Authentication: {HasAuth})",
                    _configuration.ProxyAddress,
                    !string.IsNullOrWhiteSpace(_configuration.ProxyUsername));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to configure proxy: {ProxyAddress}", _configuration.ProxyAddress);
                handler.UseProxy = false;
            }
        }

        var client = new HttpClient(handler)
        {
            Timeout = TimeSpan.FromSeconds(30),
            DefaultRequestVersion = HttpVersion.Version11, // Force HTTP/1.1
            DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact // Ensure HTTP/1.1 is used, not upgraded
        };

        // Add browser headers - required by SRF API
        client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
        client.DefaultRequestHeaders.Accept.ParseAdd("*/*");
        client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.9");
        client.DefaultRequestHeaders.Add("Accept-Charset", "utf-8");

        _logger.LogInformation(
            "HttpClient created with HTTP/1.1 and headers - User-Agent: {UserAgent}, Accept: {Accept}, Accept-Language: {AcceptLanguage}",
            client.DefaultRequestHeaders.UserAgent.ToString(),
            client.DefaultRequestHeaders.Accept.ToString(),
            client.DefaultRequestHeaders.AcceptLanguage.ToString());

        if (!string.IsNullOrWhiteSpace(baseAddress))
        {
            client.BaseAddress = new Uri(baseAddress);
        }

        return client;
    }

    /// <summary>
    /// Gets media composition by URN using curl as a fallback.
    /// </summary>
    /// <param name="urn">The URN of the content.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>The media composition.</returns>
    public async Task<MediaComposition?> GetMediaCompositionByUrnAsync(string urn, CancellationToken cancellationToken = default)
    {
        try
        {
            var fullUrl = $"{ApiEndpoints.IntegrationLayerBaseUrl}/mediaComposition/byUrn/{urn}.json";
            _logger.LogDebug("Fetching media composition for URN: {Urn} from {Url}", urn, fullUrl);

            // Use curl - HttpClient returns 404 due to server routing/network configuration
            return await FetchWithCurlAsync(fullUrl, cancellationToken).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error fetching media composition for URN: {Urn}", urn);
            return null;
        }
    }

    /// <summary>
    /// Fetches content using curl command as a fallback.
    /// </summary>
    /// <param name="url">The full URL to fetch.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>The deserialized media composition.</returns>
    private async Task<MediaComposition?> FetchWithCurlAsync(string url, CancellationToken cancellationToken)
    {
        try
        {
            _logger.LogInformation("Using curl to fetch: {Url}", url);

            var curlArgs = $"-s -H \"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\"";

            // Add proxy if configured
            if (_configuration.UseProxy && !string.IsNullOrWhiteSpace(_configuration.ProxyAddress))
            {
                curlArgs += $" -x {_configuration.ProxyAddress}";

                if (!string.IsNullOrWhiteSpace(_configuration.ProxyUsername))
                {
                    curlArgs += $" -U {_configuration.ProxyUsername}:{_configuration.ProxyPassword}";
                }

                _logger.LogInformation("curl using proxy: {ProxyAddress}", _configuration.ProxyAddress);
            }

            curlArgs += $" \"{url}\"";

            var processStartInfo = new System.Diagnostics.ProcessStartInfo
            {
                FileName = "curl",
                Arguments = curlArgs,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true,
                StandardOutputEncoding = Encoding.UTF8
            };

            using var process = new System.Diagnostics.Process { StartInfo = processStartInfo };
            process.Start();

            var output = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
            var error = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);

            await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);

            if (process.ExitCode != 0)
            {
                _logger.LogError("curl failed with exit code {ExitCode}: {Error}", process.ExitCode, error);
                return null;
            }

            if (string.IsNullOrWhiteSpace(output))
            {
                _logger.LogWarning("curl returned empty response");
                return null;
            }

            _logger.LogInformation("curl succeeded, response length: {Length}", output.Length);

            var result = JsonSerializer.Deserialize<MediaComposition>(output, _jsonOptions);

            if (result?.HasChapters == true)
            {
                _logger.LogInformation("Successfully fetched media composition via curl - Chapters: {ChapterCount}", result.ChapterList.Count);
            }

            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error using curl to fetch URL: {Url}", url);
            return null;
        }
    }

    /// <summary>
    /// Gets the latest videos for a business unit.
    /// </summary>
    /// <param name="businessUnit">The business unit (e.g., srf, rts).</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>The media composition containing latest videos.</returns>
    public Task<MediaComposition?> GetLatestVideosAsync(string businessUnit, CancellationToken cancellationToken = default)
        => GetMediaCompositionListAsync(businessUnit, "latest", cancellationToken);

    /// <summary>
    /// Gets the trending videos for a business unit.
    /// </summary>
    /// <param name="businessUnit">The business unit (e.g., srf, rts).</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>The media composition containing trending videos.</returns>
    public Task<MediaComposition?> GetTrendingVideosAsync(string businessUnit, CancellationToken cancellationToken = default)
        => GetMediaCompositionListAsync(businessUnit, "trending", cancellationToken);

    private async Task<MediaComposition?> GetMediaCompositionListAsync(string businessUnit, string endpoint, CancellationToken cancellationToken)
    {
        try
        {
            var url = $"/video/{businessUnit}/{endpoint}.json";
            _logger.LogInformation("Fetching {Endpoint} videos for business unit: {BusinessUnit} from URL: {Url}", endpoint, businessUnit, url);

            var response = await _httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);

            _logger.LogInformation("{Endpoint} videos API response: {StatusCode}", endpoint, response.StatusCode);

            if (!response.IsSuccessStatusCode)
            {
                var errorContent = await ReadAsUtf8StringAsync(response.Content, cancellationToken).ConfigureAwait(false);
                _logger.LogError("API returned error {StatusCode}: {Error}", response.StatusCode, errorContent);
                return null;
            }

            var content = await ReadAsUtf8StringAsync(response.Content, cancellationToken).ConfigureAwait(false);
            _logger.LogDebug("{Endpoint} videos response length: {Length}", endpoint, content.Length);

            var result = JsonSerializer.Deserialize<MediaComposition>(content, _jsonOptions);

            _logger.LogInformation("Successfully fetched {Endpoint} videos for business unit: {BusinessUnit}", endpoint, businessUnit);
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error fetching {Endpoint} videos for business unit: {BusinessUnit}", endpoint, businessUnit);
            return null;
        }
    }

    /// <summary>
    /// Gets all shows from the Play v3 API.
    /// </summary>
    /// <param name="businessUnit">The business unit (e.g., srf, rts).</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>List of shows.</returns>
    public Task<System.Collections.Generic.List<PlayV3Show>?> GetAllShowsAsync(string businessUnit, CancellationToken cancellationToken = default)
        => GetPlayV3DirectListAsync<PlayV3Show>(businessUnit, "shows", cancellationToken);

    /// <summary>
    /// Gets all topics from the Play v3 API.
    /// </summary>
    /// <param name="businessUnit">The business unit (e.g., srf, rts).</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>List of topics.</returns>
    public Task<System.Collections.Generic.List<PlayV3Topic>?> GetAllTopicsAsync(string businessUnit, CancellationToken cancellationToken = default)
        => GetPlayV3DirectListAsync<PlayV3Topic>(businessUnit, "topics", cancellationToken);

    private async Task<System.Collections.Generic.List<T>?> GetPlayV3DirectListAsync<T>(string businessUnit, string endpoint, CancellationToken cancellationToken)
    {
        try
        {
            var baseUrl = BuildPlayV3BaseUrl(businessUnit);
            var url = $"{baseUrl}{endpoint}";
            _logger.LogInformation("Fetching all {Endpoint} for business unit: {BusinessUnit} from URL: {Url}", endpoint, businessUnit, url);

            var response = await _playV3HttpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var errorContent = await ReadAsUtf8StringAsync(response.Content, cancellationToken).ConfigureAwait(false);
                _logger.LogError("API returned error {StatusCode}: {Error}", response.StatusCode, errorContent);
                return null;
            }

            var content = await ReadAsUtf8StringAsync(response.Content, cancellationToken).ConfigureAwait(false);
            var result = JsonSerializer.Deserialize<PlayV3DirectResponse<T>>(content, _jsonOptions);

            _logger.LogInformation("Successfully fetched {Count} {Endpoint} for business unit: {BusinessUnit}", result?.Data?.Count ?? 0, endpoint, businessUnit);
            return result?.Data;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error fetching {Endpoint} for business unit: {BusinessUnit}", endpoint, businessUnit);
            return null;
        }
    }

    /// <summary>
    /// Gets videos for a specific show from the Play v3 API.
    /// </summary>
    /// <param name="businessUnit">The business unit (e.g., srf, rts).</param>
    /// <param name="showId">The show ID.</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>List of videos.</returns>
    public async Task<System.Collections.Generic.List<PlayV3Video>?> GetVideosForShowAsync(string businessUnit, string showId, CancellationToken cancellationToken = default)
    {
        try
        {
            var baseUrl = BuildPlayV3BaseUrl(businessUnit);
            var url = $"{baseUrl}videos-by-show-id?showId={showId}";
            _logger.LogDebug("Fetching videos for show {ShowId} from business unit: {BusinessUnit}", showId, businessUnit);

            var response = await _playV3HttpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var errorContent = await ReadAsUtf8StringAsync(response.Content, cancellationToken).ConfigureAwait(false);
                _logger.LogError("API returned error {StatusCode}: {Error}", response.StatusCode, errorContent);
                return null;
            }

            var content = await ReadAsUtf8StringAsync(response.Content, cancellationToken).ConfigureAwait(false);
            var result = JsonSerializer.Deserialize<PlayV3Response<PlayV3Video>>(content, _jsonOptions);

            _logger.LogDebug("Successfully fetched {Count} videos for show {ShowId}", result?.Data?.Data?.Count ?? 0, showId);
            return result?.Data?.Data;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error fetching videos for show {ShowId} from business unit: {BusinessUnit}", showId, businessUnit);
            return null;
        }
    }

    /// <summary>
    /// Gets scheduled livestreams for sports or news events from the Play v3 API.
    /// </summary>
    /// <param name="businessUnit">The business unit (e.g., srf, rts).</param>
    /// <param name="eventType">The event type (SPORT or NEWS).</param>
    /// <param name="cancellationToken">The cancellation token.</param>
    /// <returns>List of scheduled livestream entries.</returns>
    public async Task<System.Collections.Generic.IReadOnlyList<PlayV3TvProgram>?> GetScheduledLivestreamsAsync(
        string businessUnit,
        string eventType = "SPORT",
        CancellationToken cancellationToken = default)
    {
        try
        {
            var baseUrl = BuildPlayV3BaseUrl(businessUnit);
            var url = $"{baseUrl}livestreams?eventType={eventType.ToUpperInvariant()}";
            _logger.LogInformation("Fetching scheduled livestreams for eventType={EventType} from business unit: {BusinessUnit}", eventType, businessUnit);

            var response = await _playV3HttpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);

            if (!response.IsSuccessStatusCode)
            {
                var errorContent = await ReadAsUtf8StringAsync(response.Content, cancellationToken).ConfigureAwait(false);
                _logger.LogError("API returned error {StatusCode}: {Error}", response.StatusCode, errorContent);
                return null;
            }

            var content = await ReadAsUtf8StringAsync(response.Content, cancellationToken).ConfigureAwait(false);

            // The response structure is: { "data": { "scheduledLivestreams": [...] } }
            var jsonDoc = JsonSerializer.Deserialize<JsonElement>(content, _jsonOptions);
            if (jsonDoc.TryGetProperty("data", out var dataElement) &&
                dataElement.TryGetProperty("scheduledLivestreams", out var livestreamsElement))
            {
                var livestreams = JsonSerializer.Deserialize<System.Collections.Generic.List<PlayV3TvProgram>>(
                    livestreamsElement.GetRawText(),
                    _jsonOptions);

                _logger.LogInformation("Successfully fetched {Count} scheduled livestreams for eventType={EventType}", livestreams?.Count ?? 0, eventType);
                return livestreams;
            }

            _logger.LogWarning("No scheduledLivestreams found in response");
            return null;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error fetching scheduled livestreams for eventType={EventType} from business unit: {BusinessUnit}", eventType, businessUnit);
            return null;
        }
    }

    /// <inheritdoc/>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Releases the unmanaged resources and optionally releases the managed resources.
    /// </summary>
    /// <param name="disposing">True to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            _httpClient?.Dispose();
            _playV3HttpClient?.Dispose();
        }
    }
}
