from typing import Any

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

from mcp_eregistrations_common.auth import (
    AuthenticationError,
    keycloak_password_grant,
    store_shared_tokens,
)
from mcp_eregistrations_common.auth_store import store_credentials
from mcp_eregistrations_common.config import get_instance_config
from mcp_eregistrations_smartlink.audit.logger import SlAuditLogger
from mcp_eregistrations_smartlink.rollback.manager import execute_rollback
from mcp_eregistrations_smartlink.server import mcp
from mcp_eregistrations_smartlink.smartlink_client import (
    SmartLinkClient,
    SmartLinkClientError,
    translate_error,
)


@mcp.tool()
async def smartlink_auth_login(
    username: str,
    password: str,
    instance: str,
) -> dict[str, Any]:
    """Authenticate to SmartLink via Keycloak.

    Uses the same credentials and realm as BPA. The authenticated user
    must have the sysadmin role for audit access.

    Args:
        username: Keycloak username.
        password: Keycloak password.
        instance: Instance name (required).

    Returns:
        dict with status, instance, 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")
    client_id = config.get("keycloak_client_id", "mcp-eregistrations-bpa")

    try:
        result = await keycloak_password_grant(
            keycloak_url=keycloak_url,
            realm=realm,
            client_id=client_id,
            username=username,
            password=password,
        )
    except AuthenticationError as e:
        raise ToolError(str(e)) from e

    access_token = result.get("access_token")
    if not access_token:
        raise ToolError("No access_token in Keycloak response.")

    store_shared_tokens(
        instance,
        access_token=result["access_token"],
        refresh_token=result.get("refresh_token"),
        token_endpoint=result["token_endpoint"],
        client_id=client_id,
    )

    store_credentials(
        instance,
        username,
        password,
        keycloak_url=keycloak_url,
        keycloak_realm=realm,
        client_id=client_id,
    )

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


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

    Args:
        limit: Max entries to return (default: 20).
        offset: Pagination offset (default: 0).
        object_type: Filter by object type (flow, action).
        instance: Instance name.

    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 = SlAuditLogger.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 smartlink_rollback(
    audit_id: str,
    instance: str | None = None,
) -> dict[str, Any]:
    """Rollback a SmartLink operation by MCP audit ID. Audited write operation.

    Reverses a previous flow/action update by restoring the captured state.

    Args:
        audit_id: MCP audit entry ID to rollback.
        instance: Instance name.

    Returns:
        dict with audit_id, object_type, status.
    """
    logger = SlAuditLogger.for_instance(instance)
    try:
        async with SmartLinkClient.for_instance(instance) as client:
            return await execute_rollback(client, logger, audit_id)
    except SmartLinkClientError as e:
        raise translate_error(e, resource_type="rollback", resource_id=audit_id) from e
