"""Protocol encoding and decoding for the Sandai operator client."""

import time
from typing import Any, Dict, List, Mapping, NoReturn, Optional, Tuple

from .exceptions import (
    InvalidResultFormatError,
    InvalidTaskStatusError,
    TaskCancelledError,
    TaskDeadlineExceededError,
    TaskExecutionError,
    TaskTimeoutError,
)
from .types import OperatorDescription, OperatorTaskResult, TaskPayloadDict


def is_timeout_error(exc: Exception) -> bool:
    """Return ``True`` when an exception clearly represents a timeout."""

    if isinstance(exc, TimeoutError):
        return True

    error_text = str(exc).lower()
    return "timeout" in error_text or "timed out" in error_text


def build_task_payload(
    input_artifacts: List[Dict[str, Any]],
    options: Dict[str, Any],
    *,
    timeout: Optional[int],
    default_timeout: int,
    cancel_key: Optional[str],
    default_cancel_key: Optional[str],
    persistent_output: bool,
) -> TaskPayloadDict:
    """Build the normalized task payload sent through Celery."""

    deadline = None
    if timeout is not None:
        deadline = time.time() + timeout
    elif default_timeout:
        deadline = time.time() + default_timeout

    task_data: TaskPayloadDict = {
        "meta": {"persistent-output": persistent_output},
        "artifacts": input_artifacts,
        "options": options,
    }
    if deadline is not None:
        task_data["deadline"] = deadline

    effective_cancel_key = cancel_key or default_cancel_key
    if effective_cancel_key:
        task_data["cancel_key"] = effective_cancel_key

    return task_data


def _normalize_error_info(error_info: Any) -> Dict[str, Any]:
    if not isinstance(error_info, Mapping):
        return {
            "error_type": "UnknownError",
            "error_message": str(error_info) if error_info else "Unknown error",
            "error_details": {},
        }

    error_details = error_info.get("error_details", {})
    if not isinstance(error_details, dict):
        error_details = {"raw_error_details": error_details}

    return {
        "error_type": error_info.get("error_type", "UnknownError"),
        "error_message": error_info.get("error_message", "Unknown error"),
        "error_details": error_details,
    }


def raise_task_error(task_id: str, error_info: Any) -> NoReturn:
    """Raise the domain exception represented by an operator error envelope."""

    normalized = _normalize_error_info(error_info)
    error_type = normalized["error_type"]
    error_message = normalized["error_message"]
    error_details = normalized["error_details"]

    if error_type == "TaskDeadlineExceededError":
        raise TaskDeadlineExceededError(
            task_id=task_id,
            deadline=error_details.get("deadline", 0),
            current_time=error_details.get("current_time", 0),
        )
    if error_type == "TaskCancelledError":
        raise TaskCancelledError(
            task_id=task_id,
            cancel_key=error_details.get("cancel_key", ""),
        )
    raise TaskExecutionError(
        task_id=task_id,
        error_message=error_message,
        error_details=error_details,
    )


def parse_task_result(task_id: str, result: Any) -> OperatorTaskResult:
    """Parse a task result envelope and raise typed errors for failure states."""

    if not isinstance(result, Mapping):
        raise InvalidResultFormatError(task_id, result)

    status = result.get("status", "unknown")
    if status == "success":
        artifacts = result.get("artifacts", [])
        results = result.get("results", {})
        return OperatorTaskResult(artifacts=artifacts, results=results)
    if status == "error":
        raise_task_error(task_id, result.get("error", {}))

    raise InvalidTaskStatusError(task_id, str(status))


def parse_desc_result(task_id: str, result: Any) -> OperatorDescription:
    """Parse an operator description response."""

    if not isinstance(result, Mapping):
        raise InvalidResultFormatError(task_id, result)

    status = result.get("status", "unknown")
    if status == "success":
        return OperatorDescription(
            doc=result.get("doc"),
            options_schema=result.get("options_schema"),
            results_schema=result.get("results_schema"),
        )
    if status == "error":
        raise_task_error(task_id, result.get("error", {}))

    raise InvalidTaskStatusError(task_id, str(status))


def raise_unexpected_task_error(task_id: str, timeout: int, exc: Exception) -> NoReturn:
    """Translate transport/runtime exceptions into client-facing exceptions."""

    if is_timeout_error(exc):
        raise TaskTimeoutError(task_id=task_id, timeout=timeout) from exc
    raise TaskExecutionError(
        task_id=task_id,
        error_message=str(exc),
        error_details={"exception_type": type(exc).__name__},
    ) from exc


def inspect_probe_result(result: Any) -> Tuple[bool, Optional[str]]:
    """Check whether a probe retrieved a recognizable operator envelope."""

    if not isinstance(result, Mapping):
        return False, f"Invalid result format: {type(result)!r}"

    status = result.get("status")
    if status == "success":
        return True, None
    if status == "error":
        normalized = _normalize_error_info(result.get("error", {}))
        return True, normalized["error_message"]
    return False, f"Invalid task status: {status!r}"
