from typing import Any

import httpx
from fastmcp.exceptions import ToolError
from mcp.types import ToolAnnotations

from mcp_eregistrations_common.auth import store_shared_tokens
from mcp_eregistrations_common.auth_store import store_credentials
from mcp_eregistrations_common.config import get_instance_config
from mcp_eregistrations_keycloak.audit.logger import KcAuditLogger
from mcp_eregistrations_keycloak.keycloak_client import (
    KeycloakAdminClient,
    KeycloakClientError,
    translate_error,
)
from mcp_eregistrations_keycloak.server import mcp


async def _client_credentials_grant(
    keycloak_url: str,
    realm: str,
    client_id: str,
    client_secret: str,
) -> dict[str, Any]:
    """Perform client_credentials grant against Keycloak token endpoint."""
    token_endpoint = f"{keycloak_url}/realms/{realm}/protocol/openid-connect/token"
    async with httpx.AsyncClient(timeout=30.0) as http_client:
        resp = await http_client.post(
            token_endpoint,
            data={
                "grant_type": "client_credentials",
                "client_id": client_id,
                "client_secret": client_secret,
            },
            headers={"Content-Type": "application/x-www-form-urlencoded"},
        )
        if resp.status_code != 200:
            try:
                body = resp.json()
                msg = body.get("error_description", body.get("error", str(body)))
            except Exception:
                msg = resp.text
            raise ToolError(f"Client credentials grant failed: {msg}")
        result: dict[str, Any] = resp.json()
        result["token_endpoint"] = token_endpoint
        return result


@mcp.tool()
async def kc_auth_login(
    username: str,
    password: str,
    instance: str,
    grant_type: str = "password",
) -> dict[str, Any]:
    """Authenticate to Keycloak Admin API. Audited write operation.

    Supports password grant (user credentials) and client_credentials grant
    (service account). For client_credentials, pass client_id as username
    and client_secret as password.

    Args:
        username: Admin username (or client_id for client_credentials).
        password: Admin password (or client_secret for client_credentials).
        instance: Instance name (required — no auto-select for Keycloak).
        grant_type: Auth flow (default: "password").

    Returns:
        dict with status, instance, token_type, expires_in.
    """
    config = get_instance_config(instance)
    keycloak_url = config.get("keycloak_url")
    if not keycloak_url:
        raise ToolError(f"Instance '{instance}' has no Keycloak configuration.")

    realm = config.get("keycloak_realm", "master")
    admin_client_id = config.get("keycloak_admin_client_id", "admin-cli")

    if grant_type == "password":
        from mcp_eregistrations_common.auth import keycloak_password_grant

        result = await keycloak_password_grant(
            keycloak_url=keycloak_url,
            realm=realm,
            client_id=admin_client_id,
            username=username,
            password=password,
        )
    elif grant_type == "client_credentials":
        result = await _client_credentials_grant(
            keycloak_url,
            realm,
            username,
            password,
        )
    else:
        raise ToolError(f"Unsupported grant_type: {grant_type}")

    # Store with the ADMIN client_id (not the BPA one)
    store_shared_tokens(
        instance,
        access_token=result["access_token"],
        refresh_token=result.get("refresh_token"),
        token_endpoint=result["token_endpoint"],
        client_id=admin_client_id,
    )

    # Also store credentials for auto-reauthentication
    store_credentials(
        instance,
        username,
        password,
        keycloak_url=keycloak_url,
        keycloak_realm=realm,
        client_id=admin_client_id,
    )

    return {
        "status": "authenticated",
        "instance": instance,
        "token_type": result.get("token_type", "Bearer"),
        "expires_in": result.get("expires_in"),
    }


# --- Audit & Rollback Tools ---


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
async def kc_audit_log(
    limit: int = 20,
    offset: int = 0,
    object_type: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """List Keycloak audit log entries.

    Args:
        limit: Max entries to return (default: 20).
        offset: Pagination offset (default: 0).
        object_type: Filter by object type (realm, user, role, etc.).
        instance: Instance name, or None for auto-select.

    Returns:
        dict with entries, count.
    """
    if limit < 1:
        raise ToolError("limit must be >= 1")
    if offset < 0:
        raise ToolError("offset must be >= 0")
    limit = min(limit, 100)
    logger = KcAuditLogger.for_instance(instance)
    entries = await logger.list_entries(
        limit=limit, offset=offset, object_type=object_type
    )
    return {"entries": entries, "count": len(entries)}


@mcp.tool()
async def kc_rollback(
    audit_id: str,
    instance: str | None = None,
) -> dict[str, Any]:
    """Rollback a Keycloak operation by audit ID. Audited write operation.

    Args:
        audit_id: Audit entry ID to rollback.
        instance: Instance name, or None for auto-select.

    Returns:
        dict with audit_id, status.
    """
    from mcp_eregistrations_keycloak.audit.logger import KcAuditLogger
    from mcp_eregistrations_keycloak.rollback.manager import execute_rollback

    logger = KcAuditLogger.for_instance(instance)
    try:
        async with KeycloakAdminClient.for_instance(instance) as client:
            return await execute_rollback(client, logger, audit_id)
    except KeycloakClientError as e:
        raise translate_error(e, resource_type="rollback", resource_id=audit_id) from e


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
async def kc_rollback_list(
    instance: str | None = None,
) -> dict[str, Any]:
    """List available rollback points.

    Args:
        instance: Instance name, or None for auto-select.

    Returns:
        dict with rollback_points.
    """
    logger = KcAuditLogger.for_instance(instance)
    entries = await logger.list_entries(limit=100)
    candidates = [
        e for e in entries if e["status"] == "success" and not e.get("rolled_back_at")
    ]
    # Filter to only entries that actually have rollback state
    rollback_points = []
    for entry in candidates:
        state = await logger.get_rollback_state(entry["id"])
        if state:
            rollback_points.append(entry)
    return {"rollback_points": rollback_points, "count": len(rollback_points)}


@mcp.tool()
async def kc_rollback_cleanup(
    days: int = 30,
    instance: str | None = None,
) -> dict[str, Any]:
    """Delete old rollback states. Audited write operation.

    Args:
        days: Delete states older than this many days (default: 30).
        instance: Instance name, or None for auto-select.

    Returns:
        dict with deleted_count.
    """
    if days < 1 or days > 365:
        raise ToolError("days must be between 1 and 365")
    logger = KcAuditLogger.for_instance(instance)
    deleted = await logger.cleanup_old_entries(days=days)
    return {"deleted_count": deleted, "days_threshold": days}
