from typing import Any

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

from mcp_eregistrations_keycloak.audit.logger import KcAuditLogger
from mcp_eregistrations_keycloak.audit.models import KcObjectType
from mcp_eregistrations_keycloak.keycloak_client import (
    KeycloakAdminClient,
    KeycloakClientError,
    translate_error,
)
from mcp_eregistrations_keycloak.server import mcp
from mcp_eregistrations_keycloak.tools.validation import (
    _USER_UPDATABLE_FIELDS,
    _extract_items,
    _filter_update_fields,
    _resolve_realm,
    _validate_user_id,
)


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
async def kc_list_users(
    realm: str | None = None,
    search: str | None = None,
    first: int = 0,
    max: int = 20,
    instance: str | None = None,
) -> dict[str, Any]:
    """List users in a Keycloak realm with optional search.

    Args:
        realm: Realm name, or None for instance default.
        search: Filter by username, email, first/last name.
        first: Pagination offset (default: 0).
        max: Page size (default: 20).
        instance: Instance name, or None for auto-select.

    Returns:
        dict with users, count, pagination.
    """
    try:
        async with KeycloakAdminClient.for_instance(instance) as client:
            r = _resolve_realm(realm, instance, client)
            params: dict[str, Any] = {"first": first, "max": max}
            if search:
                params["search"] = search
            users = await client.get(f"/realms/{r}/users", **params)
            count = await client.get(f"/realms/{r}/users/count")
            items = _extract_items(users)
            return {
                "users": [
                    {
                        "id": u["id"],
                        "username": u["username"],
                        "email": u.get("email"),
                        "firstName": u.get("firstName"),
                        "lastName": u.get("lastName"),
                        "enabled": u.get("enabled"),
                    }
                    for u in items
                ],
                "count": count,
                "pagination": {"first": first, "max": max},
            }
    except KeycloakClientError as e:
        raise translate_error(e, resource_type="user") from e


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
async def kc_get_user(
    user_id: str,
    realm: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Get a user by ID.

    Args:
        user_id: User UUID.
        realm: Realm name, or None for instance default.
        instance: Instance name, or None for auto-select.

    Returns:
        dict with user details.
    """
    _validate_user_id(user_id)
    try:
        async with KeycloakAdminClient.for_instance(instance) as client:
            r = _resolve_realm(realm, instance, client)
            result: dict[str, Any] = await client.get(f"/realms/{r}/users/{user_id}")
            return result
    except KeycloakClientError as e:
        raise translate_error(e, resource_type="user", resource_id=user_id) from e


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
async def kc_get_user_sessions(
    user_id: str,
    realm: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Get active sessions for a user.

    Args:
        user_id: User UUID.
        realm: Realm name, or None for instance default.
        instance: Instance name, or None for auto-select.

    Returns:
        dict with sessions list.
    """
    _validate_user_id(user_id)
    try:
        async with KeycloakAdminClient.for_instance(instance) as client:
            r = _resolve_realm(realm, instance, client)
            sessions = await client.get(f"/realms/{r}/users/{user_id}/sessions")
            return {"user_id": user_id, "sessions": sessions or []}
    except KeycloakClientError as e:
        raise translate_error(
            e,
            resource_type="user sessions",
            resource_id=user_id,
        ) from e


@mcp.tool()
async def kc_create_user(
    username: str,
    realm: str | None = None,
    email: str | None = None,
    first_name: str | None = None,
    last_name: str | None = None,
    enabled: bool = True,
    email_verified: bool = False,
    attributes: dict[str, list[str]] | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Create a new user. Audited write operation.

    Args:
        username: Unique username.
        realm: Realm name, or None for instance default.
        email: Email address.
        first_name: First name.
        last_name: Last name.
        enabled: Whether user is active (default: True).
        email_verified: Whether email is verified (default: False).
        attributes: Custom attributes as key-value pairs.
        instance: Instance name, or None for auto-select.

    Returns:
        dict with id, username, status.
    """
    logger = KcAuditLogger.for_instance(instance)
    audit_id: str | None = None
    try:
        async with KeycloakAdminClient.for_instance(instance) as client:
            r = _resolve_realm(realm, instance, client)
            audit_id = await logger.record_pending(
                None, "create", KcObjectType.USER, {"realm": r, "username": username}
            )
            user_data: dict[str, Any] = {
                "username": username,
                "enabled": enabled,
                "emailVerified": email_verified,
            }
            if email:
                user_data["email"] = email
            if first_name:
                user_data["firstName"] = first_name
            if last_name:
                user_data["lastName"] = last_name
            if attributes:
                user_data["attributes"] = attributes

            result = await client.post(f"/realms/{r}/users", data=user_data)
            outcome = {
                "username": username,
                "realm": r,
                "status": "created",
                **result,
            }
            await logger.mark_success(audit_id, outcome)
            return outcome
    except KeycloakClientError as e:
        if audit_id is not None:
            await logger.mark_failed(audit_id, str(e))
        raise translate_error(e, resource_type="user", resource_id=username) from e


@mcp.tool()
async def kc_update_user(
    user_id: str,
    updates: dict[str, Any],
    realm: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Update a user. Audited write operation.

    Args:
        user_id: User UUID.
        updates: Fields to update (email, firstName, lastName,
            enabled, emailVerified, requiredActions, attributes).
        realm: Realm name, or None for instance default.
        instance: Instance name, or None for auto-select.

    Returns:
        dict with user_id, updated_fields, previous_values.
    """
    _validate_user_id(user_id)
    filtered = _filter_update_fields(updates, _USER_UPDATABLE_FIELDS, "user")
    if not filtered:
        raise ToolError("No valid fields to update.")
    logger = KcAuditLogger.for_instance(instance)
    audit_id: str | None = None
    try:
        async with KeycloakAdminClient.for_instance(instance) as client:
            r = _resolve_realm(realm, instance, client)
            audit_id = await logger.record_pending(
                None,
                "update",
                KcObjectType.USER,
                {"realm": r, "user_id": user_id, "fields": list(filtered.keys())},
            )
            previous = await client.get(f"/realms/{r}/users/{user_id}")
            await logger.save_rollback_state(
                audit_id, KcObjectType.USER, user_id, previous
            )
            previous_values = {k: previous.get(k) for k in filtered}
            merged = {**previous, **filtered}
            await client.put(f"/realms/{r}/users/{user_id}", data=merged)
            outcome = {
                "user_id": user_id,
                "realm": r,
                "updated_fields": list(filtered.keys()),
                "previous_values": previous_values,
                "status": "updated",
            }
            await logger.mark_success(audit_id, outcome)
            return outcome
    except KeycloakClientError as e:
        if audit_id is not None:
            await logger.mark_failed(audit_id, str(e))
        raise translate_error(e, resource_type="user", resource_id=user_id) from e


@mcp.tool()
async def kc_delete_user(
    user_id: str,
    realm: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Delete a user. Audited write operation.

    Args:
        user_id: User UUID.
        realm: Realm name, or None for instance default.
        instance: Instance name, or None for auto-select.

    Returns:
        dict with user_id, status.
    """
    _validate_user_id(user_id)
    logger = KcAuditLogger.for_instance(instance)
    audit_id: str | None = None
    try:
        async with KeycloakAdminClient.for_instance(instance) as client:
            r = _resolve_realm(realm, instance, client)
            audit_id = await logger.record_pending(
                None, "delete", KcObjectType.USER, {"realm": r, "user_id": user_id}
            )
            previous = await client.get(f"/realms/{r}/users/{user_id}")
            await logger.save_rollback_state(
                audit_id, KcObjectType.USER, user_id, previous
            )
            await client.delete(f"/realms/{r}/users/{user_id}")
            outcome = {"user_id": user_id, "realm": r, "status": "deleted"}
            await logger.mark_success(audit_id, outcome)
            return outcome
    except KeycloakClientError as e:
        if audit_id is not None:
            await logger.mark_failed(audit_id, str(e))
        raise translate_error(e, resource_type="user", resource_id=user_id) from e


@mcp.tool()
async def kc_reset_user_password(
    user_id: str,
    password: str,
    temporary: bool = True,
    realm: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Reset a user's password. Audited write operation.

    Args:
        user_id: User UUID.
        password: New password.
        temporary: If True, user must change on next login (default: True).
        realm: Realm name, or None for instance default.
        instance: Instance name, or None for auto-select.

    Returns:
        dict with user_id, temporary, status.
    """
    _validate_user_id(user_id)
    logger = KcAuditLogger.for_instance(instance)
    audit_id: str | None = None
    try:
        async with KeycloakAdminClient.for_instance(instance) as client:
            r = _resolve_realm(realm, instance, client)
            audit_id = await logger.record_pending(
                None,
                "update",
                KcObjectType.USER,
                {"realm": r, "user_id": user_id, "temporary": temporary},
            )
            await client.put(
                f"/realms/{r}/users/{user_id}/reset-password",
                data={
                    "type": "password",
                    "value": password,
                    "temporary": temporary,
                },
            )
            outcome = {
                "user_id": user_id,
                "realm": r,
                "temporary": temporary,
                "status": "password_reset",
            }
            await logger.mark_success(audit_id, outcome)
            return outcome
    except KeycloakClientError as e:
        if audit_id is not None:
            await logger.mark_failed(audit_id, str(e))
        raise translate_error(
            e,
            resource_type="user password",
            resource_id=user_id,
        ) from e
