"""ECM exception hierarchy and BlueException translation.

All ECM-level exceptions raised by the high-level object API.

``raise_for_blue_exception`` is the single conversion point between the
RPC-layer :class:`~ecmind_blue_client.rpc.BlueException` and the ECM
exception hierarchy.  It maps each known DMS result code to one of the
five base exception classes and prepends a human-readable description to
the server error message.
"""

from ecmind_blue_client.rpc._blue_exception import BlueException

__all__ = [
    "ECMException",
    "ECMNotFoundException",
    "ECMWrongStateException",
    "ECMAccessDeniedException",
    "ECMMissingArgumentException",
    "raise_for_blue_exception",
]


# ---------------------------------------------------------------------------
# Exception hierarchy
# ---------------------------------------------------------------------------


class ECMException(Exception):
    """Base class for all ECM-level exceptions."""

    def __init__(self, message: str) -> None:
        super().__init__(message)


class ECMNotFoundException(ECMException):
    """Requested ECM object or resource does not exist."""


class ECMWrongStateException(ECMException):
    """Invalid state or operation on an ECM object."""


class ECMAccessDeniedException(ECMException):
    """Access to an ECM resource was denied."""


class ECMMissingArgumentException(ECMException):
    """A required argument or identifier was not provided.

    Raised when a mandatory parameter such as a folder ID, register ID,
    document ID, or object type is missing from the request.
    """


# ---------------------------------------------------------------------------
# Result code → (exception class, description) mapping
# ---------------------------------------------------------------------------

_RESULT_CODE_INFO: dict[int, tuple[type[ECMException], str]] = {
    # generic errors
    -1: (ECMException, "General error (not further specifiable)"),
    -2: (ECMMissingArgumentException, "No cabinet specified"),
    -4: (ECMMissingArgumentException, "No register type specified"),
    -6: (ECMMissingArgumentException, "No document type specified"),
    -10: (ECMMissingArgumentException, "Required folder identification is missing"),
    -11: (ECMMissingArgumentException, "Required document identification is missing"),
    -12: (ECMMissingArgumentException, "Required register identification is missing"),
    -16: (ECMException, "Folder update failed"),
    -17: (ECMException, "Document update failed"),
    -18: (ECMException, "Register update failed"),
    -24: (ECMException, "The field name could not be resolved"),
    -28: (ECMException, "The value for the given field is not allowed"),
    -29: (ECMException, "The specified object ID is invalid"),
    -30: (ECMException, "The mandatory fields have not been filled in"),
    -31: (ECMException, "The specified value does not match the type on the archive server"),
    -40: (ECMException, "A transfer parameter is incorrect or missing"),
    -65: (ECMException, "No index could be obtained from the server"),
    -1004: (ECMException, "No document list was specified"),
    -1006: (ECMException, "The requested functionality is not implemented in the current version"),
    -1007: (ECMException, "An error occurred while reading the object definitions"),
    -1013: (ECMException, "A system ID was specified but no foreign ID"),
    -1016: (ECMException, "An error occurred while parsing the XML text"),
    -1017: (ECMException, "An error occurred while validating the XML text"),
    -1018: (ECMException, "The XML text is incomplete"),
    -1024: (ECMException, "A job parameter is missing"),
    -1025: (ECMException, "The specified parameter value is invalid"),
    -1027: (ECMException, "More values were specified for a table field than there are columns"),
    -1028: (ECMException, "An error occurred while inserting the note objects/texts"),
    -1029: (ECMException, "An XML element is unknown"),
    -1032: (ECMException, "The job was cancelled by user request"),
    -1033: (ECMException, "The specified condition is invalid"),
    -1034: (ECMException, "An XML attribute is invalid"),
    -1035: (ECMException, "A required XML attribute is missing"),
    -1047: (ECMException, "The user data specifications are ambiguous"),
    -1051: (ECMException, "The query format is not supported"),
    -1055: (ECMException, "An expression has an invalid format"),
    -1056: (ECMException, "The section name is invalid"),
    -1057: (ECMException, "General error while reading the query"),
    -1058: (ECMException, "Unknown query type"),
    -1060: (ECMException, "The query could not be processed"),
    -1067: (ECMException, "The full-text query contains only words that are to be ignored"),
    -1068: (ECMException, "Insufficient memory is available"),
    # not found
    -3: (ECMNotFoundException, "Specified cabinet unknown"),
    -5: (ECMNotFoundException, "Specified register type unknown"),
    -7: (ECMNotFoundException, "Specified document type unknown"),
    -13: (ECMNotFoundException, "Required register identification is unknown"),
    -14: (ECMNotFoundException, "Required folder identification is unknown"),
    -15: (ECMNotFoundException, "Required document identification is unknown"),
    -22: (ECMNotFoundException, "The specified/required object is unknown"),
    -23: (ECMNotFoundException, "The ID of the specified register does not exist on the archive server"),
    -32: (ECMNotFoundException, "The specified/required file does not exist"),
    -1001: (ECMNotFoundException, "The specified value could not be found in the associated catalog"),
    -1019: (ECMNotFoundException, "The specified owner could not be determined"),
    -1022: (ECMNotFoundException, "The reference was not found"),
    -1030: (ECMNotFoundException, "An object type was not found on the DMS server"),
    -1031: (ECMNotFoundException, "An object field was not found on the DMS server"),
    -1044: (ECMNotFoundException, "The parent variant of the document could not be determined"),
    -1046: (ECMNotFoundException, "The document variant could not be determined"),
    -1049: (ECMNotFoundException, "The query was not found"),
    -1052: (ECMNotFoundException, "The cabinet name could not be read"),
    -1053: (ECMNotFoundException, "The document name could not be read"),
    -1054: (ECMNotFoundException, "The register name could not be read"),
    -1062: (ECMNotFoundException, "The location of the object could not be determined"),
    # wrong state / invalid operation
    -8: (ECMWrongStateException, "The specified register is not in the specified folder"),
    -9: (ECMWrongStateException, "The specified document type is not allowed in the specified cabinet"),
    -21: (ECMWrongStateException, "The document has already been archived"),
    -51: (ECMWrongStateException, "The document has no pages"),
    -68: (ECMWrongStateException, "Moving folders is not allowed"),
    -89: (ECMWrongStateException, "The relationship between document and register is not allowed"),
    -90: (ECMWrongStateException, "Reference documents cannot be moved without specifying a location"),
    -94: (ECMWrongStateException, "Document pages are not allowed"),
    -1002: (ECMWrongStateException, "The key field is not unique"),
    -1005: (ECMWrongStateException, "The specified object is connected to a workflow process"),
    -1009: (ECMWrongStateException, "The object is in the recycle bin"),
    -1010: (ECMWrongStateException, "The document is in a portfolio"),
    -1011: (ECMWrongStateException, "The recursion depth is too large, the action was aborted"),
    -1012: (ECMWrongStateException, "The target register is a child of the register to be moved"),
    -1014: (ECMWrongStateException, "Pages cannot be added to a reference document"),
    -1015: (ECMWrongStateException, "The document cannot reference another document because it has pages"),
    -1020: (ECMWrongStateException, "The object type is invalid for this operation"),
    -1021: (ECMWrongStateException, "Cascade deletion is not allowed, the object still has child objects"),
    -1036: (ECMWrongStateException, "Only documents may be checked out"),
    -1037: (ECMWrongStateException, "The document has been checked in"),
    -1038: (ECMWrongStateException, "The document has been checked out"),
    -1039: (ECMWrongStateException, "The document has been checked out to another user"),
    -1040: (ECMWrongStateException, "The document has been checked out to another station"),
    -1041: (ECMWrongStateException, "The document cannot be checked out because it has no pages"),
    -1042: (ECMWrongStateException, "The document is not in the workflow storage"),
    -1043: (ECMWrongStateException, "The document is in the workflow storage"),
    -1045: (ECMWrongStateException, "New variants may only be created for W-documents"),
    -1048: (ECMWrongStateException, "A concurrent update of an object failed"),
    -1050: (ECMWrongStateException, "The copy of the object already exists at the specified location"),
    -1059: (ECMWrongStateException, "The new variant was created but could not be set to 'Active'"),
    -1061: (ECMWrongStateException, "Moving or linking objects is locked system-wide"),
    -1063: (ECMWrongStateException, "The document already exists at the location"),
    -1064: (ECMWrongStateException, "Copying objects with key fields is not allowed"),
    -1070: (ECMWrongStateException, "In the dms.XMLImport job, 'Error' was specified as the action for the hit count"),
    # access denied
    -47: (ECMAccessDeniedException, "The user does not have the required rights on the archive server"),
    -1003: (ECMAccessDeniedException, "An attempt was made to set a read-only field"),
    -1008: (ECMAccessDeniedException, "A requested file could not be accessed"),
    -1023: (ECMAccessDeniedException, "The specified system field cannot be changed by the user"),
    -1026: (ECMAccessDeniedException, "It is not possible for the user to disable the rights check"),
    -1065: (ECMAccessDeniedException, "The specified password is incorrect"),
    -1066: (ECMAccessDeniedException, "The new and old passwords are identical"),
    -1069: (ECMAccessDeniedException, "Access to a system resource was denied"),
}


def raise_for_blue_exception(exc: BlueException) -> None:
    """Translate a :class:`~ecmind_blue_client.rpc.BlueException` into the matching ECM exception.

    Looks up the return code from *exc* in the result code map and raises the
    corresponding :class:`ECMException` subclass with the description
    prepended to the server error message (``"<description>: <api_message>"``).
    If the return code is not in the map the function returns without raising —
    the caller is responsible for re-raising the original exception.

    Args:
        exc: The :class:`~ecmind_blue_client.rpc.BlueException` to translate.

    Raises:
        ECMException: The mapped subclass for ``exc.return_code``, if a
            mapping exists.
    """
    info = _RESULT_CODE_INFO.get(exc.return_code)
    if info is not None:
        exc_class, description = info
        raise exc_class(f"{description}: {exc.message}") from exc
