"""Google OAuth authentication via Supabase."""

from __future__ import annotations

import http.server
import json
import threading
import urllib.parse
import webbrowser
from typing import Any

from .config import AUTH_FILE, load_env_config, save_json


def _find_free_port() -> int:
    import socket

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(("127.0.0.1", 0))
        return s.getsockname()[1]


# HTML returned to the browser after OAuth completes
_SUCCESS_HTML = """<!DOCTYPE html>
<html><head><title>B2Alpha</title>
<style>body{font-family:system-ui;display:flex;justify-content:center;align-items:center;
height:100vh;margin:0;background:#0a0a0a;color:#fff}
.card{text-align:center;padding:2rem;border:1px solid #333;border-radius:12px;background:#111}
h1{color:#22c55e}p{color:#aaa}</style></head>
<body><div class="card"><h1>Logged in</h1>
<p>You can close this tab and return to the terminal.</p></div></body></html>"""

_ERROR_HTML = """<!DOCTYPE html>
<html><head><title>B2Alpha</title>
<style>body{font-family:system-ui;display:flex;justify-content:center;align-items:center;
height:100vh;margin:0;background:#0a0a0a;color:#fff}
.card{text-align:center;padding:2rem;border:1px solid #333;border-radius:12px;background:#111}
h1{color:#ef4444}p{color:#aaa}</style></head>
<body><div class="card"><h1>Login failed</h1>
<p>%s</p><p>Please try again.</p></div></body></html>"""


def login_google() -> dict[str, Any]:
    """Run the full Google OAuth flow via Supabase and return the session dict."""
    supabase_url, _anon_key = load_env_config()
    port = 8914
    redirect_uri = f"http://127.0.0.1:{port}/callback"

    result: dict[str, Any] = {}
    error_msg: str = ""
    event = threading.Event()

    class CallbackHandler(http.server.BaseHTTPRequestHandler):
        def do_GET(self) -> None:
            nonlocal result, error_msg
            parsed = urllib.parse.urlparse(self.path)

            if parsed.path == "/callback":
                # Supabase redirects with fragment (#access_token=...) which
                # the browser keeps client-side. Serve a page that extracts it.
                self.send_response(200)
                self.send_header("Content-Type", "text/html")
                self.end_headers()
                self.wfile.write(b"""<!DOCTYPE html><html><body>
<script>
const hash = window.location.hash.substring(1);
if (hash) {
  fetch('/token?' + hash).then(() => {
    document.body.innerHTML = '""" + _SUCCESS_HTML.encode().replace(b"'", b"\\'").replace(b"\n", b"") + b"""';
  });
} else {
  const params = new URLSearchParams(window.location.search);
  const err = params.get('error_description') || params.get('error') || 'unknown';
  document.body.innerHTML = '<p>Error: ' + err + '</p>';
  fetch('/token?error=' + encodeURIComponent(err));
}
</script><p>Processing...</p></body></html>""")
                return

            if parsed.path == "/token":
                params = urllib.parse.parse_qs(parsed.query)
                if "error" in params:
                    error_msg = params["error"][0]
                    self.send_response(200)
                    self.send_header("Content-Type", "text/html")
                    self.end_headers()
                    self.wfile.write((_ERROR_HTML % error_msg).encode())
                elif "access_token" in params:
                    result = {
                        "access_token": params["access_token"][0],
                        "refresh_token": params.get("refresh_token", [""])[0],
                        "expires_in": params.get("expires_in", ["3600"])[0],
                        "token_type": params.get("token_type", ["bearer"])[0],
                    }
                    self.send_response(200)
                    self.send_header("Content-Type", "text/plain")
                    self.end_headers()
                    self.wfile.write(b"ok")
                else:
                    error_msg = "no_token_received"
                    self.send_response(400)
                    self.end_headers()
                    self.wfile.write(b"no token")
                event.set()
                return

            self.send_response(404)
            self.end_headers()

        def log_message(self, format: str, *args: Any) -> None:
            pass  # Silence HTTP logs

    server = http.server.HTTPServer(("127.0.0.1", port), CallbackHandler)
    thread = threading.Thread(target=server.handle_request)  # handle 2 requests (callback + token)
    thread.daemon = True
    thread.start()

    # Build Supabase OAuth URL
    oauth_url = (
        f"{supabase_url}/auth/v1/authorize"
        f"?provider=google"
        f"&redirect_to={urllib.parse.quote(redirect_uri)}"
    )

    print(f"Opening browser for Google sign-in...")
    print(f"If the browser doesn't open, visit:\n  {oauth_url}\n")
    webbrowser.open(oauth_url)

    # Wait for the callback, then handle the second /token request
    server.handle_request()
    event.wait(timeout=120)
    server.server_close()

    if error_msg:
        raise RuntimeError(f"OAuth failed: {error_msg}")
    if not result:
        raise RuntimeError("OAuth timed out – no token received.")

    save_json(AUTH_FILE, result)
    return result


def refresh_session() -> dict[str, Any] | None:
    """Attempt to refresh the access token using the stored refresh token."""
    import httpx

    from .config import load_json

    auth = load_json(AUTH_FILE)
    refresh_token = auth.get("refresh_token")
    if not refresh_token:
        return None

    supabase_url, anon_key = load_env_config()
    resp = httpx.post(
        f"{supabase_url}/auth/v1/token?grant_type=refresh_token",
        headers={"apikey": anon_key, "Content-Type": "application/json"},
        json={"refresh_token": refresh_token},
    )
    if resp.status_code != 200:
        return None

    data = resp.json()
    session = {
        "access_token": data["access_token"],
        "refresh_token": data.get("refresh_token", refresh_token),
        "expires_in": data.get("expires_in", 3600),
        "token_type": "bearer",
    }
    save_json(AUTH_FILE, session)
    return session


def _decode_jwt_payload(token: str) -> dict:
    """Decode the payload of a JWT without verification."""
    import base64

    payload_b64 = token.split(".")[1]
    padding = 4 - len(payload_b64) % 4
    if padding != 4:
        payload_b64 += "=" * padding
    return json.loads(base64.urlsafe_b64decode(payload_b64))


def get_user_id(token: str) -> str:
    """Extract the Supabase user ID (sub claim) from an access token."""
    payload = _decode_jwt_payload(token)
    uid = payload.get("sub")
    if not uid:
        raise RuntimeError("Token has no 'sub' claim.")
    return uid


def _is_token_expired(token: str) -> bool:
    """Check if a JWT access token is expired (with 30s margin)."""
    import time

    try:
        payload = _decode_jwt_payload(token)
        exp = payload.get("exp", 0)
        return time.time() > (exp - 30)
    except Exception:
        return True  # If we can't decode, treat as expired


def get_valid_token() -> str:
    """Return a valid access token, refreshing if needed."""
    from .config import get_access_token

    token = get_access_token()
    if token and not _is_token_expired(token):
        return token
    session = refresh_session()
    if session:
        return session["access_token"]
    raise RuntimeError("Not logged in. Run 'b2a login' first.")
