"""
oauth_service.py — Helper untuk OAuth2 login via Google dan GitHub.

Flow:
  1. Buat URL otorisasi + state (CSRF protection)        → build_*_auth_url()
  2. User diarahkan ke provider, login, dan kembali ke callback
  3. Tukar authorization code dengan user info           → exchange_*_code()
  4. Buat atau temukan user di DB                        → get_or_create_oauth_user() (di auth_service.py)

State disimpan di cookie HttpOnly (max 5 menit) agar tidak perlu session server-side.
"""

import secrets
import httpx
from typing import Optional
from urllib.parse import urlencode

from app.core.config import (
    GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI,
    GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GITHUB_REDIRECT_URI,
)

# ── Provider URL ─────────────────────────────────────────────────────
_GOOGLE_AUTH_URL     = "https://accounts.google.com/o/oauth2/v2/auth"
_GOOGLE_TOKEN_URL    = "https://oauth2.googleapis.com/token"
_GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v3/userinfo"

_GITHUB_AUTH_URL     = "https://github.com/login/oauth/authorize"
_GITHUB_TOKEN_URL    = "https://github.com/login/oauth/access_token"
_GITHUB_USERINFO_URL = "https://api.github.com/user"
_GITHUB_EMAIL_URL    = "https://api.github.com/user/emails"

# Timeout untuk semua request ke provider (detik)
_HTTP_TIMEOUT = 10


# ── Helpers ───────────────────────────────────────────────────────────

def generate_state() -> str:
    """
    Buat state acak yang aman untuk perlindungan CSRF.
    Disimpan di cookie dan dibandingkan saat callback.
    """
    return secrets.token_urlsafe(32)


def is_google_enabled() -> bool:
    """Kembalikan True jika kredensial Google sudah dikonfigurasi."""
    return bool(GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET)


def is_github_enabled() -> bool:
    """Kembalikan True jika kredensial GitHub sudah dikonfigurasi."""
    return bool(GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET)


# ── Google ────────────────────────────────────────────────────────────

def build_google_auth_url(state: str) -> str:
    """Bangun URL redirect ke Google OAuth2."""
    params = {
        "client_id":     GOOGLE_CLIENT_ID,
        "redirect_uri":  GOOGLE_REDIRECT_URI,
        "response_type": "code",
        "scope":         "openid email profile",
        "state":         state,
        "access_type":   "online",
        "prompt":        "select_account",
    }
    return f"{_GOOGLE_AUTH_URL}?{urlencode(params)}"


async def exchange_google_code(code: str) -> Optional[dict]:
    """
    Tukar authorization code dengan user info Google.
    Kembalikan dict berisi 'sub', 'email', 'name', 'picture'
    atau None jika gagal.
    """
    async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT) as client:
        # 1. Tukar code → access token
        token_resp = await client.post(_GOOGLE_TOKEN_URL, data={
            "code":          code,
            "client_id":     GOOGLE_CLIENT_ID,
            "client_secret": GOOGLE_CLIENT_SECRET,
            "redirect_uri":  GOOGLE_REDIRECT_URI,
            "grant_type":    "authorization_code",
        })
        if token_resp.status_code != 200:
            return None

        access_token = token_resp.json().get("access_token")
        if not access_token:
            return None

        # 2. Ambil info user
        userinfo_resp = await client.get(
            _GOOGLE_USERINFO_URL,
            headers={"Authorization": f"Bearer {access_token}"},
        )
        if userinfo_resp.status_code != 200:
            return None

        return userinfo_resp.json()


# ── GitHub ────────────────────────────────────────────────────────────

def build_github_auth_url(state: str) -> str:
    """Bangun URL redirect ke GitHub OAuth2."""
    params = {
        "client_id":    GITHUB_CLIENT_ID,
        "redirect_uri": GITHUB_REDIRECT_URI,
        "scope":        "read:user user:email",
        "state":        state,
    }
    return f"{_GITHUB_AUTH_URL}?{urlencode(params)}"


async def exchange_github_code(code: str) -> Optional[dict]:
    """
    Tukar authorization code dengan user info GitHub.
    Kembalikan dict berisi 'id', 'login', 'email', 'name', 'avatar_url'
    atau None jika gagal.

    Catatan: banyak GitHub user menyembunyikan email di profil publik,
    sehingga diperlukan request tambahan ke /user/emails.
    """
    async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT) as client:
        # 1. Tukar code → access token
        token_resp = await client.post(
            _GITHUB_TOKEN_URL,
            data={
                "code":          code,
                "client_id":     GITHUB_CLIENT_ID,
                "client_secret": GITHUB_CLIENT_SECRET,
                "redirect_uri":  GITHUB_REDIRECT_URI,
            },
            headers={"Accept": "application/json"},
        )
        if token_resp.status_code != 200:
            return None

        access_token = token_resp.json().get("access_token")
        if not access_token:
            return None

        auth_headers = {
            "Authorization": f"Bearer {access_token}",
            "Accept":        "application/vnd.github.v3+json",
        }

        # 2. Ambil profil user
        userinfo_resp = await client.get(_GITHUB_USERINFO_URL, headers=auth_headers)
        if userinfo_resp.status_code != 200:
            return None

        user_data = userinfo_resp.json()

        # 3. Ambil email utama jika profil tidak menyertakannya
        if not user_data.get("email"):
            email_resp = await client.get(_GITHUB_EMAIL_URL, headers=auth_headers)
            if email_resp.status_code == 200:
                emails = email_resp.json()
                # Prioritas: primary + verified
                primary_email = next(
                    (
                        e["email"]
                        for e in emails
                        if e.get("primary") and e.get("verified")
                    ),
                    None,
                )
                user_data["email"] = primary_email

        return user_data