from __future__ import annotations

from typing import Any

from fastmcp.exceptions import ToolError

from mcp_eregistrations_smartlink.audit.logger import SlAuditLogger
from mcp_eregistrations_smartlink.smartlink_client import SmartLinkClient

ROLLBACK_ENDPOINTS: dict[str, str] = {
    "flow": "/flows/{id}",
    "action": "/actions/{id}",
}

# Fields accepted by UpdateFlowRequest (writable only)
_FLOW_WRITABLE_FIELDS = {
    "name",
    "description",
    "sourceEndpoint",
    "httpMethod",
    "authRequired",
    "failFast",
    "mergeResponses",
    "serviceId",
    "publishId",
    "requestTransform",
    "enabled",
    "actions",
}

# Fields accepted by UpdateActionRequest (writable only)
_ACTION_WRITABLE_FIELDS = {
    "name",
    "description",
    "targetUrl",
    "httpMethod",
    "requestTransform",
    "responseTransform",
    "headers",
    "authType",
    "authConfig",
    "requestContentType",
    "targetUrlTransform",
    "runTargetRequest",
    "enabled",
}

# Fields accepted by FlowActionRequest
_FLOW_ACTION_REQUEST_FIELDS = {
    "actionId",
    "actionOrder",
    "determinantTransform",
    "parallelToPrevious",
    "mandatory",
    "iterateOverField",
}


def _sanitize_flow_for_update(flow_response: dict[str, Any]) -> dict[str, Any]:
    """Transform a FlowResponse into an UpdateFlowRequest-compatible dict.

    Strips read-only fields (id, status, createdAt, updatedAt) and converts
    FlowActionResponse[] into FlowActionRequest[] shape.
    """
    result = {k: v for k, v in flow_response.items() if k in _FLOW_WRITABLE_FIELDS}

    # Transform actions: FlowActionResponse[] → FlowActionRequest[]
    if "actions" in flow_response and flow_response["actions"]:
        result["actions"] = [_sanitize_flow_action(a) for a in flow_response["actions"]]

    return result


def _sanitize_flow_action(action_response: dict[str, Any]) -> dict[str, Any]:
    """Transform a FlowActionResponse into a FlowActionRequest-compatible dict.

    Extracts actionId from the nested action object and keeps only
    FlowActionRequest fields.
    """
    result: dict[str, Any] = {}

    # Extract actionId: prefer top-level, fall back to nested action.id
    action_id = action_response.get("actionId")
    if not action_id:
        nested_action = action_response.get("action")
        if isinstance(nested_action, dict):
            action_id = nested_action.get("id")
    if action_id:
        result["actionId"] = action_id

    # Copy FlowActionRequest-compatible fields
    for field in _FLOW_ACTION_REQUEST_FIELDS:
        if field in action_response and field != "actionId":
            result[field] = action_response[field]

    return result


def _sanitize_action_for_update(
    action_response: dict[str, Any],
) -> dict[str, Any]:
    """Transform an ActionResponse into an UpdateActionRequest-compatible dict.

    Strips read-only fields (id, status, createdAt, updatedAt).
    """
    return {k: v for k, v in action_response.items() if k in _ACTION_WRITABLE_FIELDS}


async def execute_rollback(
    client: SmartLinkClient,
    audit_logger: SlAuditLogger,
    audit_id: str,
) -> dict[str, Any]:
    """Execute a rollback for a given audit entry.

    Only supports reversing 'update' operations by PUT-ing previous state.
    Sanitizes the stored response to match the update DTO contract.
    """
    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"]

    if operation != "update":
        raise ToolError(
            f"Rollback only supports 'update' operations, got '{operation}'."
        )

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

    # Sanitize: strip read-only fields and transform response→request shape
    if object_type == "flow":
        sanitized = _sanitize_flow_for_update(previous)
    elif object_type == "action":
        sanitized = _sanitize_action_for_update(previous)
    else:
        raise ToolError(f"Unknown object type '{object_type}'.")

    object_id = state.get("object_id") or entry.get("object_id")
    path = endpoint_template.format(id=object_id)
    await client.put(path, data=sanitized)

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