"""IBM MQ bindings models for AsyncAPI 3.0 specification."""

__all__ = [
    "IBMMQChannelBindings",
    "IBMMQMessageBindings",
    "IBMMQOperationBindings",
    "IBMMQQueue",
    "IBMMQServerBindings",
    "IBMMQTopic",
]

from typing import Literal

from pydantic import Field, field_validator, model_validator

from asyncapi3.models.base_models import NonExtendableBaseModel
from asyncapi3.models.helpers import is_null


# TODO: Depends on parent object
class IBMMQServerBindings(NonExtendableBaseModel):
    """
    IBM MQ Server Binding Object.

    This object contains server connection information about the IBM MQ server, referred
    to as an IBM MQ queue manager. This object contains additional connectivity
    information not possible to represent within the core AsyncAPI specification.

    This object MUST contain only the properties defined below.
    """

    # TODO: MUST NOT be specified for URI Scheme http:// or file://
    group_id: str | None = Field(
        default=None,
        exclude_if=is_null,
        alias="groupId",
        description=(
            "Defines a logical group of IBM MQ server objects. This is necessary to "
            "specify multi-endpoint configurations used in high availability "
            "deployments. If omitted, the server object is not part of a group. MUST "
            "NOT be specified for URI Scheme http:// or file://."
        ),
    )
    # TODO: MUST NOT be specified for URI Scheme ibmmq://
    ccdt_queue_manager_name: str | None = Field(
        default=None,
        exclude_if=is_null,
        alias="ccdtQueueManagerName",
        description=(
            "The name of the IBM MQ queue manager to bind to in the CCDT file. "
            "Optional, defaults to *. MUST NOT be specified for URI Scheme ibmmq://."
        ),
    )
    # TODO: MUST NOT be specified for protocol ibmmq or URI Scheme file:// or http://
    cipher_spec: str | None = Field(
        default=None,
        exclude_if=is_null,
        alias="cipherSpec",
        description=(
            "The recommended cipher specification used to establish a TLS connection "
            "between the client and the IBM MQ queue manager. More information on "
            "SSL/TLS cipher specifications supported by IBM MQ can be found in the "
            "IBM MQ Knowledge Center. Optional, defaults to ANY. MUST NOT be specified "
            "for protocol ibmmq or URI Scheme file:// or http://."
        ),
    )
    # TODO: MUST NOT be specified for URI Scheme file:// or http://
    multi_endpoint_server: bool | None = Field(
        default=None,
        exclude_if=is_null,
        alias="multiEndpointServer",
        description=(
            "If multiEndpointServer is true then multiple connections can be workload "
            "balanced and applications should not make assumptions as to where "
            "messages are processed. Where message ordering, or affinity to specific "
            "message resources is necessary, a single endpoint "
            "(multiEndpointServer = false) may be required. Optional, defaults to "
            "false. MUST NOT be specified for URI Scheme file:// or http://."
        ),
    )
    heart_beat_interval: int | None = Field(
        default=None,
        exclude_if=is_null,
        alias="heartBeatInterval",
        description=(
            "The recommended value (in seconds) for the heartbeat sent to the queue "
            "manager during periods of inactivity. A value of zero means that no heart "
            "beats are sent. A value of 1 means that the client will use the value "
            "defined by the queue manager. More information on heart beat interval can "
            "be found in the IBM MQ Knowledge Center. Optional, defaults to 300. MUST "
            "be 0-999999."
        ),
    )
    binding_version: str = Field(
        default="0.1.0",
        alias="bindingVersion",
        description="The version of this binding. Optional, defaults to latest.",
    )

    @field_validator("heart_beat_interval")
    @classmethod
    def validate_heart_beat_interval(
        cls, heart_beat_interval: int | None
    ) -> int | None:
        if heart_beat_interval is not None and (
            heart_beat_interval < 0 or heart_beat_interval > 999999
        ):
            raise ValueError("heartBeatInterval MUST be 0-999999")
        return heart_beat_interval


class IBMMQQueue(NonExtendableBaseModel):
    """
    IBM MQ Queue.

    Defines the properties of a queue.
    """

    object_name: str = Field(
        max_length=48,
        alias="objectName",
        description=(
            "Defines the name of the IBM MQ queue associated with the channel. "
            "A value MUST be specified. MUST NOT exceed 48 characters in length. "
            "MUST be a valid IBM MQ queue name."
        ),
    )
    is_partitioned: bool = Field(
        default=False,
        alias="isPartitioned",
        description=(
            "Defines if the queue is a cluster queue and therefore partitioned. If "
            "true, a binding option MAY be specified when accessing the queue. More "
            "information on binding options can be found in the IBM MQ Knowledge "
            "Center. Optional, defaults to false. If false, binding options SHOULD "
            "NOT be specified when accessing the queue."
        ),
    )
    exclusive: bool = Field(
        default=False,
        description=(
            "Specifies if it is recommended to open the queue exclusively. Optional, "
            "defaults to false."
        ),
    )


class IBMMQTopic(NonExtendableBaseModel):
    """
    IBM MQ Topic.

    Defines the properties of a topic.
    """

    # TODO: OPTIONAL Note: if specified, SHALL override AsyncAPI channel name.
    string: str | None = Field(
        default=None,
        exclude_if=is_null,
        max_length=10240,
        description=(
            "The value of the IBM MQ topic string to be used. Optional. Note: if "
            "specified, SHALL override AsyncAPI channel name. MUST NOT exceed 10240 "
            "characters in length. MAY coexist with topic.objectName."
        ),
    )
    # TODO: OPTIONAL Note: if specified, SHALL override AsyncAPI channel name.
    object_name: str | None = Field(
        default=None,
        exclude_if=is_null,
        alias="objectName",
        max_length=48,
        description=(
            "The name of the IBM MQ topic object. Optional. Note: if specified, SHALL "
            "override AsyncAPI channel name. MUST NOT exceed 48 characters in length. "
            "MAY coexist with topic.string."
        ),
    )
    durable_permitted: bool = Field(
        default=True,
        alias="durablePermitted",
        description=(
            "Defines if the subscription may be durable. Optional, defaults to true."
        ),
    )
    last_msg_retained: bool = Field(
        default=False,
        alias="lastMsgRetained",
        description=(
            "Defines if the last message published will be made available to new "
            "subscriptions. Optional, defaults to false."
        ),
    )


class IBMMQChannelBindings(NonExtendableBaseModel):
    """
    IBM MQ Channel Binding Object.

    This object contains information about the channel representation in IBM MQ. Each
    channel corresponds to a Queue or Topic within IBM MQ.

    This object MUST contain only the properties defined below.
    """

    destination_type: Literal["topic", "queue"] = Field(
        default="topic",
        alias="destinationType",
        description=(
            "Defines the type of AsyncAPI channel. Optional, defaults to topic. MUST "
            "be either topic or queue. For type topic, the AsyncAPI channel name "
            "MUST be assumed for the IBM MQ topic string unless overridden."
        ),
    )
    queue: IBMMQQueue | None = Field(
        default=None,
        exclude_if=is_null,
        description=(
            "Defines the properties of a queue. REQUIRED if destinationType = queue. "
            "queue and topic fields MUST NOT coexist within a channel binding."
        ),
    )
    topic: IBMMQTopic | None = Field(
        default=None,
        exclude_if=is_null,
        description=(
            "Defines the properties of a topic. OPTIONAL if destinationType = topic. "
            "queue and topic fields MUST NOT coexist within a channel binding."
        ),
    )
    max_msg_length: int | None = Field(
        default=None,
        exclude_if=is_null,
        alias="maxMsgLength",
        description=(
            "The maximum length of the physical message (in bytes) accepted by the "
            "Topic or Queue. Messages produced that are greater in size than this "
            "value may fail to be delivered. More information on the maximum message "
            "length can be found in the IBM MQ Knowledge Center. Optional, defaults to "
            "negotiated on IBM MQ channel. MUST be 0-104,857,600 bytes (100 MB)."
        ),
    )
    binding_version: str = Field(
        default="0.1.0",
        alias="bindingVersion",
        description="The version of this binding. Optional, defaults to latest.",
    )

    @field_validator("max_msg_length")
    @classmethod
    def validate_max_msg_length(cls, max_msg_length: int | None) -> int | None:
        if max_msg_length is not None and (
            max_msg_length < 0 or max_msg_length > 104857600
        ):
            raise ValueError("maxMsgLength MUST be 0-104,857,600 bytes (100 MB)")
        return max_msg_length

    @model_validator(mode="after")
    def validate_queue_topic_constraints(self) -> "IBMMQChannelBindings":
        # queue and topic fields MUST NOT coexist within a channel binding
        if self.queue is not None and self.topic is not None:
            raise ValueError(
                "queue and topic fields MUST NOT coexist within a channel binding"
            )

        # queue REQUIRED if destinationType = queue
        if self.destination_type == "queue" and self.queue is None:
            raise ValueError("queue must be provided when destinationType='queue'")

        return self


class IBMMQOperationBindings(NonExtendableBaseModel):
    """
    IBM MQ Operation Binding Object.

    This object MUST NOT contain any properties. Its name is reserved for future use.
    """


class IBMMQMessageBindings(NonExtendableBaseModel):
    """
    IBM MQ Message Binding Object.

    This object contains information about the message representation in IBM MQ.

    This object MUST contain only the properties defined below.
    """

    type: Literal["string", "jms", "binary"] = Field(
        default="string",
        description="The type of the message.",
    )
    headers: str | None = Field(
        default=None,
        exclude_if=is_null,
        description=(
            "Defines the IBM MQ message headers to include with this message. More "
            "than one header can be specified as a comma separated list. Supporting "
            "information on IBM MQ message formats can be found in the IBM MQ "
            "Knowledge Center. Optional if type = binary. headers MUST NOT be "
            "specified if type = string or jms."
        ),
    )
    description: str | None = Field(
        default=None,
        exclude_if=is_null,
        description=(
            "Provides additional information for application developers: describes "
            "the message type or format."
        ),
    )
    expiry: int | None = Field(
        default=None,
        exclude_if=is_null,
        description=(
            "The recommended setting the client should use for the TTL (Time-To-Live) "
            "of the message. This is a period of time expressed in milliseconds and "
            "set by the application that puts the message. expiry values are API "
            "dependant e.g., MQI and JMS use different units of time and default "
            "values for unlimited. General information on IBM MQ message expiry can "
            "be found in the IBM MQ Knowledge Center. Optional, defaults to "
            "unlimited. expiry value MUST be either zero (unlimited) or greater than "
            "zero."
        ),
    )
    binding_version: str = Field(
        default="0.1.0",
        alias="bindingVersion",
        description="The version of this binding. Optional, defaults to latest.",
    )

    @field_validator("expiry")
    @classmethod
    def validate_expiry(cls, expiry: int | None) -> int | None:
        if expiry is not None and expiry < 0:
            raise ValueError(
                "expiry value MUST be either zero (unlimited) or greater than zero"
            )
        return expiry

    @model_validator(mode="after")
    def validate_headers_constraints(self) -> "IBMMQMessageBindings":
        # headers MUST NOT be specified if type = string or jms
        if self.type in ["string", "jms"] and self.headers is not None:
            raise ValueError(
                "headers MUST NOT be specified if type = 'string' or 'jms'"
            )
        return self
