from __future__ import annotations

import urllib.parse
from typing import TYPE_CHECKING, Any

from fastapi import APIRouter
from pydantic import BaseModel, Field

if TYPE_CHECKING:
    from niannian.client import NianNian


class CloudLoginBody(BaseModel):
    token: str = Field(..., min_length=1)
    oauth: bool = False


def _merge_oauth_cfg(config: dict[str, Any], provider: str) -> dict[str, str]:
    storage = config.get("storage") or {}
    cloud = storage.get("cloud") or {}
    oauth_cfg = dict((cloud.get(provider) or {}).get("oauth") or {})
    try:
        from niannian.config import load_providers_config

        provider_oauth = (load_providers_config().get(provider) or {}).get("oauth") or {}
        for key, value in provider_oauth.items():
            if value not in (None, ""):
                oauth_cfg[key] = str(value)
    except Exception:
        pass
    return oauth_cfg


def _onedrive_oauth_status(config: dict[str, Any]) -> dict[str, Any]:
    oauth = _merge_oauth_cfg(config, "onedrive")
    client_id = str(oauth.get("client_id") or "").strip()
    tenant = str(oauth.get("tenant") or "common").strip()
    redirect_uri = str(oauth.get("redirect_uri") or "http://localhost:8787/oauth/onedrive/callback").strip()
    scope = str(oauth.get("scope") or "offline_access Files.ReadWrite").strip()
    if not client_id:
        return {
            "configured": False,
            "portal_url": "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
            "redirect_uri": redirect_uri,
            "scope": scope,
        }
    params = urllib.parse.urlencode(
        {
            "client_id": client_id,
            "response_type": "code",
            "redirect_uri": redirect_uri,
            "scope": scope,
            "response_mode": "query",
        }
    )
    return {
        "configured": True,
        "authorize_url": f"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?{params}",
        "redirect_uri": redirect_uri,
        "scope": scope,
        "portal_url": "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
    }


def _google_oauth_status(config: dict[str, Any]) -> dict[str, Any]:
    oauth = _merge_oauth_cfg(config, "google_drive")
    client_id = str(oauth.get("client_id") or "").strip()
    redirect_uri = str(oauth.get("redirect_uri") or "http://localhost:18767/oauth/callback").strip()
    scope = str(oauth.get("scope") or "https://www.googleapis.com/auth/drive.file").strip()
    if not client_id:
        return {
            "configured": False,
            "portal_url": "https://console.cloud.google.com/apis/credentials",
            "redirect_uri": redirect_uri,
            "scope": scope,
        }
    params = urllib.parse.urlencode(
        {
            "client_id": client_id,
            "response_type": "code",
            "redirect_uri": redirect_uri,
            "scope": scope,
            "access_type": "offline",
            "prompt": "consent",
        }
    )
    return {
        "configured": True,
        "authorize_url": f"https://accounts.google.com/o/oauth2/v2/auth?{params}",
        "redirect_uri": redirect_uri,
        "scope": scope,
        "portal_url": "https://console.cloud.google.com/apis/credentials",
    }


def _baidu_oauth_status(config: dict[str, Any]) -> dict[str, Any]:
    from niannian.storage.cloud_auth.baidu_oauth import BaiduOAuthError, build_authorize_url, resolve_baidu_oauth_config

    try:
        oauth = resolve_baidu_oauth_config(config)
        return {
            "configured": True,
            "authorize_url": build_authorize_url(config),
            "union_url": "https://pan.baidu.com/union",
            "redirect_uri": oauth["redirect_uri"],
            "scope": oauth["scope"],
        }
    except BaiduOAuthError:
        return {
            "configured": False,
            "union_url": "https://pan.baidu.com/union",
            "redirect_uri": "oob",
            "scope": "basic,netdisk",
        }


def _store_oauth_provider_token(home, config, provider: str, token: str) -> None:
    from niannian.storage.cloud_auth.vault import resolve_credentials_path, update_vault
    from niannian.storage.manifest import utc_now_iso

    path = resolve_credentials_path(home, config)

    def mutator(vault: dict[str, Any]) -> None:
        entry = vault.setdefault(provider, {})
        entry["refresh_token"] = token
        entry["auth_kind"] = "bearer"
        entry["status"] = "connected"
        entry["updated_at"] = utc_now_iso()
        entry.pop("error", None)

    update_vault(path, mutator)


def create_cloud_router(nn: NianNian) -> APIRouter:
    router = APIRouter(prefix="/v1/cloud", tags=["cloud"])

    @router.get("/status")
    def cloud_status() -> dict[str, Any]:
        from niannian.storage.cloud_auth.manager import CloudCredentialManager
        from niannian.storage.cloud_mirror import read_mirror_state
        from niannian.storage.cloud_tier import CloudTierRouter
        from niannian.storage.cloud_base import resolve_remote_root
        from niannian.storage.feimao_probe import FeimaoReachability
        from niannian.storage.monthly_archive import read_monthly_archive_report

        sync_dict = nn.sync_status(probe_tier=False)
        cloud = nn._store.cloud
        mirror_state = read_mirror_state(nn._home)
        feimao_reach: dict[str, Any] | None = None
        if isinstance(cloud, CloudTierRouter):
            feimao_ps = (cloud.tier_state.get("providers") or {}).get("feimao") or {}
            if feimao_ps.get("reachability"):
                feimao_reach = FeimaoReachability(
                    status=str(feimao_ps.get("reachability")),
                    hint=feimao_ps.get("reachability_hint"),
                    error_class=feimao_ps.get("last_error_class"),
                ).to_dict()
        mgr = CloudCredentialManager(nn._home, config=nn._config)
        credentials = mgr.credentials_health(feimao_reachability=feimao_reach, mirror_state=mirror_state)
        monthly = read_monthly_archive_report(nn._home)
        tier_block = sync_dict.get("tier") or {}
        return {
            "remote_root": resolve_remote_root(nn._config),
            "credentials": credentials,
            "tier": tier_block,
            "clouds": sync_dict.get("backends", {}),
            "monthly_archive": monthly,
            "recent_alerts": tier_block.get("recent_alerts", []),
            "baidu_oauth": _baidu_oauth_status(nn._config),
            "onedrive_oauth": _onedrive_oauth_status(nn._config),
            "google_oauth": _google_oauth_status(nn._config),
        }

    @router.post("/login/{provider}")
    def cloud_login(provider: str, body: CloudLoginBody) -> dict[str, Any]:
        allowed = ("quark", "baidu", "feimao", "onedrive", "google_drive")
        if provider not in allowed:
            return {"ok": False, "error": "unknown provider"}

        from niannian.storage.cloud_auth.manager import CloudCredentialManager

        mgr = CloudCredentialManager(nn._home, config=nn._config)
        if provider in ("onedrive", "google_drive"):
            _store_oauth_provider_token(nn._home, nn._config, provider, body.token)
            vault = mgr.status()
            return {"ok": True, "provider": provider, "status": vault.get(provider) or {"auth": "connected"}}
        if provider == "baidu" and body.oauth:
            try:
                from niannian.storage.cloud_auth.baidu_oauth import (
                    apply_token_response,
                    exchange_authorization_code,
                )
                from niannian.storage.cloud_auth.vault import update_vault

                token_data = exchange_authorization_code(body.token, config=nn._config)

                def mutator(vault: dict[str, Any]) -> None:
                    entry = vault.setdefault("baidu", {})
                    apply_token_response(entry, token_data)

                update_vault(mgr.path, mutator)
                return {"ok": True, "provider": provider, "status": mgr.status().get(provider)}
            except Exception as exc:
                return {"ok": False, "error": str(exc)}
        mgr.set_login_token(provider, body.token)
        return {"ok": True, "provider": provider, "status": mgr.status().get(provider)}

    return router
