"""MCP tools for BPA role status operations.

Role statuses define workflow transition states for roles (e.g., FILE VALIDATED,
SEND BACK). They control where applications go next in the workflow.

Write operations follow the audit-before-write pattern.

API Endpoints used:
- GET /role_status/{role_status_id} - Get role status by ID
- POST /role/{role_id}/role_status/user_defined_status - Create user-defined status
- PUT /role_status/{id}/user_defined_status - Update user-defined status
- PUT /role_status/{id}/file_validated_status - Update file-validated status
- PUT /role_status/{id}/file_reject_status - Update file-reject status
- PUT /role_status/{id}/file_pending_status - Update file-pending status
- PUT /role_status/{id}/file_decline_status - Update file-decline status
- DELETE /role_status/{role_status_id} - Delete role status
"""

from __future__ import annotations

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

__all__ = [
    "rolestatus_get",
    "rolestatus_create",
    "rolestatus_update",
    "rolestatus_delete",
    "register_role_status_tools",
]

_STATUS_TYPE_TO_ENDPOINT: dict[str, str] = {
    "UserDefinedStatus": "user_defined_status",
    "FileValidatedStatus": "file_validated_status",
    "FileRejectStatus": "file_reject_status",
    "FilePendingStatus": "file_pending_status",
    "FileDeclineStatus": "file_decline_status",
}


def _get_status_endpoint_suffix(status_data: dict[str, Any]) -> str:
    """Determine correct API endpoint suffix for a role status type."""
    role_status_type = status_data.get("roleStatusType", "")
    if isinstance(role_status_type, str):
        suffix = _STATUS_TYPE_TO_ENDPOINT.get(role_status_type)
        if suffix:
            return suffix
    raise ToolError(
        f"Unknown role status type: '{role_status_type}'. "
        f"Known types: {', '.join(_STATUS_TYPE_TO_ENDPOINT.keys())}. "
        "This may be a new status type not yet supported."
    )


def _transform_role_status_response(data: dict[str, Any]) -> dict[str, Any]:
    """Transform role status API response from camelCase to snake_case.

    Args:
        data: Raw API response with camelCase keys.

    Returns:
        dict: Transformed response with snake_case keys.
    """
    return {
        "id": data.get("id"),
        "name": data.get("name"),
        "type": data.get("type"),
        "role_status_type": data.get("roleStatusType"),
        "destination_id": data.get("destinationId"),
        "destinations": [
            {
                "id": d.get("id"),
                "destination_id": d.get("destinationId"),
                "sort_order": d.get("sortOrder"),
            }
            for d in data.get("destinations", [])
            if isinstance(d, dict)
        ],
        "role_id": data.get("roleId"),
        # Role status message (for notifications)
        "role_status_message": data.get("roleStatusMessage"),
        # Audit fields
        "created_by": data.get("createdBy"),
        "created_when": data.get("createdWhen"),
        "last_changed_by": data.get("lastChangedBy"),
        "last_changed_when": data.get("lastChangedWhen"),
    }


async def rolestatus_get(
    role_status_id: str | int, instance: str | None = None
) -> dict[str, Any]:
    """Get details of a BPA role status by ID.

    Role statuses define workflow transitions (e.g., FILE VALIDATED, SEND BACK).

    Args:
        role_status_id: The unique identifier of the role status.
        instance: Instance profile name (from instance_list).

    Returns:
        dict with id, name, type, role_status_type, destination_id, role_id.
    """
    if not role_status_id:
        raise ToolError(
            "Cannot get role status: 'role_status_id' is required. "
            "Use 'role_get' to see statuses for a role."
        )

    try:
        async with BPAClient.for_instance(instance) as client:
            try:
                status_data = await client.get(
                    "/role_status/{role_status_id}",
                    path_params={"role_status_id": role_status_id},
                    resource_type="role_status",
                    resource_id=role_status_id,
                )
            except BPANotFoundError:
                raise ToolError(
                    f"Role status '{role_status_id}' not found. "
                    "Use 'role_get' with a role_id to see available statuses."
                )
    except ToolError:
        raise
    except BPAClientError as e:
        raise translate_error(
            e, resource_type="role_status", resource_id=role_status_id
        )

    return _transform_role_status_response(status_data)


async def rolestatus_create(
    role_id: str | int,
    name: str,
    destination_role_ids: list[str] | None = None,
    message: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Create a user-defined role status. Audited write operation.

    Role statuses control workflow transitions (e.g., forward to next role,
    return to applicant). Always creates UserDefined type (roleStatusType=4).

    Args:
        role_id: Role to add status to.
        name: Status name (e.g., "APPROVED", "SEND BACK").
        destination_role_ids: Optional list of target role IDs for transitions.
        message: Optional notification message for this status.
        instance: Instance profile name (from instance_list).

    Returns:
        dict with id, name, role_status_type, destinations, role_id, audit_id.
    """
    # Pre-flight validation
    if not role_id:
        raise ToolError(
            "Cannot create role status: 'role_id' is required. "
            "Use 'role_list' with service_id to find valid role IDs."
        )
    if not name or not name.strip():
        raise ToolError("Role status name is required.")

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

    # Build payload — always UserDefined (type 4)
    payload: dict[str, Any] = {
        "name": name.strip(),
        "roleStatusType": 4,
    }
    if destination_role_ids:
        payload["destinations"] = [
            {"destinationId": str(rid)} for rid in destination_role_ids
        ]
    if message:
        payload["roleStatusMessage"] = message

    try:
        async with BPAClient.for_instance(instance) as client:
            # Verify role exists
            try:
                await client.get(
                    "/role/{role_id}",
                    path_params={"role_id": role_id},
                    resource_type="role",
                    resource_id=role_id,
                )
            except BPANotFoundError:
                raise ToolError(
                    f"Role '{role_id}' not found. "
                    "Use 'role_list' with service_id to see available roles."
                )

            # Create audit record BEFORE API call
            audit_logger = AuditLogger(db_path=resolve_db_path(instance))
            audit_id = await audit_logger.record_pending(
                user_email=user_email,
                operation_type="create",
                object_type="role_status",
                params={"role_id": str(role_id), **payload},
            )

            try:
                status_data = await client.post(
                    "/role/{role_id}/role_status/user_defined_status",
                    path_params={"role_id": role_id},
                    json=payload,
                    resource_type="role_status",
                )

                # Save rollback state
                created_id = status_data.get("id")
                await audit_logger.save_rollback_state(
                    audit_id=audit_id,
                    object_type="role_status",
                    object_id=str(created_id),
                    previous_state={
                        "id": created_id,
                        "role_id": str(role_id),
                        "name": status_data.get("name"),
                        "_operation": "create",
                    },
                )

                await audit_logger.mark_success(
                    audit_id=audit_id,
                    result={"id": created_id, "name": status_data.get("name")},
                )

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

    except ToolError:
        raise
    except BPAClientError as e:
        raise translate_error(e, resource_type="role", resource_id=role_id)

    result = _transform_role_status_response(status_data)
    result["audit_id"] = audit_id
    return result


async def rolestatus_update(
    role_status_id: str | int,
    name: str | None = None,
    destination_role_ids: list[str] | None = None,
    message: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Update any role status (user-defined or system). Audited write operation.

    Routes to the correct type-specific PUT endpoint based on roleStatusType.

    Args:
        role_status_id: Role status ID to update.
        name: New status name (optional).
        destination_role_ids: New list of target role IDs (optional).
        message: New notification message (optional).
        instance: Instance profile name (from instance_list).

    Returns:
        dict with id, name, role_status_type, destinations, previous_state,
        audit_id.
    """
    # Pre-flight validation
    if not role_status_id:
        raise ToolError(
            "Cannot update role status: 'role_status_id' is required. "
            "Use 'role_get' to see statuses for a role."
        )

    # At least one field must be provided
    if name is None and destination_role_ids is None and message is None:
        raise ToolError("At least one field must be provided for update.")

    # 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:
            # Capture current state for rollback
            try:
                previous_state = await client.get(
                    "/role_status/{role_status_id}",
                    path_params={"role_status_id": role_status_id},
                    resource_type="role_status",
                    resource_id=role_status_id,
                )
            except BPANotFoundError:
                raise ToolError(
                    f"Role status '{role_status_id}' not found. "
                    "Use 'role_get' to see available statuses."
                )

            # Determine correct endpoint based on status type
            endpoint_suffix = _get_status_endpoint_suffix(previous_state)

            # Merge with current state
            payload: dict[str, Any] = {
                "id": role_status_id,
                "name": name.strip() if name else previous_state.get("name"),
                "roleStatusType": previous_state.get("roleStatusType"),
            }
            # Use destinations array instead of singular destinationId
            if destination_role_ids is not None:
                payload["destinations"] = [
                    {"destinationId": str(rid)} for rid in destination_role_ids
                ]
            elif previous_state.get("destinations"):
                payload["destinations"] = previous_state["destinations"]

            if message is not None:
                payload["roleStatusMessage"] = message
            elif previous_state.get("roleStatusMessage"):
                payload["roleStatusMessage"] = previous_state.get("roleStatusMessage")

            # Create 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="role_status",
                object_id=str(role_status_id),
                params={"changes": payload},
            )

            # Save rollback state
            await audit_logger.save_rollback_state(
                audit_id=audit_id,
                object_type="role_status",
                object_id=str(role_status_id),
                previous_state=previous_state,
            )

            try:
                await client.put(
                    f"/role_status/{{role_status_id}}/{endpoint_suffix}",
                    path_params={"role_status_id": role_status_id},
                    json=payload,
                    resource_type="role_status",
                    resource_id=role_status_id,
                )

                # PUT returns empty body — re-fetch updated state
                status_data = await client.get(
                    "/role_status/{role_status_id}",
                    path_params={"role_status_id": role_status_id},
                    resource_type="role_status",
                    resource_id=role_status_id,
                )

                await audit_logger.mark_success(
                    audit_id=audit_id,
                    result={
                        "id": status_data.get("id"),
                        "name": status_data.get("name"),
                    },
                )

            except BPAClientError as e:
                await audit_logger.mark_failed(audit_id=audit_id, error_message=str(e))
                raise translate_error(
                    e, resource_type="role_status", resource_id=role_status_id
                )

    except ToolError:
        raise
    except BPAClientError as e:
        raise translate_error(
            e, resource_type="role_status", resource_id=role_status_id
        )

    result = _transform_role_status_response(status_data)
    result["previous_state"] = _transform_role_status_response(previous_state)
    result["audit_id"] = audit_id
    return result


async def rolestatus_delete(
    role_status_id: str | int, instance: str | None = None
) -> dict[str, Any]:
    """Delete a role status. Audited write operation.

    Args:
        role_status_id: Role status ID to delete.
        instance: Instance profile name (from instance_list).

    Returns:
        dict with deleted (bool), role_status_id, deleted_status, audit_id.
    """
    if not role_status_id:
        raise ToolError(
            "Cannot delete role status: 'role_status_id' is required. "
            "Use 'role_get' to see statuses for a role."
        )

    # 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:
            # Capture current state for rollback
            try:
                previous_state = await client.get(
                    "/role_status/{role_status_id}",
                    path_params={"role_status_id": role_status_id},
                    resource_type="role_status",
                    resource_id=role_status_id,
                )
            except BPANotFoundError:
                raise ToolError(
                    f"Role status '{role_status_id}' not found. "
                    "Use 'role_get' to see available statuses."
                )

            # Guard: refuse to delete system statuses
            system_status_types = {
                "FileValidatedStatus",
                "FilePendingStatus",
                "FileDeclineStatus",
                "FileRejectStatus",
            }
            status_type = previous_state.get("roleStatusType", "")
            if isinstance(status_type, str) and status_type in system_status_types:
                status_name = previous_state.get("name", status_type)
                raise ToolError(
                    f"Cannot delete system status '{status_name}' "
                    f"(type: {status_type}). System statuses (FILE PENDING, "
                    "FILE VALIDATED, SEND BACK TO CORRECTIONS, FILE REJECT) "
                    "are required for the BPA workflow and cannot be removed. "
                    "Only user-defined statuses can be deleted. "
                    "Use rolestatus_update to modify status destinations."
                )

            # Create 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="role_status",
                object_id=str(role_status_id),
                params={},
            )

            # Save rollback state
            await audit_logger.save_rollback_state(
                audit_id=audit_id,
                object_type="role_status",
                object_id=str(role_status_id),
                previous_state=previous_state,
            )

            try:
                await client.delete(
                    "/role_status/{role_status_id}",
                    path_params={"role_status_id": role_status_id},
                    resource_type="role_status",
                    resource_id=role_status_id,
                )

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

            except BPAClientError as e:
                await audit_logger.mark_failed(audit_id=audit_id, error_message=str(e))
                raise translate_error(
                    e, resource_type="role_status", resource_id=role_status_id
                )

    except ToolError:
        raise
    except BPAClientError as e:
        raise translate_error(
            e, resource_type="role_status", resource_id=role_status_id
        )

    return {
        "deleted": True,
        "role_status_id": str(role_status_id),
        "deleted_status": _transform_role_status_response(previous_state),
        "audit_id": audit_id,
    }


def register_role_status_tools(mcp: Any) -> None:
    """Register role status tools with the MCP server.

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

    mcp.tool(annotations=READ)(rolestatus_get)
    mcp.tool(annotations=WRITE)(rolestatus_create)
    mcp.tool(annotations=WRITE)(rolestatus_update)
    mcp.tool(annotations=DESTRUCTIVE)(rolestatus_delete)
