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

Composes the OAuth1 RSA-SHA1 signature base string by hand (RFC 5849
§3.4.1) and delegates only the actual RSA signing step to authlib's
`rsa_sha1_signature` — a public, low-level helper in
`authlib.oauth1.rfc5849.signature` built on the `cryptography` package,
not authlib's full `OAuth1Session`/`Client` abstraction. This mirrors
`targets::rust`'s and `targets::typescript`'s approach of composing the
signature base string directly rather than depending on a heavyweight
OAuth client library.
"""

from __future__ import annotations

import time
import uuid
from urllib.parse import parse_qsl, quote, urlparse

from authlib.oauth1.rfc5849.signature import rsa_sha1_signature

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

# RFC 3986 unreserved characters (`-`, `.`, `_`, `~`) are the only ones
# OAuth1 percent-encoding (RFC 5849 §3.6) leaves unescaped beyond
# alphanumerics.
_OAUTH_SAFE = "-._~"


def _oauth_encode(value: str) -> str:
    return quote(value, safe=_OAUTH_SAFE)


def build_authorization_header(
    consumer_key: str,
    private_key_pem: str,
    token: str | None,
    method: str,
    url: str,
) -> str:
    """Computes the per-request `Authorization` header for OAuth1
    RSA-SHA1. Unlike the other strategies, OAuth1 signs the method + URL +
    params on every call, so this can't be precomputed once in
    `authenticate()` — callers (the generated API client, Story P6) call
    this immediately before dispatch.
    """
    parsed_url = urlparse(url)
    base_url = f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}"

    nonce = uuid.uuid4().hex
    timestamp = str(int(time.time()))

    params: dict[str, str] = dict(parse_qsl(parsed_url.query))
    params["oauth_consumer_key"] = consumer_key
    params["oauth_nonce"] = nonce
    params["oauth_signature_method"] = "RSA-SHA1"
    params["oauth_timestamp"] = timestamp
    params["oauth_version"] = "1.0"
    if token:
        params["oauth_token"] = token

    normalized_params = "&".join(
        f"{_oauth_encode(key)}={_oauth_encode(value)}" for key, value in sorted(params.items())
    )

    base_string = "&".join(
        [method.upper(), _oauth_encode(base_url), _oauth_encode(normalized_params)]
    )

    signature = rsa_sha1_signature(base_string, private_key_pem)

    header_params = [
        ("oauth_consumer_key", consumer_key),
        ("oauth_nonce", nonce),
        ("oauth_signature", signature),
        ("oauth_signature_method", "RSA-SHA1"),
        ("oauth_timestamp", timestamp),
        ("oauth_version", "1.0"),
    ]
    if token:
        header_params.append(("oauth_token", token))

    header_value = ", ".join(f'{key}="{_oauth_encode(value)}"' for key, value in header_params)
    return f"OAuth {header_value}"


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

        credentials: Credentials = {
            "consumer_key": consumer_key,
            "private_key": private_key,
        }
        token = config.get("token")
        if token:
            credentials["token"] = token
        return credentials

    def validate_credentials(self, credentials: Credentials) -> bool:
        return "consumer_key" in credentials and "private_key" in credentials
