"""
OAuth2 / OpenID Connect 소셜 로그인 — GUARDiA ITSM

지원 제공자:
  google  : Google OAuth2 (GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET)
  github  : GitHub OAuth2  (GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET)
  kakao   : 카카오 로그인   (KAKAO_CLIENT_ID)
  naver   : 네이버 로그인   (NAVER_CLIENT_ID / NAVER_CLIENT_SECRET)
  keycloak: 온프레미스 Keycloak OIDC (KEYCLOAK_BASE_URL / KEYCLOAK_REALM / ...)

환경변수 미설정 시 해당 제공자는 로그인 화면에서 숨겨진다.
외부망 차단 환경에서는 Keycloak(온프레미스)만 활성화 권장.
"""
from __future__ import annotations

import hashlib
import hmac
import os
import secrets
import time
from typing import Optional
from urllib.parse import urlencode

import httpx

# ── 환경변수 ──────────────────────────────────────────────────────────────────

_BASE = os.getenv("OAUTH_REDIRECT_BASE", "http://localhost:8001")

PROVIDERS: dict[str, dict] = {
    "google": {
        "name":          "Google",
        "icon":          "google",
        "enabled":       bool(os.getenv("GOOGLE_CLIENT_ID")),
        "client_id":     os.getenv("GOOGLE_CLIENT_ID", ""),
        "client_secret": os.getenv("GOOGLE_CLIENT_SECRET", ""),
        "auth_url":      "https://accounts.google.com/o/oauth2/v2/auth",
        "token_url":     "https://oauth2.googleapis.com/token",
        "userinfo_url":  "https://www.googleapis.com/oauth2/v3/userinfo",
        "scope":         "openid email profile",
        "redirect_uri":  f"{_BASE}/api/auth/oauth/google/callback",
    },
    "github": {
        "name":          "GitHub",
        "icon":          "github",
        "enabled":       bool(os.getenv("GITHUB_CLIENT_ID")),
        "client_id":     os.getenv("GITHUB_CLIENT_ID", ""),
        "client_secret": os.getenv("GITHUB_CLIENT_SECRET", ""),
        "auth_url":      "https://github.com/login/oauth/authorize",
        "token_url":     "https://github.com/login/oauth/access_token",
        "userinfo_url":  "https://api.github.com/user",
        "scope":         "read:user user:email",
        "redirect_uri":  f"{_BASE}/api/auth/oauth/github/callback",
    },
    "kakao": {
        "name":          "카카오",
        "icon":          "kakao",
        "enabled":       bool(os.getenv("KAKAO_CLIENT_ID")),
        "client_id":     os.getenv("KAKAO_CLIENT_ID", ""),
        "client_secret": os.getenv("KAKAO_CLIENT_SECRET", ""),
        "auth_url":      "https://kauth.kakao.com/oauth/authorize",
        "token_url":     "https://kauth.kakao.com/oauth/token",
        "userinfo_url":  "https://kapi.kakao.com/v2/user/me",
        "scope":         "profile_nickname account_email",
        "redirect_uri":  f"{_BASE}/api/auth/oauth/kakao/callback",
    },
    "naver": {
        "name":          "네이버",
        "icon":          "naver",
        "enabled":       bool(os.getenv("NAVER_CLIENT_ID")),
        "client_id":     os.getenv("NAVER_CLIENT_ID", ""),
        "client_secret": os.getenv("NAVER_CLIENT_SECRET", ""),
        "auth_url":      "https://nid.naver.com/oauth2.0/authorize",
        "token_url":     "https://nid.naver.com/oauth2.0/token",
        "userinfo_url":  "https://openapi.naver.com/v1/nid/me",
        "scope":         "name email",
        "redirect_uri":  f"{_BASE}/api/auth/oauth/naver/callback",
    },
    "keycloak": {
        "name":          "SSO (Keycloak)",
        "icon":          "sso",
        "enabled":       bool(os.getenv("KEYCLOAK_BASE_URL")),
        "client_id":     os.getenv("KEYCLOAK_CLIENT_ID", "guardia"),
        "client_secret": os.getenv("KEYCLOAK_CLIENT_SECRET", ""),
        "auth_url":      f"{os.getenv('KEYCLOAK_BASE_URL','')}/realms/{os.getenv('KEYCLOAK_REALM','master')}/protocol/openid-connect/auth",
        "token_url":     f"{os.getenv('KEYCLOAK_BASE_URL','')}/realms/{os.getenv('KEYCLOAK_REALM','master')}/protocol/openid-connect/token",
        "userinfo_url":  f"{os.getenv('KEYCLOAK_BASE_URL','')}/realms/{os.getenv('KEYCLOAK_REALM','master')}/protocol/openid-connect/userinfo",
        "scope":         "openid email profile",
        "redirect_uri":  f"{_BASE}/api/auth/oauth/keycloak/callback",
    },
}

# 임시 state 저장소 (메모리 — 분산 환경에서는 Redis 사용)
_states: dict[str, float] = {}


def get_enabled_providers() -> list[dict]:
    """활성화된 OAuth 제공자 목록 (민감정보 제외)."""
    return [
        {"id": pid, "name": p["name"], "icon": p["icon"]}
        for pid, p in PROVIDERS.items()
        if p["enabled"]
    ]


def build_auth_url(provider_id: str) -> str:
    """OAuth 인증 URL 생성 + CSRF state 발급."""
    p = PROVIDERS.get(provider_id)
    if not p or not p["enabled"]:
        raise ValueError(f"OAuth 제공자 미설정: {provider_id}")

    state = secrets.token_urlsafe(32)
    _states[state] = time.time()
    # 5분 초과 state 정리
    cutoff = time.time() - 300
    expired = [k for k, v in _states.items() if v < cutoff]
    for k in expired:
        _states.pop(k, None)

    params = {
        "client_id":     p["client_id"],
        "redirect_uri":  p["redirect_uri"],
        "scope":         p["scope"],
        "response_type": "code",
        "state":         state,
    }
    return p["auth_url"] + "?" + urlencode(params)


def verify_state(state: str) -> bool:
    """CSRF state 검증."""
    ts = _states.pop(state, None)
    if ts is None:
        return False
    return (time.time() - ts) < 300  # 5분 이내


async def exchange_code(provider_id: str, code: str) -> Optional[dict]:
    """인가 코드 → 액세스 토큰 교환 → 사용자 정보 조회."""
    p = PROVIDERS.get(provider_id)
    if not p:
        return None

    try:
        async with httpx.AsyncClient(timeout=15.0) as client:
            # 토큰 교환
            token_data = {
                "grant_type":    "authorization_code",
                "code":          code,
                "redirect_uri":  p["redirect_uri"],
                "client_id":     p["client_id"],
                "client_secret": p["client_secret"],
            }
            headers = {"Accept": "application/json"}
            resp = await client.post(p["token_url"], data=token_data, headers=headers)
            resp.raise_for_status()
            tokens = resp.json()

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

        async with httpx.AsyncClient(timeout=10.0) as client:
            # 사용자 정보 조회
            userinfo_resp = await client.get(
                p["userinfo_url"],
                headers={"Authorization": f"Bearer {access_token}"},
            )
            userinfo_resp.raise_for_status()
            return userinfo_resp.json()

    except Exception:
        return None


def extract_email(provider_id: str, userinfo: dict) -> Optional[str]:
    """제공자별 이메일 추출."""
    if provider_id in ("google", "keycloak"):
        return userinfo.get("email")
    if provider_id == "github":
        return userinfo.get("email") or f"{userinfo.get('login', '')}@github.com"
    if provider_id == "kakao":
        return userinfo.get("kakao_account", {}).get("email")
    if provider_id == "naver":
        return userinfo.get("response", {}).get("email")
    return userinfo.get("email")


def extract_name(provider_id: str, userinfo: dict) -> str:
    """제공자별 표시 이름 추출."""
    if provider_id in ("google", "keycloak"):
        return userinfo.get("name", "")
    if provider_id == "github":
        return userinfo.get("name") or userinfo.get("login", "")
    if provider_id == "kakao":
        return userinfo.get("kakao_account", {}).get("profile", {}).get("nickname", "")
    if provider_id == "naver":
        return userinfo.get("response", {}).get("name", "")
    return userinfo.get("name", "")
