"""DS MCP write tools for file lifecycle management.

Tools for creating, filling, submitting, processing, paying,
and cleaning up application files programmatically.
"""

from __future__ import annotations

import json
import os
from typing import Any

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

from mcp_eregistrations_ds.ds_client import DSClient, DSClientError, translate_error
from mcp_eregistrations_ds.server import mcp

# ---------------------------------------------------------------------------
# Action value mapping for file_process_action
# ---------------------------------------------------------------------------

_ACTION_MAP = {
    "approve": "filevalidated",
    "validate": "filevalidated",
    "sendback": "filedecline",
    "decline": "filedecline",
    "reject": "filereject",
}


# ---------------------------------------------------------------------------
# Task 1: file_create
# ---------------------------------------------------------------------------


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=False, idempotentHint=False))
async def file_create(
    service_id: str,
    business_entity_id: str | None = None,
    meta_data: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Create a new application file for a service.

    Args:
        service_id: UUID of the service.
        business_entity_id: UUID of business entity (default: None).
        meta_data: Optional metadata string.
        instance: Target DS instance.

    Returns:
        dict with file_id, state, service_id, service_name, created_at.
    """
    body: dict[str, Any] = {"service_id": service_id}
    if business_entity_id is not None:
        body["business_entity_id"] = business_entity_id
    if meta_data is not None:
        body["metaData"] = meta_data

    try:
        async with DSClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.post(
                "/backend/files",
                json=body,
            )
    except DSClientError as e:
        raise translate_error(e, resource_type="file") from e
    return result


# ---------------------------------------------------------------------------
# Task 2: file_set_data
# ---------------------------------------------------------------------------


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=False, idempotentHint=True))
async def file_set_data(
    file_id: str,
    data: dict[str, Any],
    form_type: str = "WIZARD",
    skip_validation: bool = True,
    instance: str | None = None,
) -> dict[str, Any]:
    """Set form field values on an application file.

    Args:
        file_id: UUID of the file.
        data: Flat dict of field key-value pairs.
        form_type: Form type URL segment (default: WIZARD).
        skip_validation: Skip FormIO validation (default: True).
        instance: Target DS instance.

    Returns:
        dict with form_type, data_content.
    """
    params: dict[str, str] = {}
    if skip_validation:
        params["ignore_formio_validation"] = "1"

    body = {"data_content": json.dumps(data)}

    try:
        async with DSClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.put(
                f"/backend/files/{file_id}/data/{form_type}",
                json=body,
                params=params,
            )
    except DSClientError as e:
        raise translate_error(
            e,
            resource_type="file",
            resource_id=file_id,
        ) from e
    return result


# ---------------------------------------------------------------------------
# Task 3: file_upload_document
# ---------------------------------------------------------------------------


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=False, idempotentHint=False))
async def file_upload_document(
    file_id: str,
    file_path: str,
    document_requirement_id: str,
    document_name: str | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Upload a document to an application file.

    Args:
        file_id: UUID of the file.
        file_path: Local path to the file to upload.
        document_requirement_id: BPA document requirement key.
        document_name: Optional display name.
        instance: Target DS instance.

    Returns:
        dict with id, file_name, file_type, document_id, url.
    """
    if not os.path.exists(file_path):
        raise ToolError(f"File not found: {file_path}")

    file_name = os.path.basename(file_path)
    with open(file_path, "rb") as f:
        file_data = f.read()

    params: dict[str, str] = {}
    if document_name:
        params["document_name"] = document_name

    try:
        async with DSClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.upload(
                f"/backend/files/{file_id}/document",
                file_data=file_data,
                file_name=file_name,
                field_name="file",
                extra_fields={"name": document_requirement_id},
                params=params or None,
            )
    except DSClientError as e:
        raise translate_error(
            e,
            resource_type="document",
            resource_id=file_id,
        ) from e
    return result


# ---------------------------------------------------------------------------
# Task 4: file_submit
# ---------------------------------------------------------------------------


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=False, idempotentHint=False))
async def file_submit(
    file_id: str,
    data: dict[str, Any] | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Submit an application file — starts the Camunda process.

    File must have required data/documents set. Services with
    digital payment require file_pay before submit.

    Args:
        file_id: UUID of the file to submit.
        data: Optional final form data.
        instance: Target DS instance.

    Returns:
        dict with file_id, state, process_instance_id, service_id.
    """
    body = {"data_content": json.dumps(data or {})}

    try:
        async with DSClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.post(
                f"/backend/files/{file_id}/start_process",
                json=body,
            )
    except DSClientError as e:
        raise translate_error(
            e,
            resource_type="file",
            resource_id=file_id,
        ) from e
    return result


# ---------------------------------------------------------------------------
# Task 5: file_process_action
# ---------------------------------------------------------------------------


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=False, idempotentHint=False))
async def file_process_action(
    process_id: str,
    role_name: str,
    action: str,
    form_data: dict[str, Any] | None = None,
    form_type: str = "WIZARD",
    dry_run: bool = False,
    instance: str | None = None,
) -> dict[str, Any]:
    """Process a file at a desk — approve, send back, or reject.

    For resubmit after sendback: role_name="applicant", action="approve".
    Services with payment require completed payment before approve.

    Args:
        process_id: Camunda process instance ID.
        role_name: Desk/role name (e.g. "technicalEvaluation").
        action: "approve", "sendback", or "reject".
        form_data: Optional desk-specific field values.
        form_type: Form type URL segment (default: WIZARD).
        dry_run: Save data without completing task (default: False).
        instance: Target DS instance.

    Returns:
        dict with processing result.
    """
    action_value = _ACTION_MAP.get(action)
    if not action_value:
        raise ToolError(
            f"Invalid action '{action}'. Use: {', '.join(_ACTION_MAP.keys())}"
        )

    data = dict(form_data or {})
    data[f"{role_name}selection"] = action_value

    body: dict[str, Any] = {"data": data}
    path = f"/backend/process/{process_id}/role-forms/{role_name}/{form_type}"

    try:
        async with DSClient.for_instance(instance) as client:
            if dry_run:
                result: dict[str, Any] = await client.patch(
                    path,
                    json=body,
                )
            else:
                result = await client.put(path, json=body)
    except DSClientError as e:
        raise translate_error(
            e,
            resource_type="process",
            resource_id=process_id,
        ) from e
    return result


# ---------------------------------------------------------------------------
# Task 6: file_pay
# ---------------------------------------------------------------------------


@mcp.tool(annotations=ToolAnnotations(readOnlyHint=False, idempotentHint=False))
async def file_pay(
    file_id: str,
    provider_code: str,
    provider_data: dict[str, Any] | None = None,
    instance: str | None = None,
) -> dict[str, Any]:
    """Create a payment transaction for a file.

    Args:
        file_id: UUID of the file to pay for.
        provider_code: Payment provider (e.g. "pagadito", "fedapay").
        provider_data: Optional provider-specific data.
        instance: Target DS instance.

    Returns:
        dict with transaction_id, total_cost, cost_currency, status.
    """
    body = {
        "data": {"fileId": file_id},
        "providerData": provider_data or {},
    }

    try:
        async with DSClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.post(
                f"/backend/payment_transaction/{provider_code}",
                json=body,
            )
    except DSClientError as e:
        raise translate_error(
            e,
            resource_type="payment",
            resource_id=file_id,
        ) from e
    return result


# ---------------------------------------------------------------------------
# Task 7: file_restart_process
# ---------------------------------------------------------------------------


@mcp.tool(
    annotations=ToolAnnotations(
        readOnlyHint=False,
        destructiveHint=True,
        idempotentHint=False,
    )
)
async def file_jump_to_role(
    process_definition_id: str,
    process_instance_id: str,
    target_role: str,
    instance: str | None = None,
) -> dict[str, Any]:
    """Jump a live process to resume after a specific role.

    Cancels current tasks and creates new ones after target_role.
    Process must be active (not ended). Use service_roles to find
    role IDs, process_get to find process/definition IDs.

    Requires superuser, superinspector, or ds_file_manage_any.

    Args:
        process_definition_id: Camunda process definition ID.
        process_instance_id: Camunda process instance ID.
        target_role: Role ID to jump to (e.g. "revision", "role1A").
        instance: Target DS instance.

    Returns:
        dict with modification result.
    """
    data: dict[str, Any] = {
        "ended": False,
        "instructions": [
            {"type": "startAfterActivity", "activityId": target_role},
        ],
        "processInstanceIds": [process_instance_id],
        "initialVariables": False,
        "skipCustomListeners": False,
        "withoutBusinessKey": False,
    }

    try:
        async with DSClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.post(
                f"/api/v1/restart-process/{process_definition_id}/",
                json={"data": data},
            )
    except DSClientError as e:
        raise translate_error(
            e,
            resource_type="process",
            resource_id=process_definition_id,
        ) from e
    return result


# ---------------------------------------------------------------------------
# Task 8: file_delete_process
# ---------------------------------------------------------------------------


@mcp.tool(
    annotations=ToolAnnotations(
        readOnlyHint=False,
        destructiveHint=True,
    )
)
async def file_delete_process(
    process_instance_id: str,
    instance: str | None = None,
) -> dict[str, Any]:
    """Delete a file and its process — DESTRUCTIVE, removes all data.

    Deletes DS file, documents, certificates AND Camunda process.
    Requires superinspector or ds_file_manage_any permission.

    Args:
        process_instance_id: Camunda process instance ID.
        instance: Target DS instance.

    Returns:
        Empty dict on success.
    """
    try:
        async with DSClient.for_instance(instance) as client:
            result: dict[str, Any] = await client.delete(
                f"/api/v1/delete-process/{process_instance_id}/",
            )
    except DSClientError as e:
        raise translate_error(
            e,
            resource_type="process",
            resource_id=process_instance_id,
        ) from e
    return result
