from __future__ import annotations

from typing import Any

from fastmcp.exceptions import ToolError

from mcp_eregistrations_keycloak.audit.logger import KcAuditLogger
from mcp_eregistrations_keycloak.keycloak_client import KeycloakAdminClient


def _format_endpoint(template: str, params: dict[str, Any]) -> str:
    """Format endpoint template, raising ToolError on missing keys."""
    try:
        return template.format(**params)
    except KeyError as e:
        raise ToolError(
            f"Cannot format rollback endpoint '{template}': missing key {e}. "
            f"Available params: {list(params.keys())}"
        ) from e


ROLLBACK_ENDPOINTS: dict[str, tuple[str | None, str | None, str | None]] = {
    # (delete_endpoint, update_endpoint, create_endpoint)
    "realm": (
        "/realms/{realm}",
        "/realms/{realm}",
        "/realms",
    ),
    "user": (
        "/realms/{realm}/users/{id}",
        "/realms/{realm}/users/{id}",
        "/realms/{realm}/users",
    ),
    "role": (
        "/realms/{realm}/roles/{name}",
        "/realms/{realm}/roles/{name}",
        "/realms/{realm}/roles",
    ),
    "theme": (
        None,
        "/realms/{realm}",
        None,
    ),
    "user_profile": (
        None,
        "/realms/{realm}/users/profile",
        None,
    ),
    "client_mapper": (
        "/realms/{realm}/clients/{client_id}/protocol-mappers/models/{id}",
        None,
        "/realms/{realm}/clients/{client_id}/protocol-mappers/models",
    ),
    "composite_role": (
        "/realms/{realm}/roles/{role_name}/composites",
        None,
        "/realms/{realm}/roles/{role_name}/composites",
    ),
    "user_role_mapping": (
        "/realms/{realm}/users/{user_id}/role-mappings/realm",
        None,
        "/realms/{realm}/users/{user_id}/role-mappings/realm",
    ),
}


async def execute_rollback(
    client: KeycloakAdminClient,
    audit_logger: KcAuditLogger,
    audit_id: str,
) -> dict[str, Any]:
    """Execute a rollback for a given audit entry."""
    entry = await audit_logger.get_entry(audit_id)
    if not entry:
        raise ToolError(f"Audit entry '{audit_id}' not found.")

    if entry["status"] != "success":
        raise ToolError(
            f"Cannot rollback entry with status '{entry['status']}'. "
            f"Only 'success' entries can be rolled back."
        )
    if entry.get("rolled_back_at"):
        raise ToolError(f"Entry '{audit_id}' has already been rolled back.")

    state = await audit_logger.get_rollback_state(audit_id)
    if not state:
        raise ToolError(
            f"No rollback state found for entry '{audit_id}'. "
            f"This operation did not capture previous state."
        )

    object_type = entry["object_type"]
    operation = entry["operation_type"]
    previous = state["previous_state"]
    params = entry.get("params", {})

    endpoints = ROLLBACK_ENDPOINTS.get(object_type)
    if not endpoints:
        raise ToolError(f"Rollback not supported for object type '{object_type}'.")

    delete_ep, update_ep, create_ep = endpoints

    if operation == "create" and delete_ep:
        # Reverse a create → delete
        object_id = state.get("object_id") or params.get("id")
        path = _format_endpoint(delete_ep, {**params, "id": object_id})
        if isinstance(previous, list):
            await client.delete(path, data=previous)
        else:
            await client.delete(path)
    elif operation == "update" and update_ep:
        # Reverse an update → PUT previous state
        path = _format_endpoint(update_ep, params)
        await client.put(path, data=previous)
    elif operation == "delete" and create_ep:
        # Reverse a delete → POST to recreate
        path = _format_endpoint(create_ep, params)
        await client.post(path, data=previous)
    else:
        raise ToolError(
            f"Cannot rollback '{operation}' on '{object_type}': no endpoint configured."
        )

    await audit_logger.mark_rolled_back(audit_id)
    return {
        "audit_id": audit_id,
        "operation": operation,
        "object_type": object_type,
        "status": "rolled_back",
    }
