from __future__ import annotations

import json
import logging
import os
import ssl
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timedelta, timezone
from typing import Any, Callable

from niannian.config import load_providers_config
from niannian.storage.cloud_auth.https_urlopen import default_urlopen
from niannian.storage.manifest import utc_now_iso

logger = logging.getLogger(__name__)

DEFAULT_SCOPE = "https://www.googleapis.com/auth/drive.file"
DEFAULT_AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/v2/auth"
DEFAULT_TOKEN_URL = "https://oauth2.googleapis.com/token"
HttpOpener = Callable[..., Any]


class GoogleDriveOAuthError(Exception):
    pass


def resolve_google_drive_oauth_config(config: dict[str, Any] | None = None) -> dict[str, str]:
    config = config or {}
    cloud = ((config.get("storage") or {}).get("cloud") or {}).get("google_drive") or {}
    oauth_cfg = dict(cloud.get("oauth") or {})
    providers = load_providers_config()
    provider_oauth = (providers.get("google_drive") or {}).get("oauth") or {}
    for key, value in provider_oauth.items():
        if value not in (None, ""):
            oauth_cfg[key] = value
    env_map = {
        "client_id": os.environ.get("GOOGLE_DRIVE_CLIENT_ID"),
        "client_secret": os.environ.get("GOOGLE_DRIVE_CLIENT_SECRET"),
        "redirect_uri": os.environ.get("GOOGLE_DRIVE_REDIRECT_URI"),
    }
    for key, value in env_map.items():
        if value:
            oauth_cfg[key] = value
    client_id = str(oauth_cfg.get("client_id") or "").strip()
    client_secret = str(oauth_cfg.get("client_secret") or "").strip()
    if not client_id or not client_secret:
        raise GoogleDriveOAuthError("missing google_drive oauth client_id/client_secret in providers.local.yaml")
    return {
        "client_id": client_id,
        "client_secret": client_secret,
        "redirect_uri": str(oauth_cfg.get("redirect_uri") or "http://localhost").strip(),
        "scope": str(oauth_cfg.get("scope") or DEFAULT_SCOPE).strip(),
        "authorize_url": str(oauth_cfg.get("authorize_url") or DEFAULT_AUTHORIZE_URL).strip(),
        "token_url": str(oauth_cfg.get("token_url") or DEFAULT_TOKEN_URL).strip(),
    }


def build_authorize_url(config: dict[str, Any] | None = None, *, state: str = "niannian") -> str:
    oauth = resolve_google_drive_oauth_config(config)
    params = {
        "client_id": oauth["client_id"],
        "response_type": "code",
        "redirect_uri": oauth["redirect_uri"],
        "scope": oauth["scope"],
        "access_type": "offline",
        "prompt": "consent",
        "state": state,
    }
    return f"{oauth['authorize_url']}?{urllib.parse.urlencode(params)}"


def _post_token(oauth: dict[str, str], params: dict[str, str], *, http_opener: HttpOpener | None = None) -> dict[str, Any]:
    opener = http_opener or default_urlopen
    body = urllib.parse.urlencode(params).encode("utf-8")
    req = urllib.request.Request(
        oauth["token_url"],
        data=body,
        method="POST",
        headers={"Content-Type": "application/x-www-form-urlencoded", "User-Agent": "niannian/0.2.9"},
    )
    try:
        with opener(req, timeout=30) as resp:
            raw = resp.read().decode("utf-8", "replace")
    except urllib.error.HTTPError as exc:
        detail = exc.read().decode("utf-8", "replace") if exc.fp else str(exc)
        raise GoogleDriveOAuthError(f"token HTTP {exc.code}: {detail[:300]}") from exc
    except (urllib.error.URLError, TimeoutError, ssl.SSLError, OSError) as exc:
        raise GoogleDriveOAuthError(f"token network error: {exc}") from exc
    payload = json.loads(raw)
    if payload.get("error"):
        desc = payload.get("error_description") or payload.get("error")
        raise GoogleDriveOAuthError(f"token error: {desc}")
    if not payload.get("access_token"):
        raise GoogleDriveOAuthError(f"token response missing access_token: {raw[:200]}")
    return payload


def exchange_authorization_code(code: str, config: dict[str, Any] | None = None, *, http_opener: HttpOpener | None = None) -> dict[str, Any]:
    oauth = resolve_google_drive_oauth_config(config)
    params = {
        "client_id": oauth["client_id"],
        "client_secret": oauth["client_secret"],
        "code": code.strip(),
        "redirect_uri": oauth["redirect_uri"],
        "grant_type": "authorization_code",
    }
    return _post_token(oauth, params, http_opener=http_opener)


def refresh_access_token(refresh_token: str, config: dict[str, Any] | None = None, *, http_opener: HttpOpener | None = None) -> dict[str, Any]:
    oauth = resolve_google_drive_oauth_config(config)
    params = {
        "client_id": oauth["client_id"],
        "client_secret": oauth["client_secret"],
        "refresh_token": refresh_token.strip(),
        "grant_type": "refresh_token",
    }
    return _post_token(oauth, params, http_opener=http_opener)


def apply_token_response(entry: dict[str, Any], token_data: dict[str, Any]) -> dict[str, Any]:
    now = datetime.now(timezone.utc)
    expires_in = int(token_data.get("expires_in") or 3600)
    entry["access_token"] = str(token_data["access_token"])
    refresh = token_data.get("refresh_token")
    if refresh:
        entry["refresh_token"] = str(refresh)
    entry["status"] = "connected"
    entry["auth_kind"] = "bearer"
    entry["role"] = "monthly_only"
    entry["expires_at"] = (now + timedelta(seconds=expires_in)).replace(microsecond=0).isoformat().replace("+00:00", "Z")
    entry["updated_at"] = utc_now_iso()
    entry.pop("error", None)
    return entry
