"""{{ display_name }} MCP server — generated by mcpify. Do not hand-edit."""

from __future__ import annotations

import time
from typing import Any

import httpx
from authlib.common.security import generate_token
from authlib.oauth2.rfc7636 import create_s256_code_challenge

from ..auth_strategy import AuthConfig, AuthStrategy, Credentials
from ..errors import MissingCredentialsError, TokenExpiredError


def create_pkce_pair() -> tuple[str, str]:
    """Generates a PKCE `code_verifier`/`code_challenge` pair (RFC 7636),
    for the setup wizard's authorization-code flow.
    """
    verifier = generate_token(64)
    challenge = create_s256_code_challenge(verifier)
    return verifier, challenge


async def _exchange(token_url: str, params: dict[str, str]) -> dict[str, Any]:
    async with httpx.AsyncClient() as client:
        response = await client.post(token_url, data=params)
        response.raise_for_status()
        return response.json()


def _from_token_response(
    data: dict[str, Any],
    client_id: str | None,
    client_secret: str | None,
    token_url: str | None,
) -> Credentials:
    credentials: Credentials = {
        "authorization_header": f"Bearer {data['access_token']}",
        "access_token": data["access_token"],
    }
    if refresh_token := data.get("refresh_token"):
        credentials["refresh_token"] = refresh_token
    if expires_in := data.get("expires_in"):
        credentials["expires_at"] = str(int((time.time() + expires_in) * 1000))
    if client_id:
        credentials["client_id"] = client_id
    if client_secret:
        credentials["client_secret"] = client_secret
    if token_url:
        credentials["token_url"] = token_url
    return credentials


class OAuth2Strategy(AuthStrategy):
    async def authenticate(self, config: AuthConfig) -> Credentials:
        client_id = config.get("client_id")
        token_url = config.get("token_url")
        if not client_id or not token_url:
            raise MissingCredentialsError("client_id, token_url")

        params = {"grant_type": "authorization_code", "client_id": client_id}
        if client_secret := config.get("client_secret"):
            params["client_secret"] = client_secret
        if code := config.get("authorization_code"):
            params["code"] = code
        if redirect_uri := config.get("redirect_uri"):
            params["redirect_uri"] = redirect_uri
        if verifier := config.get("code_verifier"):
            params["code_verifier"] = verifier

        response = await _exchange(token_url, params)
        return _from_token_response(response, client_id, config.get("client_secret"), token_url)

    async def refresh_token(self, credentials: Credentials) -> Credentials:
        refresh_token = credentials.get("refresh_token")
        token_url = credentials.get("token_url")
        if not refresh_token or not token_url:
            raise TokenExpiredError()

        params = {
            "grant_type": "refresh_token",
            "refresh_token": refresh_token,
            "client_id": credentials.get("client_id", ""),
        }
        if client_secret := credentials.get("client_secret"):
            params["client_secret"] = client_secret

        response = await _exchange(token_url, params)
        return _from_token_response(
            response, credentials.get("client_id"), credentials.get("client_secret"), token_url
        )

    def validate_credentials(self, credentials: Credentials) -> bool:
        if "access_token" not in credentials:
            return False
        expires_at = credentials.get("expires_at")
        if expires_at is None:
            return True
        try:
            return int(time.time() * 1000) < int(expires_at)
        except ValueError:
            return False
