using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using ytLive.Models;

namespace ytLive.Services;

/// <summary>
/// Handles YouTube OAuth2 authentication flow.
/// Uses a local HTTP listener for the redirect callback.
/// </summary>
public class YouTubeAuthService
{
    private const string AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth";
    private const string TokenEndpoint = "https://oauth2.googleapis.com/token";
    private const string Scope = "https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.force-ssl";

    private readonly string _clientId;
    private readonly string _clientSecret;
    private readonly HttpClient _http;
    private readonly Action<YouTubeChannel>? _sessionChanged;

    public YouTubeChannel? CurrentChannel { get; private set; }

    public YouTubeAuthService(string clientId, string clientSecret, HttpClient? http = null,
        Action<YouTubeChannel>? sessionChanged = null)
    {
        _clientId = clientId;
        _clientSecret = clientSecret;
        _http = http ?? new HttpClient();
        _sessionChanged = sessionChanged;
    }

    /// <summary>Replaces the current session (used when a saved token loads at startup).</summary>
    public void SetSession(YouTubeChannel channel)
    {
        CurrentChannel = channel;
    }

    /// <summary>Signs out: drops the in-memory session (a graceful End Livestream clears the saved token via the caller).</summary>
    public void ClearSession()
    {
        CurrentChannel = null;
    }

    public string GetAuthorizationUrl(string redirectUri)
    {
        var query = new[]
        {
            ("client_id", _clientId),
            ("redirect_uri", redirectUri),
            ("response_type", "code"),
            ("scope", Scope),
            ("access_type", "offline"),
            ("prompt", "consent"),
        };

        var encoded = string.Join("&", query.Select(kv =>
            $"{Uri.EscapeDataString(kv.Item1)}={Uri.EscapeDataString(kv.Item2)}"));

        return $"{AuthorizationEndpoint}?{encoded}";
    }

    public async Task<YouTubeChannel?> AuthenticateAsync()
    {
        const int port = 8765;
        var redirectUri = $"http://localhost:{port}/oauth2/callback";

        if (string.IsNullOrWhiteSpace(_clientId) || string.IsNullOrWhiteSpace(_clientSecret))
            return null;

        using var listener = new HttpListener();
        listener.Prefixes.Add($"{redirectUri}/");
        listener.Start();

        Process.Start(new ProcessStartInfo(GetAuthorizationUrl(redirectUri)) { UseShellExecute = true });

        HttpListenerContext context;
        try
        {
            using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
            context = await listener.GetContextAsync().WaitAsync(cts.Token);
        }
        catch (OperationCanceledException)
        {
            return null;
        }

        var code = context.Request.QueryString["code"];
        var error = context.Request.QueryString["error"];

        var html = error != null
            ? "<html><body style=\"font-family:sans-serif\"><h2>Sign-in failed</h2><p>You can close this window and return to ytLlive.</p></body></html>"
            : "<html><body style=\"font-family:sans-serif\"><h2>Sign-in successful!</h2><p>You can close this window and return to ytLlive.</p></body></html>";
        var buffer = Encoding.UTF8.GetBytes(html);
        context.Response.ContentType = "text/html; charset=utf-8";
        context.Response.ContentLength64 = buffer.Length;
        await context.Response.OutputStream.WriteAsync(buffer);
        context.Response.Close();

        if (error != null || string.IsNullOrEmpty(code))
            return null;

        return await ExchangeCodeForToken(code, redirectUri);
    }

    public async Task<YouTubeChannel?> ExchangeCodeForToken(string code, string redirectUri)
    {
        var body = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["code"] = code,
            ["client_id"] = _clientId,
            ["client_secret"] = _clientSecret,
            ["redirect_uri"] = redirectUri,
            ["grant_type"] = "authorization_code"
        });

        var response = await _http.PostAsync(TokenEndpoint, body);
        if (!response.IsSuccessStatusCode)
            return null;

        var json = await response.Content.ReadAsStringAsync();
        var tokenData = JsonSerializer.Deserialize<JsonElement>(json);

        var accessToken = tokenData.GetProperty("access_token").GetString()!;
        var refreshToken = tokenData.GetProperty("refresh_token").GetString()!;
        var expiresIn = tokenData.GetProperty("expires_in").GetInt32();

        // Fetch channel info
        return await FetchChannelInfo(accessToken, refreshToken, expiresIn);
    }

    public async Task<bool> RefreshToken()
    {
        if (CurrentChannel == null) return false;

        var body = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["refresh_token"] = CurrentChannel.RefreshToken,
            ["client_id"] = _clientId,
            ["client_secret"] = _clientSecret,
            ["grant_type"] = "refresh_token"
        });

        var response = await _http.PostAsync(TokenEndpoint, body);
        if (!response.IsSuccessStatusCode) return false;

        var json = await response.Content.ReadAsStringAsync();
        var tokenData = JsonSerializer.Deserialize<JsonElement>(json);

        CurrentChannel.AccessToken = tokenData.GetProperty("access_token").GetString()!;
        CurrentChannel.TokenExpiry = DateTime.UtcNow.AddSeconds(tokenData.GetProperty("expires_in").GetInt32());
        _sessionChanged?.Invoke(CurrentChannel);
        return true;
    }

    private async Task<YouTubeChannel?> FetchChannelInfo(string accessToken, string refreshToken, int expiresIn)
    {
        _http.DefaultRequestHeaders.Authorization = new("Bearer", accessToken);
        var response = await _http.GetAsync("https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true");
        if (!response.IsSuccessStatusCode) return null;

        var json = await response.Content.ReadAsStringAsync();
        var data = JsonSerializer.Deserialize<JsonElement>(json);
        var items = data.GetProperty("items");

        if (items.GetArrayLength() == 0) return null;

        var snippet = items[0].GetProperty("snippet");
        CurrentChannel = new YouTubeChannel
        {
            ChannelId = items[0].GetProperty("id").GetString()!,
            DisplayName = snippet.GetProperty("title").GetString()!,
            ProfileImageUrl = snippet.GetProperty("thumbnails").GetProperty("default").GetProperty("url").GetString()!,
            AccessToken = accessToken,
            RefreshToken = refreshToken,
            TokenExpiry = DateTime.UtcNow.AddSeconds(expiresIn)
        };

        _sessionChanged?.Invoke(CurrentChannel);
        return CurrentChannel;
    }
}
