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

Selects exactly one active auth strategy per deployment, chosen via the
`auth_method` config value (REQ-1.2.3) — there is no runtime engine for
resolving multiple simultaneously-required schemes.
"""

from __future__ import annotations

import json

from ..core.config import AuthMethod, Transport
from ..core.credential_storage import load_credential, save_credential
from .auth_strategy import AuthConfig, AuthStrategy, Credentials
from .errors import NoActiveCredentialsError
from .request_credentials import RequestCredentials
{%- if "oauth1" in auth_method_keys %}
from .strategies import oauth1
{%- endif %}
from .strategies import StubAuthStrategy
{%- if "basic" in auth_method_keys %}
from .strategies import BasicAuthStrategy
{%- endif %}
{%- if "apiKey" in auth_method_keys %}
from .strategies import ApiKeyStrategy
{%- endif %}
{%- if "pat" in auth_method_keys %}
from .strategies import PatAuthStrategy
{%- endif %}
{%- if "oauth1" in auth_method_keys %}
from .strategies import OAuth1Strategy
{%- endif %}
{%- if "oauth2" in auth_method_keys %}
from .strategies import OAuth2Strategy
{%- endif %}

CREDENTIAL_ACCOUNT = "active-credentials"


def header_location_for(auth_method: AuthMethod) -> tuple[str, str]:
    """Where `auth_method`'s credential value travels on the wire — used
    only by the HTTP-transport auth-gate/extractor (`http.auth_extractor`)
    to know which incoming header to read; stdio never calls this.
    """
    locations: dict[AuthMethod, tuple[str, str]] = {
{%- for method in auth_methods %}
        AuthMethod.{{ method.class_name | upper }}: ("{{ method.header_location }}", "{{ method.header_name }}"),
{%- endfor %}
    }
    return locations[auth_method]


def strategy_for(auth_method: AuthMethod) -> AuthStrategy:
    strategies: dict[AuthMethod, type[AuthStrategy]] = {
{%- if "basic" in auth_method_keys %}
        AuthMethod.BASIC: BasicAuthStrategy,
{%- endif %}
{%- if "apiKey" in auth_method_keys %}
        AuthMethod.APIKEY: ApiKeyStrategy,
{%- endif %}
{%- if "pat" in auth_method_keys %}
        AuthMethod.PAT: PatAuthStrategy,
{%- endif %}
{%- if "oauth2" in auth_method_keys %}
        AuthMethod.OAUTH2: OAuth2Strategy,
{%- endif %}
{%- if "oauth1" in auth_method_keys %}
        AuthMethod.OAUTH1: OAuth1Strategy,
{%- endif %}
    }
    return strategies.get(auth_method, StubAuthStrategy)()


class AuthManager:
    def __init__(self, auth_method: AuthMethod) -> None:
        self._auth_method = auth_method
        self._strategy = strategy_for(auth_method)
        self._cached_credentials: Credentials | None = None

    async def login(self, config: AuthConfig) -> Credentials:
        credentials = await self._strategy.authenticate(config)
        self._cached_credentials = credentials
        save_credential(CREDENTIAL_ACCOUNT, json.dumps(credentials))
        return credentials

    def set_credentials(self, credentials: Credentials) -> None:
        """Seeds in-memory credentials directly, bypassing OS-keychain/file
        storage entirely — for tests (which must never touch the real OS
        keychain as a side effect of running) and for callers that already
        hold validated credentials from elsewhere.
        """
        self._cached_credentials = credentials

    async def credentials(self) -> Credentials:
        if self._cached_credentials is not None and self._strategy.validate_credentials(
            self._cached_credentials
        ):
            return self._cached_credentials

        stored = load_credential(CREDENTIAL_ACCOUNT)
        if stored is not None:
            parsed: Credentials = json.loads(stored)
            if self._strategy.validate_credentials(parsed):
                self._cached_credentials = parsed
                return parsed
            try:
                refreshed = await self._strategy.refresh_token(parsed)
            except Exception:
                pass
            else:
                self._cached_credentials = refreshed
                save_credential(CREDENTIAL_ACCOUNT, json.dumps(refreshed))
                return refreshed

        raise NoActiveCredentialsError(self._auth_method.value)

    async def apply_auth_headers(
        self,
        headers: dict[str, str],
        method: str,
        url: str,
        transport: Transport,
        request_override: RequestCredentials | None = None,
    ) -> dict[str, str]:
        """Decorates outgoing request headers with credentials for
        `transport`: on `Transport.HTTP`, `request_override` (extracted
        per-request from the caller's own headers — see
        `http.auth_extractor`) is the *only* source ever consulted, since
        HTTP deployments must never leak the operator's local
        config/keychain secrets to a caller who didn't supply their own; a
        missing override is a hard error, not a fallback. On
        `Transport.STDIO`, `request_override` is ignored and behavior is
        unchanged from before HTTP-transport support existed:
        `self.credentials()` (config cascade + keychain).

        API-key placement (header vs. query, and the header/param name)
        defaults to an `X-Api-Key` header here; a scheme declaring a
        different location is one of Story P6's api-client
        responsibilities to refine, not this manager's — `request_override`'s
        `request_header_name`/`request_header_value` pair is the one
        exception, since the HTTP path already knows the scheme's declared
        header name (`PyAuthSchemeView.header_name`) at generation time.
        """
        {%- if "oauth1" in auth_method_keys %}
        if transport is Transport.HTTP and self._auth_method is AuthMethod.OAUTH1:
            raise ValueError(
                "OAuth1 is not supported over HTTP transport: its signature must be "
                "recomputed per request against the destination URL using the server's "
                "consumer key/secret, which a simple incoming-header relay cannot "
                "provide. Use stdio transport for OAuth1-authenticated APIs."
            )
        {%- endif %}

        if transport is Transport.HTTP:
            if request_override is None:
                raise ValueError(
                    "no credentials were supplied on this HTTP request; local "
                    "config/env/keychain is never used as a fallback over HTTP transport"
                )
            credentials = request_override.to_credentials_map()
        else:
            credentials = await self.credentials()

        {%- if "oauth1" in auth_method_keys %}
        if self._auth_method is AuthMethod.OAUTH1:
            consumer_key = credentials.get("consumer_key")
            private_key = credentials.get("private_key")
            if not consumer_key or not private_key:
                raise ValueError("OAuth1 credentials missing consumer_key/private_key")
            token = credentials.get("token")
            headers = dict(headers)
            headers["Authorization"] = oauth1.build_authorization_header(
                consumer_key, private_key, token, method, url
            )
            return headers
        {%- endif %}

        headers = dict(headers)
        if request_header_name := credentials.get("request_header_name"):
            headers[request_header_name] = credentials.get("request_header_value", "")
        elif authorization_header := credentials.get("authorization_header"):
            headers["Authorization"] = authorization_header
        elif api_key := credentials.get("api_key"):
            headers["X-Api-Key"] = api_key
        return headers
