using System.Security.Cryptography;
using System.Text;
using System.Xml.Linq;
using Application.Abstractions.YouTube;
using Microsoft.Extensions.Logging;

namespace Infrastructure.YouTube;

/// <summary>
/// YouTube PubSubHubbub(WebSub) 구독 관리
/// - 구독 신청/해지: POST https://pubsubhubbub.appspot.com/subscribe
/// - 콜백 검증: hub.challenge GET 요청 응답
/// - Atom Feed 파싱: 새 영상/생방송 알림 수신
/// </summary>
internal sealed class YouTubePubSubService(
    IHttpClientFactory httpClientFactory,
    ILogger<YouTubePubSubService> logger
) : IYouTubePubSubService
{
    private const string HubUrl = "https://pubsubhubbub.appspot.com/subscribe";
    private const string TopicBase = "https://www.youtube.com/feeds/videos.xml?channel_id=";

    // HMAC 서명 검증용 시크릿 — 실제 운영에서는 Config/환경변수에서 주입
    internal string HmacSecret { get; set; } = "dpot-youtube-pubsub-secret";

    // 콜백 URL — 서버 시작 시 설정
    internal string CallbackUrl { get; set; } = string.Empty;

    public async Task<bool> SubscribeAsync(string channelId, CancellationToken ct)
    {
        return await SendHubRequestAsync("subscribe", channelId, ct);
    }

    public async Task<bool> UnsubscribeAsync(string channelId, CancellationToken ct)
    {
        return await SendHubRequestAsync("unsubscribe", channelId, ct);
    }

    public YouTubePubSubNotification? ParseNotification(string atomXml)
    {
        try
        {
            var doc = XDocument.Parse(atomXml);
            XNamespace atom = "http://www.w3.org/2005/Atom";
            XNamespace yt = "http://www.youtube.com/xml/schemas/2015";

            var entry = doc.Descendants(atom + "entry").FirstOrDefault();
            if (entry is null)
            {
                return null;
            }

            var videoId = entry.Element(yt + "videoId")?.Value;
            var channelId = entry.Element(yt + "channelId")?.Value;
            var title = entry.Element(atom + "title")?.Value ?? "";

            var published = entry.Element(atom + "published")?.Value;
            var updated = entry.Element(atom + "updated")?.Value;

            if (string.IsNullOrEmpty(videoId) || string.IsNullOrEmpty(channelId))
            {
                logger.LogWarning("[PubSub] Atom feed missing videoId or channelId");
                return null;
            }

            return new YouTubePubSubNotification(
                videoId,
                channelId,
                title,
                DateTime.TryParse(published, out var pub) ? pub : DateTime.UtcNow,
                DateTime.TryParse(updated, out var upd) ? upd : DateTime.UtcNow
            );
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "[PubSub] Failed to parse Atom feed");
            return null;
        }
    }

    public bool VerifySignature(string payload, string signature)
    {
        if (string.IsNullOrEmpty(signature))
        {
            return false;
        }

        // X-Hub-Signature 형식: "sha1=hex_digest" 또는 "sha256=hex_digest"
        var parts = signature.Split('=', 2);
        if (parts.Length != 2)
        {
            return false;
        }

        var algorithm = parts[0];
        var expectedHex = parts[1];

        byte[] hash;
        var keyBytes = Encoding.UTF8.GetBytes(HmacSecret);
        var payloadBytes = Encoding.UTF8.GetBytes(payload);

        if (algorithm is "sha1")
        {
            hash = HMACSHA1.HashData(keyBytes, payloadBytes);
        }
        else if (algorithm is "sha256")
        {
            hash = HMACSHA256.HashData(keyBytes, payloadBytes);
        }
        else
        {
            logger.LogWarning("[PubSub] Unsupported HMAC algorithm: {Algorithm}", algorithm);
            return false;
        }

        var computedHex = Convert.ToHexString(hash).ToLowerInvariant();
        return string.Equals(computedHex, expectedHex, StringComparison.OrdinalIgnoreCase);
    }

    // ── Private ──────────────────────────────────────────────────────

    private async Task<bool> SendHubRequestAsync(string mode, string channelId, CancellationToken ct)
    {
        try
        {
            var client = httpClientFactory.CreateClient("PubSubHub");
            var topicUrl = $"{TopicBase}{channelId}";

            var content = new FormUrlEncodedContent(new Dictionary<string, string>
            {
                ["hub.mode"] = mode,
                ["hub.topic"] = topicUrl,
                ["hub.callback"] = CallbackUrl,
                ["hub.secret"] = HmacSecret,
                ["hub.verify"] = "async"
            });

            var response = await client.PostAsync(HubUrl, content, ct);

            if (response.IsSuccessStatusCode || (int)response.StatusCode == 202)
            {
                logger.LogInformation("[PubSub] {Mode} request accepted for channelId={ChannelId}", mode, channelId);
                return true;
            }

            var body = await response.Content.ReadAsStringAsync(ct);
            logger.LogWarning("[PubSub] {Mode} failed: {StatusCode} {Body}", mode, response.StatusCode, body);
            return false;
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "[PubSub] {Mode} error for channelId={ChannelId}", mode, channelId);
            return false;
        }
    }
}