"""Component action tools for BPA API.

This module provides MCP tools for managing component actions
in the BPA API. Component actions link bots to form components,
enabling workflow automation (e.g., button triggers bot execution).

Tools:
    componentaction_get: Get component actions by ID
    componentaction_get_by_component: Get actions for a form component
    componentaction_get_system_actions: List built-in system button actions
    componentaction_save: Create/replace component actions (audited)
    componentaction_update: Update component actions by ID (audited)
    componentaction_delete: Remove component actions (audited)
"""

from __future__ import annotations

import logging
import re
from typing import Any

from fastmcp.exceptions import ToolError

from mcp_eregistrations_bpa.audit.context import (
    NotAuthenticatedError,
    get_current_user_email,
)
from mcp_eregistrations_bpa.audit.logger import AuditLogger
from mcp_eregistrations_bpa.bpa_client import BPAClient
from mcp_eregistrations_bpa.bpa_client.errors import (
    BPAClientError,
    BPANotFoundError,
    translate_error,
)
from mcp_eregistrations_bpa.config import resolve_db_path
from mcp_eregistrations_bpa.tools._determinant_shared import build_determinant_refs
from mcp_eregistrations_bpa.tools._form_shared import parse_form_schema
from mcp_eregistrations_bpa.tools.formio_helpers import find_component
from mcp_eregistrations_bpa.tools.forms import FORM_TYPES

logger = logging.getLogger(__name__)

# UUID pattern for distinguishing real bot IDs from system bot names
_UUID_PATTERN = re.compile(
    r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE
)

# System action categories derived from CustomButtonAction.java
_SYSTEM_ACTION_CATEGORIES: dict[str, str] = {
    # Applicant (PartA) actions
    "saveData": "applicant",
    "saveA5": "applicant",
    "saveSENDPAGE": "applicant",
    "savePAYMENTPAGE": "applicant",
    "saveDOCUMENT": "applicant",
    "start_kyc": "applicant",
    "saveA3": "applicant",
    "saveA4": "applicant",
    "saveB3": "applicant",
    # Processing (PartB) actions
    "fileValidated": "processing",
    "fileDecline": "processing",
    "fileReject": "processing",
    "cancelFile": "processing",
    "saveSendBackPage": "processing",
    "assignFile": "processing",
    # Print actions
    "printApplicantGuide": "print",
    "printApplicantFile": "print",
    "printEntirePartAButton": "print",
    # Navigation actions
    "goToNextPage": "navigation",
    "goToPreviousPage": "navigation",
    "validateCurrentTab": "navigation",
}

__all__ = [
    "componentaction_get",
    "componentaction_get_by_component",
    "componentaction_get_system_actions",
    "componentaction_save",
    "componentaction_update",
    "componentaction_delete",
    "register_action_tools",
]


async def componentaction_get(id: str, instance: str | None = None) -> dict[str, Any]:
    """Get component actions by ID.

    Args:
        id: The unique identifier of the component actions record.
        instance: Instance profile name (from instance_list).

    Returns:
        dict with id, service_id, component_key, actions.
    """
    try:
        async with BPAClient.for_instance(instance) as client:
            try:
                data = await client.get(
                    "/componentactions/{id}",
                    path_params={"id": id},
                    resource_type="componentactions",
                    resource_id=id,
                )
            except BPANotFoundError:
                raise ToolError(
                    f"ComponentActions '{id}' not found. Verify the ID is correct."
                )
    except ToolError:
        raise
    except BPAClientError as e:
        raise translate_error(e, resource_type="componentactions", resource_id=id)

    return _transform_component_actions(data)


async def componentaction_get_by_component(
    service_id: str | int, component_key: str, instance: str | None = None
) -> dict[str, Any]:
    """Get component actions for a specific form component.

    Args:
        service_id: The service containing the component.
        component_key: The form component key/identifier.
        instance: Instance profile name (from instance_list).

    Returns:
        dict with id, service_id, component_key, actions.
    """
    try:
        async with BPAClient.for_instance(instance) as client:
            try:
                data = await client.get(
                    "/service/{service_id}/componentactions/{component_key}",
                    path_params={
                        "service_id": service_id,
                        "component_key": component_key,
                    },
                    resource_type="componentactions",
                )
            except BPANotFoundError:
                raise ToolError(
                    f"No actions found for component '{component_key}' "
                    f"in service '{service_id}'."
                )
    except ToolError:
        raise
    except BPAClientError as e:
        raise translate_error(e, resource_type="componentactions")

    return _transform_component_actions(data)


def _transform_bot(bot: dict[str, Any]) -> dict[str, Any]:
    """Transform a bot object from camelCase to snake_case.

    Args:
        bot: Raw bot object from API.

    Returns:
        dict: Transformed bot with snake_case keys.
    """
    return {
        "id": bot.get("id"),
        "bot_type": bot.get("botType"),
        "name": bot.get("name"),
        "description": bot.get("description"),
        "enabled": bot.get("enabled", True),
    }


def _transform_component_actions(data: dict[str, Any]) -> dict[str, Any]:
    """Transform API response to snake_case format.

    Args:
        data: Raw API response with camelCase keys.

    Returns:
        dict: Transformed response with snake_case keys.
    """
    actions = []
    for action in data.get("actions", []):
        bots = [_transform_bot(bot) for bot in action.get("bots", [])]
        actions.append(
            {
                "id": action.get("id"),
                "json_determinants": action.get("jsonDeterminants"),
                "bots": bots,
                "sort_order": action.get("sortOrderNumber"),
                "parallel": action.get("parallel", False),
                "mandatory": action.get("mandatory", False),
                "multiple_bot": action.get("multipleBot", False),
                "multiple_field_key": action.get("multipleFieldKey"),
            }
        )

    return {
        "id": data.get("id"),
        "service_id": data.get("serviceId"),
        "component_key": data.get("componentKey"),
        "actions": actions,
    }


def _is_system_bot_id(bot_id: str) -> bool:
    """Check if a bot ID looks like a system bot (non-UUID, snake_case name)."""
    return not _UUID_PATTERN.match(bot_id)


def _validate_actions(actions: list[dict[str, Any]]) -> None:
    """Validate action dicts (shared by save and update).

    Raises:
        ToolError: If validation fails.
    """
    if not actions:
        raise ToolError(
            "'actions' must be a non-empty list. "
            "To remove all actions, use 'componentaction_delete' instead."
        )
    seen_bot_ids: set[str] = set()
    for i, action in enumerate(actions):
        if not isinstance(action, dict):
            raise ToolError(
                f"actions[{i}] must be a dict, got {type(action).__name__}."
            )

        has_bot_id = bool(action.get("bot_id"))
        has_bot_ids = "bot_ids" in action

        if has_bot_id and has_bot_ids:
            raise ToolError(
                f"actions[{i}]: 'bot_id' and 'bot_ids' are mutually exclusive. "
                "Use 'bot_id' for single-bot or 'bot_ids' for multi-bot."
            )
        if not has_bot_id and not has_bot_ids:
            raise ToolError(
                f"actions[{i}] is missing required 'bot_id' (or 'bot_ids'). "
                "Each action must specify which bot to trigger."
            )

        # Duplicate bot_id across action rows
        if has_bot_id:
            bot_id = action["bot_id"]
            if bot_id in seen_bot_ids:
                raise ToolError(
                    f"actions[{i}]: Duplicate bot_id '{bot_id}'. "
                    "Each action row must reference a unique bot."
                )
            seen_bot_ids.add(bot_id)

        if has_bot_ids:
            bot_ids = action["bot_ids"]
            if not isinstance(bot_ids, list) or len(bot_ids) == 0:
                raise ToolError(f"actions[{i}]: 'bot_ids' must be a non-empty list.")
            # Check for duplicates within bot_ids
            unique_ids = set(bot_ids)
            if len(unique_ids) != len(bot_ids):
                raise ToolError(f"actions[{i}]: 'bot_ids' contains duplicates.")

        # multi-bot validation
        multiple_bot = action.get("multiple_bot", False)
        if multiple_bot and not action.get("multiple_field_key"):
            raise ToolError(
                f"actions[{i}]: 'multiple_field_key' is required when "
                "'multiple_bot' is True."
            )

        # determinant_ids validation
        if "determinant_ids" in action:
            det_ids = action["determinant_ids"]
            if not isinstance(det_ids, list) or not all(
                isinstance(d, str) for d in det_ids
            ):
                raise ToolError(
                    f"actions[{i}]: 'determinant_ids' must be a list "
                    "of strings (determinant UUIDs)."
                )
            if len(det_ids) == 0:
                raise ToolError(f"actions[{i}]: 'determinant_ids' must not be empty.")
            logic = action.get("logic", "AND")
            if logic.upper() not in ("AND", "OR"):
                raise ToolError(
                    f"actions[{i}]: 'logic' must be 'AND' or 'OR', got {logic!r}."
                )

        if "sort_order" in action:
            sort_order = action["sort_order"]
            if not isinstance(sort_order, int) or sort_order < 0:
                raise ToolError(
                    f"actions[{i}]: sort_order must be a non-negative integer, "
                    f"got {sort_order!r}."
                )


async def _verify_bot_ids(client: Any, actions: list[dict[str, Any]]) -> None:
    """Verify all referenced bots exist. System bots (non-UUID) are warned, not failed.

    Raises:
        ToolError: If a real bot (UUID) is not found.
    """
    for i, action in enumerate(actions):
        all_ids: list[str] = []
        if action.get("bot_ids"):
            all_ids = action["bot_ids"]
        elif action.get("bot_id"):
            all_ids = [action["bot_id"]]

        for bot_id in all_ids:
            if _is_system_bot_id(bot_id):
                logger.info(
                    "actions[%d]: Bot ID '%s' appears to be a system bot, "
                    "skipping verification.",
                    i,
                    bot_id,
                )
                continue
            try:
                await client.get(
                    "/bot/{id}",
                    path_params={"id": bot_id},
                    resource_type="bot",
                    resource_id=bot_id,
                )
            except BPANotFoundError:
                raise ToolError(
                    f"actions[{i}]: Bot '{bot_id}' not found. "
                    "Use 'bot_list' to see available bots."
                )


def _build_actions_payload(actions: list[dict[str, Any]]) -> dict[str, Any]:
    """Build the camelCase API payload from action dicts.

    Returns:
        dict with 'actions' key containing the API-formatted action rows.
    """
    api_actions = []
    for i, action in enumerate(actions):
        # Build bots array
        if action.get("bot_ids"):
            bots = [{"id": bid} for bid in action["bot_ids"]]
        else:
            bots = [{"id": action["bot_id"]}]

        # Build jsonDeterminants: prefer structured determinant_ids
        json_dets = None
        if action.get("determinant_ids"):
            logic = action.get("logic", "AND")
            json_dets = build_determinant_refs(action["determinant_ids"], logic)
        elif action.get("json_determinants"):
            json_dets = action["json_determinants"]

        row: dict[str, Any] = {
            "bots": bots,
            "sortOrderNumber": action.get("sort_order", i),
            "parallel": action.get("parallel", False),
            "mandatory": action.get("mandatory", False),
            "jsonDeterminants": json_dets,
        }

        # Multi-bot flags
        if action.get("multiple_bot"):
            row["multipleBot"] = True
            row["multipleFieldKey"] = action["multiple_field_key"]

        api_actions.append(row)
    return {"actions": api_actions}


def _collect_bot_ids(actions: list[dict[str, Any]]) -> list[str]:
    """Collect all bot IDs from action dicts for audit logging."""
    ids: list[str] = []
    for action in actions:
        if action.get("bot_ids"):
            ids.extend(action["bot_ids"])
        elif action.get("bot_id"):
            ids.append(action["bot_id"])
    return ids


async def _sync_action_metadata_to_form(
    client: Any,
    service_id: str | int,
    component_key: str,
    component_action_id: str | None,
    action_row_ids: list[str],
    form_type: str = "applicant",
) -> bool:
    """Sync componentActionId and actionRowIds to form component.

    The BPA frontend updates these properties via postMessage after saving
    actions. MCP must do this explicitly so the action counter/icon A
    appears in the form builder.

    Best-effort: returns False on any error without raising.
    """
    try:
        form_config = FORM_TYPES.get(form_type)
        if not form_config:
            return False

        form_data = await client.get(
            form_config["get_endpoint"],
            path_params={"id": service_id},
            params={"reusable": "false"},
            resource_type="form",
        )
        form_schema = parse_form_schema(form_data)
        components = form_schema.get("components", [])
        comp_result = find_component(components, component_key)
        if comp_result is None:
            return False

        comp, _ = comp_result
        comp["componentActionId"] = component_action_id or ""
        comp["actionRowIds"] = action_row_ids
        form_data["formSchema"] = form_schema
        await client.put(
            form_config["put_endpoint"],
            path_params={"form_id": form_data["id"]},
            json=form_data,
            resource_type="form",
        )
        return True
    except Exception:
        logger.debug(
            "Best-effort action metadata sync failed for %s/%s",
            service_id,
            component_key,
            exc_info=True,
        )
        return False


async def componentaction_save(
    service_id: str | int,
    component_key: str,
    actions: list[dict[str, Any]],
    instance: str | None = None,
) -> dict[str, Any]:
    """Save component actions linking bots to a component. Audited write operation.

    Args:
        service_id: Parent service ID.
        component_key: Form component key to attach actions to.
        actions: List of action dicts. Each requires bot_id (str) or
            bot_ids (list[str]) for multi-bot. Optional: sort_order (int),
            parallel (bool), mandatory (bool), determinant_ids (list[str],
            preferred), logic (str, "AND"/"OR", default "AND"),
            json_determinants (str, raw fallback), multiple_bot (bool),
            multiple_field_key (str, required when multiple_bot=True).
        instance: Instance profile name (from instance_list).

    Returns:
        dict with component_key, action_count, service_id, audit_id.
    """
    # Pre-flight validation (no audit record for validation failures)
    if not service_id:
        raise ToolError("'service_id' is required.")
    if not component_key or (
        isinstance(component_key, str) and not component_key.strip()
    ):
        raise ToolError("'component_key' is required.")
    _validate_actions(actions)

    # Get authenticated user for audit
    try:
        user_email = get_current_user_email(instance=instance)
    except NotAuthenticatedError as e:
        raise ToolError(str(e))

    try:
        async with BPAClient.for_instance(instance) as client:
            # Verify service exists
            try:
                await client.get(
                    "/service/{id}",
                    path_params={"id": service_id},
                    resource_type="service",
                    resource_id=service_id,
                )
            except BPANotFoundError:
                raise ToolError(
                    f"Cannot save component actions: Service '{service_id}' not found. "
                    "Use 'service_list' to see available services."
                )

            # Verify bots exist (system bots skipped gracefully)
            await _verify_bot_ids(client, actions)

            # Fetch existing state for rollback (may not exist yet)
            existing_state = None
            try:
                existing_state = await client.get(
                    "/service/{service_id}/componentactions/{component_key}",
                    path_params={
                        "service_id": service_id,
                        "component_key": component_key,
                    },
                    resource_type="componentactions",
                )
            except BPANotFoundError:
                pass  # No existing actions — this is a create

            # Build payload
            payload = _build_actions_payload(actions)

            # Create PENDING audit record
            audit_logger = AuditLogger(db_path=resolve_db_path(instance))
            audit_id = await audit_logger.record_pending(
                user_email=user_email,
                operation_type="save",
                object_type="componentactions",
                params={
                    "service_id": str(service_id),
                    "component_key": component_key,
                    "action_count": len(actions),
                    "bot_ids": _collect_bot_ids(actions),
                    "had_existing": existing_state is not None,
                },
            )

            # Save rollback state
            await audit_logger.save_rollback_state(
                audit_id=audit_id,
                object_type="componentactions",
                object_id=f"{service_id}/{component_key}",
                previous_state=existing_state or {},
            )

            try:
                result = await client.put(
                    "/service/{service_id}/componentactions/{component_key}",
                    path_params={
                        "service_id": service_id,
                        "component_key": component_key,
                    },
                    json=payload,
                    resource_type="componentactions",
                )

                # Sync actionRowIds to form component (best-effort)
                ca_id = result.get("id")
                row_ids = [
                    a.get("id") for a in result.get("actions", []) if a.get("id")
                ]
                metadata_synced = await _sync_action_metadata_to_form(
                    client,
                    service_id,
                    component_key,
                    ca_id,
                    row_ids,
                )

                await audit_logger.mark_success(
                    audit_id,
                    result={
                        "component_key": component_key,
                        "action_count": len(actions),
                        "service_id": str(service_id),
                        "metadata_synced": metadata_synced,
                    },
                )

                return {
                    "component_key": component_key,
                    "action_count": len(actions),
                    "actions": _transform_component_actions(result).get("actions", []),
                    "service_id": str(service_id),
                    "audit_id": audit_id,
                    "metadata_synced": metadata_synced,
                }

            except BPAClientError as e:
                await audit_logger.mark_failed(audit_id, str(e))
                raise translate_error(e, resource_type="componentactions")

    except ToolError:
        raise
    except BPAClientError as e:
        raise translate_error(e, resource_type="service", resource_id=service_id)


async def componentaction_delete(
    service_id: str | int,
    component_key: str,
    instance: str | None = None,
) -> dict[str, Any]:
    """Remove all actions from a component. Audited write operation.

    Args:
        service_id: Parent service ID.
        component_key: Form component key to remove actions from.
        instance: Instance profile name (from instance_list).

    Returns:
        dict with deleted (bool), component_key, service_id, audit_id.
    """
    if not service_id:
        raise ToolError("'service_id' is required.")
    if not component_key or (
        isinstance(component_key, str) and not component_key.strip()
    ):
        raise ToolError("'component_key' is required.")

    # Get authenticated user for audit
    try:
        user_email = get_current_user_email(instance=instance)
    except NotAuthenticatedError as e:
        raise ToolError(str(e))

    try:
        async with BPAClient.for_instance(instance) as client:
            # Fetch existing state for rollback
            try:
                existing_state = await client.get(
                    "/service/{service_id}/componentactions/{component_key}",
                    path_params={
                        "service_id": service_id,
                        "component_key": component_key,
                    },
                    resource_type="componentactions",
                )
            except BPANotFoundError:
                return {
                    "deleted": False,
                    "component_key": component_key,
                    "service_id": str(service_id),
                    "message": "No actions found for this component. "
                    "Nothing to delete.",
                }

            # Create PENDING audit record
            audit_logger = AuditLogger(db_path=resolve_db_path(instance))
            audit_id = await audit_logger.record_pending(
                user_email=user_email,
                operation_type="delete",
                object_type="componentactions",
                object_id=f"{service_id}/{component_key}",
                params={
                    "service_id": str(service_id),
                    "component_key": component_key,
                },
            )

            # Save rollback state
            await audit_logger.save_rollback_state(
                audit_id=audit_id,
                object_type="componentactions",
                object_id=f"{service_id}/{component_key}",
                previous_state=existing_state,
            )

            try:
                await client.delete(
                    "/service/{service_id}/componentactions/{component_key}",
                    path_params={
                        "service_id": service_id,
                        "component_key": component_key,
                    },
                    resource_type="componentactions",
                )

                # Clear actionRowIds from form component (best-effort)
                metadata_cleared = await _sync_action_metadata_to_form(
                    client,
                    service_id,
                    component_key,
                    None,
                    [],
                )

                await audit_logger.mark_success(
                    audit_id,
                    result={
                        "deleted": True,
                        "component_key": component_key,
                        "service_id": str(service_id),
                        "metadata_cleared": metadata_cleared,
                    },
                )

                return {
                    "deleted": True,
                    "component_key": component_key,
                    "service_id": str(service_id),
                    "deleted_actions": _transform_component_actions(existing_state),
                    "metadata_cleared": metadata_cleared,
                    "audit_id": audit_id,
                }

            except BPAClientError as e:
                await audit_logger.mark_failed(audit_id, str(e))
                raise translate_error(e, resource_type="componentactions")

    except ToolError:
        raise
    except BPAClientError as e:
        raise translate_error(e, resource_type="componentactions")


async def componentaction_get_system_actions(
    instance: str | None = None,
) -> dict[str, Any]:
    """List built-in system button actions available for form components.

    Args:
        instance: Instance profile name (from instance_list).

    Returns:
        dict with actions list (id, name, category), count, note.
    """
    try:
        async with BPAClient.for_instance(instance) as client:
            data = await client.get(
                "/service/button_actions",
                resource_type="button_actions",
            )
    except BPAClientError as e:
        raise translate_error(e, resource_type="button_actions")

    # data is expected to be a list of action objects
    if not isinstance(data, list):
        logger.warning(
            "GET /service/button_actions returned %s instead of list, got: %s",
            type(data).__name__,
            str(data)[:200],
        )
    actions_list: list[dict[str, Any]] = data if isinstance(data, list) else []

    categorized: list[dict[str, str]] = []
    for action in actions_list:
        action_id = action.get("id") or action.get("name", "")
        category = _SYSTEM_ACTION_CATEGORIES.get(action_id, "other")
        categorized.append(
            {
                "id": action_id,
                "name": action.get("name", action_id),
                "category": category,
            }
        )

    return {
        "actions": categorized,
        "count": len(categorized),
        "note": (
            "System actions use their id as bot_id in componentaction_save. "
            "They do not appear in bot_list."
        ),
    }


async def componentaction_update(
    id: str,
    actions: list[dict[str, Any]],
    instance: str | None = None,
) -> dict[str, Any]:
    """Update component actions by ID. Audited write operation.

    Use when you have a componentaction ID (from componentaction_get
    or a component's componentActionId field). Replaces all action
    rows atomically.

    Args:
        id: ComponentActions record ID.
        actions: List of action dicts (same format as componentaction_save).
        instance: Instance profile name (from instance_list).

    Returns:
        dict with id, component_key, action_count, audit_id.
    """
    if not id or (isinstance(id, str) and not id.strip()):
        raise ToolError("'id' is required.")
    _validate_actions(actions)

    # Get authenticated user for audit
    try:
        user_email = get_current_user_email(instance=instance)
    except NotAuthenticatedError as e:
        raise ToolError(str(e))

    try:
        async with BPAClient.for_instance(instance) as client:
            # Verify record exists and capture for rollback
            try:
                existing_state = await client.get(
                    "/componentactions/{id}",
                    path_params={"id": id},
                    resource_type="componentactions",
                    resource_id=id,
                )
            except BPANotFoundError:
                raise ToolError(
                    f"ComponentActions '{id}' not found. Verify the ID is correct."
                )

            # Verify bots exist (system bots skipped gracefully)
            await _verify_bot_ids(client, actions)

            # Build payload
            payload = _build_actions_payload(actions)

            # Create PENDING audit record
            audit_logger = AuditLogger(db_path=resolve_db_path(instance))
            audit_id = await audit_logger.record_pending(
                user_email=user_email,
                operation_type="update",
                object_type="componentactions",
                object_id=id,
                params={
                    "id": id,
                    "action_count": len(actions),
                    "bot_ids": _collect_bot_ids(actions),
                },
            )

            # Save rollback state
            await audit_logger.save_rollback_state(
                audit_id=audit_id,
                object_type="componentactions",
                object_id=id,
                previous_state=existing_state,
            )

            try:
                result = await client.put(
                    "/componentactions/{id}",
                    path_params={"id": id},
                    json=payload,
                    resource_type="componentactions",
                )

                component_key = result.get("componentKey") or existing_state.get(
                    "componentKey", ""
                )
                update_service_id = result.get("serviceId") or existing_state.get(
                    "serviceId", ""
                )

                # Sync actionRowIds to form component (best-effort)
                metadata_synced = False
                if update_service_id and component_key:
                    ca_id = result.get("id")
                    row_ids = [
                        a.get("id") for a in result.get("actions", []) if a.get("id")
                    ]
                    metadata_synced = await _sync_action_metadata_to_form(
                        client,
                        update_service_id,
                        component_key,
                        ca_id,
                        row_ids,
                    )

                await audit_logger.mark_success(
                    audit_id,
                    result={
                        "id": id,
                        "component_key": component_key,
                        "action_count": len(actions),
                        "metadata_synced": metadata_synced,
                    },
                )

                return {
                    "id": id,
                    "component_key": component_key,
                    "action_count": len(actions),
                    "actions": _transform_component_actions(result).get("actions", []),
                    "audit_id": audit_id,
                    "metadata_synced": metadata_synced,
                }

            except BPAClientError as e:
                await audit_logger.mark_failed(audit_id, str(e))
                raise translate_error(e, resource_type="componentactions")

    except ToolError:
        raise
    except BPAClientError as e:
        raise translate_error(e, resource_type="componentactions", resource_id=id)


def register_action_tools(mcp: Any) -> None:
    """Register action tools with the MCP server.

    Args:
        mcp: The FastMCP server instance.
    """
    from mcp_eregistrations_bpa.tools.annotations import DESTRUCTIVE, READ, WRITE

    # Read operations
    mcp.tool(annotations=READ)(componentaction_get)
    mcp.tool(annotations=READ)(componentaction_get_by_component)
    mcp.tool(annotations=READ)(componentaction_get_system_actions)
    # Write operations
    mcp.tool(annotations=WRITE)(componentaction_save)
    mcp.tool(annotations=WRITE)(componentaction_update)
    mcp.tool(annotations=DESTRUCTIVE)(componentaction_delete)
