"""Google OAuth flow for CLI — opens browser, catches redirect on localhost."""

import hashlib
import secrets
import socket
import urllib.parse
import webbrowser
from http.server import HTTPServer, BaseHTTPRequestHandler
from threading import Event

import httpx

from personal_assistant_mcp.config import GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET

GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"
SCOPES = "openid email profile"


def _find_free_port() -> int:
    """Find a free port."""
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(("0.0.0.0", 0))
        return s.getsockname()[1]


def _generate_pkce() -> tuple[str, str]:
    """Generate PKCE code_verifier and code_challenge."""
    code_verifier = secrets.token_urlsafe(64)
    digest = hashlib.sha256(code_verifier.encode()).digest()
    code_challenge = (
        secrets.token_urlsafe(0).__class__  # just to get base64url via the same module
        and digest.hex()  # fallback
    )
    # Proper base64url encoding
    import base64
    code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
    return code_verifier, code_challenge


class _OAuthCallbackHandler(BaseHTTPRequestHandler):
    """HTTP handler that captures the OAuth callback."""

    auth_code: str | None = None
    error: str | None = None
    done: Event

    def do_GET(self):
        query = urllib.parse.urlparse(self.path).query
        params = urllib.parse.parse_qs(query)

        if "code" in params:
            _OAuthCallbackHandler.auth_code = params["code"][0]
        elif "error" in params:
            _OAuthCallbackHandler.error = params["error"][0]
        else:
            _OAuthCallbackHandler.error = "No code in callback"

        self.send_response(200)
        self.send_header("Content-Type", "text/html")
        self.end_headers()
        self.wfile.write(
            b"<html><body><h2>Login successful!</h2>"
            b"<p>You can close this tab and return to the terminal.</p>"
            b"</body></html>"
        )
        _OAuthCallbackHandler.done.set()

    def log_message(self, format, *args):
        pass  # Suppress request logging


def google_oauth_flow() -> str:
    """Run the Google OAuth flow and return a Google ID token.

    Opens the user's browser for consent, catches the redirect on localhost,
    exchanges the auth code for tokens, and returns the id_token.
    """
    if not GOOGLE_CLIENT_ID:
        raise RuntimeError(
            "Google OAuth is not configured. GOOGLE_CLIENT_ID is missing."
        )

    port = _find_free_port()
    redirect_uri = f"http://localhost:{port}"
    code_verifier, code_challenge = _generate_pkce()

    # Build authorization URL
    auth_params = {
        "client_id": GOOGLE_CLIENT_ID,
        "redirect_uri": redirect_uri,
        "response_type": "code",
        "scope": SCOPES,
        "code_challenge": code_challenge,
        "code_challenge_method": "S256",
        "access_type": "offline",
        "prompt": "consent",
    }
    auth_url = f"{GOOGLE_AUTH_URL}?{urllib.parse.urlencode(auth_params)}"

    # Set up callback server
    _OAuthCallbackHandler.auth_code = None
    _OAuthCallbackHandler.error = None
    _OAuthCallbackHandler.done = Event()

    # Listen on 0.0.0.0 so the callback works in WSL (browser runs on Windows,
    # server runs on Linux — 127.0.0.1 may not be reachable from Windows side).
    server = HTTPServer(("0.0.0.0", port), _OAuthCallbackHandler)
    server.timeout = 5  # short timeout so we poll frequently

    # Open browser
    print("Opening browser for Google login...")
    print(f"If the browser doesn't open, visit:\n{auth_url}\n")
    webbrowser.open(auth_url)

    # Wait for callback (up to 2 minutes)
    import time
    deadline = time.time() + 120
    while not _OAuthCallbackHandler.done.is_set() and time.time() < deadline:
        server.handle_request()

    server.server_close()

    if not _OAuthCallbackHandler.done.is_set():
        raise RuntimeError("Timed out waiting for Google login callback.")

    if _OAuthCallbackHandler.error:
        raise RuntimeError(f"Google OAuth error: {_OAuthCallbackHandler.error}")

    auth_code = _OAuthCallbackHandler.auth_code
    if not auth_code:
        raise RuntimeError("No authorization code received.")

    # Exchange auth code for tokens
    token_data = {
        "code": auth_code,
        "client_id": GOOGLE_CLIENT_ID,
        "redirect_uri": redirect_uri,
        "grant_type": "authorization_code",
        "code_verifier": code_verifier,
    }
    if GOOGLE_CLIENT_SECRET:
        token_data["client_secret"] = GOOGLE_CLIENT_SECRET

    resp = httpx.post(GOOGLE_TOKEN_URL, data=token_data)
    resp.raise_for_status()
    tokens = resp.json()

    # Use access token with "access:" prefix — the backend validates it via
    # Google's userinfo endpoint, which works regardless of client ID type
    # (Desktop apps have a different audience than web/mobile client IDs).
    access_token = tokens.get("access_token")
    if not access_token:
        raise RuntimeError("Google OAuth did not return an access token.")
    return f"access:{access_token}"
