"""Centralized Bedrock client creation and authentication.

Eliminates the ~60-line API-key-vs-SSO branching block that was previously
duplicated across every command that calls the Bedrock Converse API.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Any

from alice_cli import personality
from alice_cli.auth import detect_identity_from_key, detect_username
from alice_cli.aws.bedrock import BedrockClient, GatewayBedrockClient
from alice_cli.aws.session import get_session
from alice_cli.console import print_status, spinner
from alice_cli.logging import get_logger
from alice_cli.secrets import build_secret_path, fetch_credentials

if TYPE_CHECKING:
    from alice_cli.models import CLIConfig

log = get_logger(__name__)


def _resolve_gateway_url(config: CLIConfig) -> str | None:
    """Resolve the Cheshire Gate MCP endpoint URL.

    Two-step lookup:
    1. SSM ``/{namespace}/{environment}/cheshire-gate/gateway-name`` → gateway name
    2. AgentCore control-plane ``list-gateways`` → filter by name → ``get-gateway`` → ``gatewayUrl``

    Returns ``None`` on any failure so callers degrade gracefully to direct Bedrock.
    """
    if TYPE_CHECKING:
        from alice_cli.models import CLIConfig as _CLIConfig  # noqa: F401

    ssm_name = f"/{config.namespace}/{config.environment}/cheshire-gate/gateway-name"
    try:
        session = get_session(profile=config.profile, region=config.region)

        # Step 1: gateway name from SSM
        ssm = session.client("ssm")
        resp = ssm.get_parameter(Name=ssm_name)
        gateway_name: str = resp["Parameter"]["Value"]

        # Step 2: gateway URL from AgentCore control plane
        agentcore_ctl = session.client("bedrock-agentcore-control", region_name=config.region)
        gateways = agentcore_ctl.list_gateways().get("items", [])
        gateway_id: str | None = None
        for gw in gateways:
            if gw.get("name") == gateway_name:
                gateway_id = gw.get("gatewayId")
                break

        if not gateway_id:
            log.debug("gateway_not_found_by_name", gateway_name=gateway_name)
            return None

        detail = agentcore_ctl.get_gateway(gatewayIdentifier=gateway_id)
        url: str = detail["gatewayUrl"]
        log.debug("gateway_url_resolved", gateway_name=gateway_name, gateway_id=gateway_id, url=url)
        return url
    except Exception:  # noqa: BLE001
        log.debug("gateway_url_not_found", ssm_name=ssm_name)
        return None


def _resolve_model_to_tool(config: CLIConfig, session: object) -> dict[str, str]:
    """Load the model_id → MCP tool name map from SSM.

    The ``gateway-targets`` SSM parameter stores ``{tool_name: model_id}``.
    We reverse it so callers can look up ``tool_name = map[model_id]``.
    Returns an empty dict on any failure (gateway routing will fall back to direct Bedrock).
    """
    import json as _json

    ssm_name = f"/{config.namespace}/{config.environment}/cheshire-gate/gateway-targets"
    try:
        from alice_cli.aws.session import (
            get_session as _get_session,  # avoid circular at module load
        )

        s = _get_session(profile=config.profile, region=config.region)
        ssm = s.client("ssm")
        resp = ssm.get_parameter(Name=ssm_name)
        tool_to_model: dict[str, str] = _json.loads(resp["Parameter"]["Value"])
        # Reverse: model_id → tool_name
        return {v: k for k, v in tool_to_model.items()}
    except Exception:  # noqa: BLE001
        return {}


def _read_agentcore_gateway_url() -> str | None:
    """Read gateway endpoint URL from ``~/.alice/agentcore.json``."""
    import json as _json
    from pathlib import Path

    config_path = Path.home() / ".alice" / "agentcore.json"
    try:
        data = _json.loads(config_path.read_text())
        url: str | None = data.get("gateway", {}).get("endpoint_url")
        return url
    except (FileNotFoundError, KeyError, _json.JSONDecodeError):
        return None


@dataclass
class BedrockContext:
    """Authenticated context for Bedrock operations.

    Attributes:
        client: boto3 bedrock-runtime client, ready to call ``converse()``.
        username: User's JHED identifier (extracted from STS or API key alias).
        inference_profile_arns: Mapping of model alias → inference profile ARN.
            Pass this to ``resolve_model_id()`` when resolving model aliases.
        config: The CLI configuration used to create this context.
    """

    client: Any
    username: str
    inference_profile_arns: dict[str, str]
    config: CLIConfig


def create_bedrock_context(config: CLIConfig) -> BedrockContext:
    """Authenticate and return a ready-to-use Bedrock context.

    Handles both API-key mode and SSO profile mode transparently.

    **API-key mode** (``config.bedrock_secret_key`` is set, ``config.profile``
    is unset): skips STS and Secrets Manager, uses the stored key directly.

    **SSO profile mode**: calls STS ``get-caller-identity`` to detect the JHED,
    then fetches credentials from Secrets Manager to obtain inference profile ARNs.

    Args:
        config: Resolved CLI configuration.

    Returns:
        BedrockContext with authenticated client and inference profile ARNs.

    Raises:
        AuthError: If STS identity detection fails.
        SecretError: If credentials cannot be fetched from Secrets Manager.

    Example::

        context = create_bedrock_context(config)
        resolved_id = resolve_model_id(model_alias, context.inference_profile_arns)
        response = context.client.converse(modelId=resolved_id, messages=[...])
    """
    if config.profile:
        # ── SSO Profile Mode ──────────────────────────────────────────────────
        # When a profile is specified, permanently remove the IAM/API-key
        # credential env vars for this process lifetime.  botocore's EnvProvider
        # runs *first* in the credential chain, so leaving AWS_ACCESS_KEY_ID (or
        # AWS_BEARER_TOKEN_BEDROCK) set causes every boto3 client — including
        # third-party SDKs like bedrock-agentcore — to silently use the API-key
        # identity instead of the SSO role, even when profile_name is explicit.
        import os as _os

        for _var in (
            "AWS_ACCESS_KEY_ID",
            "AWS_SECRET_ACCESS_KEY",
            "AWS_SESSION_TOKEN",
            "AWS_BEARER_TOKEN_BEDROCK",
        ):
            _os.environ.pop(_var, None)

        with spinner(personality.status_detecting_identity(), config.quiet):
            result = detect_username(config.profile, config.region)
        username = result.username
        print_status(personality.status_identity_detected(username), config.quiet)

        secret_path = build_secret_path(config.namespace, config.environment, username)
        assert config.profile is not None  # detect_username() already raised if profile is None
        with spinner(personality.status_fetching_secret(), config.quiet):
            creds = fetch_credentials(config.profile, config.region, secret_path)

        log.debug(
            "bedrock_context_created",
            auth_mode="sso",
            username=username,
            profile=config.profile,
            has_inference_profiles=bool(creds.inference_profile_arns),
        )

        session = get_session(profile=config.profile, region=config.region)

        bedrock_client: BedrockClient | GatewayBedrockClient = BedrockClient.from_session(session)

        # --- Gateway client selection ---
        if not getattr(config, "direct", False):
            gateway_url = _read_agentcore_gateway_url() or _resolve_gateway_url(config)
            if gateway_url:
                model_to_tool = _resolve_model_to_tool(config, session)
                log.debug(
                    "gateway_mode_active", gateway_url=gateway_url, targets=list(model_to_tool)
                )
                bedrock_client = GatewayBedrockClient(gateway_url, session, model_to_tool)

        # Application inference profile ARNs live in the Bedrock service
        # account and are not accessible cross-account via an SSO role.
        # Pass None so resolve_model_id falls back to cross-region model IDs
        # (e.g. us.anthropic.claude-sonnet-4-6) — matching alice auth --claude.
        return BedrockContext(
            client=bedrock_client,
            username=username,
            inference_profile_arns={},
            config=config,
        )

    else:
        # ── API Key Mode ──────────────────────────────────────────────────────
        # Used when no SSO profile is configured (e.g. BEDROCK_SECRET_KEY env
        # var alone, or explicit --api-key flag without --profile).
        username = detect_identity_from_key(config.bedrock_access_key)
        print_status(personality.status_using_api_key(username), config.quiet)
        log.debug("bedrock_context_created", auth_mode="api_key", username=username)

        session = get_session(
            region=config.region,
            access_key=config.bedrock_access_key or "",
            secret_key=config.bedrock_secret_key or "",
        )

        bedrock_client = BedrockClient.from_session(session)

        # --- Gateway client selection ---
        if not getattr(config, "direct", False):
            gateway_url = _read_agentcore_gateway_url() or _resolve_gateway_url(config)
            if gateway_url:
                model_to_tool = _resolve_model_to_tool(config, session)
                log.debug(
                    "gateway_mode_active_apikey",
                    gateway_url=gateway_url,
                    targets=list(model_to_tool),
                )
                bedrock_client = GatewayBedrockClient(gateway_url, session, model_to_tool)

        return BedrockContext(
            client=bedrock_client,
            username=username,
            inference_profile_arns=config.inference_profile_arns or {},
            config=config,
        )
