from __future__ import annotations

"""
Copyright (C) 2025 Luciano Guerche

This file is part of rabbitmq-mcp-server.

rabbitmq-mcp-server is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

rabbitmq-mcp-server is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with rabbitmq-mcp-server. If not, see <https://www.gnu.org/licenses/>.
"""

"""Topology schemas for queues, exchanges, and bindings operations."""

from typing import Any, Literal

from pydantic import BaseModel, Field, computed_field, field_validator


class QueueDetails(BaseModel):
    """Queue details schema with statistics and state (AC #1, #2, #3).

    Represents a RabbitMQ queue with metadata, configuration, statistics,
    and runtime state information from the Management API.
    """

    name: str = Field(..., description="Queue name")
    vhost: str = Field(..., description="Virtual host name")
    durable: bool = Field(..., description="Queue survives broker restart")
    auto_delete: bool = Field(
        ..., description="Queue deleted when last consumer disconnects"
    )
    arguments: dict[str, Any] = Field(
        default_factory=dict, description="Queue arguments"
    )

    # Statistics fields (AC #2)
    messages: int = Field(default=0, ge=0, description="Total messages in queue")
    messages_ready: int = Field(
        default=0, ge=0, description="Messages ready for delivery"
    )
    messages_unacknowledged: int = Field(
        default=0, ge=0, description="Messages delivered but not acknowledged"
    )
    consumers: int = Field(default=0, ge=0, description="Number of consumers")
    memory: int = Field(default=0, ge=0, description="Memory used in bytes")

    # State field (AC #3)
    state: Literal["running", "idle", "flow"] = Field(
        default="idle",
        description="Queue state: running (active), idle (no activity), flow (flow control active)",
    )

    # Optional message rates
    message_rates: dict[str, float] | None = Field(
        default=None, description="Message rates: publish, deliver, ack"
    )

    model_config = {"extra": "ignore"}

    @field_validator(
        "messages", "messages_ready", "messages_unacknowledged", "consumers", "memory"
    )
    @classmethod
    def validate_non_negative(cls, v: int) -> int:
        """Validate non-negative integers (AC #1)."""
        if v < 0:
            raise ValueError("Value must be non-negative")
        return v

    @computed_field  # type: ignore[prop-decorator]
    @property
    def memory_formatted(self) -> str:
        """Human-readable memory format (AC #2, Task 6).

        Converts bytes to KB, MB, GB using 1024-based units.
        """
        return format_memory(self.memory)


def format_memory(bytes_val: int) -> str:
    """Convert bytes to human-readable format (AC #2).

    Uses 1024-based units (KiB, MiB, GiB) following RabbitMQ convention.

    Args:
        bytes_val: Memory size in bytes

    Returns:
        Formatted string like "512 MB", "1.2 GB"
    """
    if bytes_val < 1024:
        return f"{bytes_val} B"
    elif bytes_val < 1024**2:
        return f"{bytes_val / 1024:.2f} KB"
    elif bytes_val < 1024**3:
        return f"{bytes_val / (1024 ** 2):.2f} MB"
    else:
        return f"{bytes_val / (1024 ** 3):.2f} GB"


class CreateQueueRequest(BaseModel):
    """Request schema for creating a queue (Story 3.2, AC #1, #2, #6, #7).

    Validates queue creation parameters including name, vhost, durability,
    and arguments (x-message-ttl, x-max-length, x-queue-type).

    Examples:
        Classic queue with TTL:
            CreateQueueRequest(
                name="orders",
                vhost="/",
                durable=True,
                arguments={"x-message-ttl": 60000}
            )

        Quorum queue with max length:
            CreateQueueRequest(
                name="events",
                vhost="/prod",
                durable=True,
                arguments={"x-queue-type": "quorum", "x-max-length": 1000}
            )

        Queue with DLX:
            CreateQueueRequest(
                name="tasks",
                vhost="/",
                arguments={"x-dead-letter-exchange": "dlx-exchange"}
            )
    """

    name: str = Field(
        ...,
        description="Queue name (alphanumeric, hyphen, underscore, period; max 255 chars)",
    )
    vhost: str = Field(default="/", description="Virtual host name")
    durable: bool = Field(
        default=True, description="Queue survives broker restart (default: True)"
    )
    auto_delete: bool = Field(
        default=False,
        description="Queue deleted when last consumer disconnects (default: False)",
    )
    arguments: dict[str, Any] | None = Field(
        default=None,
        description=(
            "Queue arguments: "
            "x-message-ttl (int, milliseconds), "
            "x-max-length (int, max messages), "
            "x-dead-letter-exchange (str, DLX name), "
            "x-queue-type (str, 'classic' or 'quorum')"
        ),
    )

    model_config = {"extra": "ignore"}

    @field_validator("name")
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Validate queue name (AC #6, #7).

        Queue names must:
        - Be 1-255 characters long
        - Contain only alphanumeric, hyphen, underscore, period

        Args:
            v: Queue name to validate

        Returns:
            Validated queue name

        Raises:
            ValueError: If name is invalid
        """
        if not v:
            raise ValueError("Queue name cannot be empty")

        if len(v) > 255:
            raise ValueError(f"Queue name exceeds 255 characters (length: {len(v)})")

        import re

        if not re.match(r"^[a-zA-Z0-9._-]+$", v):
            raise ValueError(
                f"Queue name '{v}' contains invalid characters. "
                f"Only alphanumeric, hyphen, underscore, and period are allowed."
            )

        return v

    @field_validator("arguments")
    @classmethod
    def validate_arguments(cls, v: dict[str, Any] | None) -> dict[str, Any] | None:
        """Validate queue arguments (AC #2, Task 5).

        Validates:
        - x-message-ttl: positive integer (milliseconds)
        - x-max-length: positive integer (max messages)
        - x-queue-type: "classic" or "quorum"
        - x-dead-letter-exchange: non-empty string

        Args:
            v: Queue arguments dictionary

        Returns:
            Validated arguments

        Raises:
            ValueError: If arguments are invalid
        """
        if v is None:
            return None

        # Validate x-message-ttl
        if "x-message-ttl" in v:
            ttl = v["x-message-ttl"]
            if not isinstance(ttl, int) or ttl <= 0:
                raise ValueError(
                    f"x-message-ttl must be a positive integer (got: {ttl})"
                )

        # Validate x-max-length
        if "x-max-length" in v:
            max_length = v["x-max-length"]
            if not isinstance(max_length, int) or max_length <= 0:
                raise ValueError(
                    f"x-max-length must be a positive integer (got: {max_length})"
                )

        # Validate x-queue-type
        if "x-queue-type" in v:
            queue_type = v["x-queue-type"]
            if queue_type not in ["classic", "quorum"]:
                raise ValueError(
                    f"x-queue-type must be 'classic' or 'quorum' (got: {queue_type})"
                )

        # Validate x-dead-letter-exchange
        if "x-dead-letter-exchange" in v:
            dlx = v["x-dead-letter-exchange"]
            if not isinstance(dlx, str) or not dlx:
                raise ValueError(
                    f"x-dead-letter-exchange must be a non-empty string (got: {dlx})"
                )

        return v


class DeleteQueueRequest(BaseModel):
    """Request schema for deleting a queue with safety validation (Story 3.3, AC #1-#5).

    Validates queue deletion parameters including name, vhost, and force flag.
    Implements safety checks to prevent accidental deletion of queues with
    messages or consumers unless explicitly forced.

    Examples:
        Delete empty queue (no force needed):
            DeleteQueueRequest(name="temp-queue", vhost="/")

        Force delete queue with messages/consumers:
            DeleteQueueRequest(name="orders", vhost="/", force=True)

        Delete from specific vhost:
            DeleteQueueRequest(name="tasks", vhost="/prod")
    """

    name: str = Field(
        ...,
        description="Queue name (alphanumeric, hyphen, underscore, period; max 255 chars)",
    )
    vhost: str = Field(default="/", description="Virtual host name")
    force: bool = Field(
        default=False,
        description="Bypass safety checks to delete queue with messages/consumers (default: False)",
    )

    model_config = {"extra": "ignore"}

    @field_validator("name")
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Validate queue name (AC #5, Task 1).

        Queue names must:
        - Be 1-255 characters long
        - Contain only alphanumeric, hyphen, underscore, period

        Args:
            v: Queue name to validate

        Returns:
            Validated queue name

        Raises:
            ValueError: If name is invalid
        """
        if not v:
            raise ValueError("Queue name cannot be empty")

        if len(v) > 255:
            raise ValueError(f"Queue name exceeds 255 characters (length: {len(v)})")

        import re

        if not re.match(r"^[a-zA-Z0-9._-]+$", v):
            raise ValueError(
                f"Queue name '{v}' contains invalid characters. "
                f"Only alphanumeric, hyphen, underscore, and period are allowed."
            )

        return v


class PurgeQueueRequest(BaseModel):
    """Request schema for purging all messages from a queue (Story 3.4, AC #1-8).

    Validates queue purge parameters including name and vhost.
    Purge removes all messages from the queue while preserving queue
    configuration, settings, and consumer connections.

    Examples:
        Purge queue in default vhost:
            PurgeQueueRequest(name="orders", vhost="/")

        Purge queue in specific vhost:
            PurgeQueueRequest(name="tasks", vhost="/prod")

        Purge empty queue (returns purged_message_count=0, not error):
            PurgeQueueRequest(name="temp-queue", vhost="/")
    """

    name: str = Field(
        ...,
        description="Queue name (alphanumeric, hyphen, underscore, period; max 255 chars)",
    )
    vhost: str = Field(default="/", description="Virtual host name")

    model_config = {"extra": "ignore"}

    @field_validator("name")
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Validate queue name (AC #7, Task 1).

        Queue names must:
        - Be 1-255 characters long
        - Contain only alphanumeric, hyphen, underscore, period

        Args:
            v: Queue name to validate

        Returns:
            Validated queue name

        Raises:
            ValueError: If name is invalid
        """
        if not v:
            raise ValueError("Queue name cannot be empty")

        if len(v) > 255:
            raise ValueError(f"Queue name exceeds 255 characters (length: {len(v)})")

        import re

        if not re.match(r"^[a-zA-Z0-9._-]+$", v):
            raise ValueError(
                f"Queue name '{v}' contains invalid characters. "
                f"Only alphanumeric, hyphen, underscore, and period are allowed."
            )

        return v


class MessageStats(BaseModel):
    """Message statistics for exchanges (Story 3.5, AC #3, Task 2).

    Represents message rates and counts for an exchange from RabbitMQ API.
    """

    publish_in: float | None = Field(
        default=None,
        description="Message publish-in rate (messages/second)",
    )
    publish_out: float | None = Field(
        default=None,
        description="Message publish-out rate (messages/second)",
    )

    model_config = {"extra": "ignore"}


class ExchangeInfo(BaseModel):
    """Exchange information schema with properties and statistics (Story 3.5, AC #2, #3).

    Represents a RabbitMQ exchange with metadata, configuration, and optional
    message statistics from the Management API.

    System exchanges (amq.*) and default exchange ("") are immutable and cannot be deleted.
    Internal exchanges cannot receive direct publishes, only exchange-to-exchange bindings.

    Exchange Types:
        - direct: Exact routing key match
        - topic: Wildcard routing (e.g., "logs.*", "logs.#")
        - fanout: Broadcast to all bound queues
        - headers: Header-based routing

    Examples:
        Standard exchange:
            ExchangeInfo(name="orders", vhost="/", type="direct", durable=True, ...)

        System exchange:
            ExchangeInfo(name="amq.direct", vhost="/", type="direct", durable=True, internal=False, ...)

        Default exchange:
            ExchangeInfo(name="", vhost="/", type="direct", durable=True, ...)
    """

    name: str = Field(
        ..., description="Exchange name (empty string for default exchange)"
    )
    vhost: str = Field(..., description="Virtual host name")
    type: Literal["direct", "topic", "fanout", "headers"] = Field(
        ...,
        description="Exchange type: direct (exact match), topic (wildcard), fanout (broadcast), headers (header-based)",
    )
    durable: bool = Field(..., description="Exchange survives broker restart")
    auto_delete: bool = Field(
        ...,
        description="Exchange deleted when last binding removed",
    )
    internal: bool = Field(
        ...,
        description="Internal exchanges cannot receive direct publishes (only exchange-to-exchange bindings)",
    )
    arguments: dict[str, Any] = Field(
        default_factory=dict,
        description="Exchange arguments (e.g., alternate-exchange)",
    )

    # Optional message statistics (AC #3)
    message_stats: MessageStats | None = Field(
        default=None,
        description="Message statistics (optional, depends on exchange activity)",
    )

    model_config = {"extra": "ignore"}

    @field_validator("type")
    @classmethod
    def validate_type(cls, v: str) -> str:
        """Validate exchange type (AC #2, Task 2).

        Args:
            v: Exchange type to validate

        Returns:
            Validated exchange type

        Raises:
            ValueError: If type is invalid
        """
        valid_types = {"direct", "topic", "fanout", "headers"}
        if v not in valid_types:
            raise ValueError(
                f"Exchange type '{v}' is invalid. Must be one of: {', '.join(valid_types)}"
            )

        return v


class ListExchangesRequest(BaseModel):
    """Request schema for listing exchanges in a vhost (Story 3.5, AC #1, #6, #7).

    Validates list exchanges parameters including vhost, optional filters
    (name_pattern, type), and sort options.

    Examples:
        List all exchanges in default vhost:
            ListExchangesRequest(vhost="/")

        List exchanges filtered by type:
            ListExchangesRequest(vhost="/", type="topic")

        List exchanges with name pattern:
            ListExchangesRequest(vhost="/prod", name_pattern="^amq\\..*")

        List and sort by message rate:
            ListExchangesRequest(vhost="/", sort_by="message_in_rate")
    """

    vhost: str = Field(default="/", description="Virtual host name")
    name_pattern: str | None = Field(
        default=None,
        description="Regex pattern to filter exchange names (optional)",
    )
    type: Literal["direct", "topic", "fanout", "headers"] | None = Field(
        default=None,
        description="Filter by exchange type (optional)",
    )
    sort_by: Literal["name", "type", "message_in_rate"] = Field(
        default="name",
        description="Sort field: name (alphabetical), type (alphabetical), message_in_rate (descending)",
    )

    model_config = {"extra": "ignore"}

    @field_validator("vhost")
    @classmethod
    def validate_vhost(cls, v: str) -> str:
        """Validate vhost format (AC #6, Task 1).

        Basic format check, actual validation delegated to VhostValidator middleware.

        Args:
            v: Virtual host name to validate

        Returns:
            Validated vhost name

        Raises:
            ValueError: If vhost is invalid
        """
        if not v:
            raise ValueError("Virtual host name cannot be empty")

        return v


class CreateExchangeRequest(BaseModel):
    """Request schema for creating an exchange (Story 3.6, AC #1-8).

    Validates exchange creation parameters including name, type, and properties.
    Implements system exchange protection and name validation.

    Exchange Types:
        - **direct**: Exact routing key match (point-to-point messaging)
        - **topic**: Pattern matching with wildcards (* for one word, # for zero or more)
        - **fanout**: Broadcast to all bound queues (routing key ignored)
        - **headers**: Header-based routing using message properties (x-match argument)

    Common Arguments:
        - **alternate-exchange**: Route unroutable messages to alternate exchange (string)

    Examples:
        Create topic exchange:
            CreateExchangeRequest(
                vhost="/",
                name="events",
                type="topic",
                durable=True
            )

        Create fanout exchange with alternate:
            CreateExchangeRequest(
                vhost="/prod",
                name="notifications",
                type="fanout",
                durable=True,
                arguments={"alternate-exchange": "unroutable"}
            )
    """

    vhost: str = Field(default="/", description="Virtual host name")
    name: str = Field(..., description="Exchange name")
    type: Literal["direct", "topic", "fanout", "headers"] = Field(
        ...,
        description="Exchange type: direct (exact), topic (wildcard), fanout (broadcast), headers (header-based)",
    )
    durable: bool = Field(
        default=True,
        description="Exchange survives broker restart",
    )
    auto_delete: bool = Field(
        default=False,
        description="Exchange deleted when last binding removed",
    )
    internal: bool = Field(
        default=False,
        description="Internal exchanges cannot receive direct publishes (only exchange-to-exchange bindings)",
    )
    arguments: dict[str, Any] | None = Field(
        default=None,
        description="Exchange arguments (e.g., alternate-exchange)",
    )

    model_config = {"extra": "ignore"}

    @field_validator("vhost")
    @classmethod
    def validate_vhost(cls, v: str) -> str:
        """Validate vhost format (AC #1, Task 1).

        Basic format check, actual validation delegated to VhostValidator middleware.

        Args:
            v: Virtual host name to validate

        Returns:
            Validated vhost name

        Raises:
            ValueError: If vhost is invalid
        """
        if not v:
            raise ValueError("Virtual host name cannot be empty")

        return v

    @field_validator("name")
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Validate exchange name (AC #7, #8, Task 1).

        Exchange names must:
        - Be 1-255 characters
        - Contain only alphanumeric, hyphen, underscore, or period
        - Not start with "amq." (reserved for RabbitMQ system exchanges)
        - Not be empty string (default exchange cannot be created)

        Args:
            v: Exchange name to validate

        Returns:
            Validated exchange name

        Raises:
            ValueError: If exchange name is invalid
        """
        if not v:
            raise ValueError(
                "Exchange name cannot be empty (default exchange '' cannot be created)"
            )

        if len(v) > 255:
            raise ValueError(
                f"Exchange name exceeds maximum length of 255 characters: {len(v)}"
            )

        # System exchange protection (AC #8)
        if v.startswith("amq."):
            raise ValueError(
                f"Cannot create exchanges with 'amq.' prefix (reserved): '{v}'"
            )

        # Name pattern validation
        if not v.replace("-", "").replace("_", "").replace(".", "").isalnum():
            raise ValueError(
                f"Exchange name '{v}' contains invalid characters. "
                "Only alphanumeric, hyphen, underscore, and period allowed"
            )

        return v

    @field_validator("type")
    @classmethod
    def validate_type(cls, v: str) -> str:
        """Validate exchange type (AC #2, Task 5).

        Args:
            v: Exchange type to validate

        Returns:
            Validated exchange type

        Raises:
            ValueError: If type is invalid
        """
        valid_types = {"direct", "topic", "fanout", "headers"}
        if v not in valid_types:
            raise ValueError(
                f"Exchange type '{v}' is invalid. Must be one of: {', '.join(valid_types)}"
            )

        return v


class DeleteExchangeRequest(BaseModel):
    """Request schema for deleting an exchange with safety validation (Story 3.7, AC #1, #4, #5).

    Validates exchange deletion parameters including name, vhost, and force flag.
    Implements protection for system exchanges (amq.*) and default exchange ("").
    Safety checks prevent deletion of exchanges with active bindings unless forced.

    Examples:
        Delete exchange without bindings:
            DeleteExchangeRequest(vhost="/", name="events")

        Force delete exchange with bindings:
            DeleteExchangeRequest(vhost="/", name="orders", force=True)

        Delete from specific vhost:
            DeleteExchangeRequest(vhost="/prod", name="notifications")
    """

    vhost: str = Field(default="/", description="Virtual host name")
    name: str = Field(..., description="Exchange name")
    force: bool = Field(
        default=False,
        description="Bypass safety checks to delete exchange with bindings (default: False)",
    )

    model_config = {"extra": "ignore"}

    @field_validator("vhost")
    @classmethod
    def validate_vhost(cls, v: str) -> str:
        """Validate vhost format (AC #1, Task 1).

        Basic format check, actual validation delegated to VhostValidator middleware.

        Args:
            v: Virtual host name to validate

        Returns:
            Validated vhost name

        Raises:
            ValueError: If vhost is invalid
        """
        if not v:
            raise ValueError("Virtual host name cannot be empty")

        return v

    @field_validator("name")
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Validate exchange name (AC #4, #5, Task 1).

        Exchange name validation:
        - Cannot be empty string (default exchange protection, AC #5)
        - Cannot start with "amq." (system exchange protection, AC #4)

        Args:
            v: Exchange name to validate

        Returns:
            Validated exchange name

        Raises:
            ValueError: If exchange name is invalid
        """
        # Protect default exchange (AC #5)
        if v == "":
            raise ValueError("Cannot delete default exchange")

        # Protect system exchanges (AC #4)
        if v.startswith("amq."):
            raise ValueError(f"Cannot delete system exchange '{v}'")

        return v


class DeleteExchangeResponse(BaseModel):
    """Response schema for delete exchange operation (Story 3.7, AC #6).

    Represents successful exchange deletion with status and message.

    Example:
        DeleteExchangeResponse(
            status="success",
            message="Exchange 'events' deleted",
            vhost="/",
            name="events"
        )
    """

    status: Literal["success"] = Field(..., description="Operation status")
    message: str = Field(..., description="Success message")
    vhost: str = Field(..., description="Virtual host name")
    name: str = Field(..., description="Exchange name")

    model_config = {"extra": "ignore"}


class BindingInfo(BaseModel):
    """Binding information schema (Story 3.8, AC #1, #3, #4).

    Represents a RabbitMQ binding between an exchange and a queue (or another exchange)
    with routing key, arguments, and unique properties key identifier.

    Binding Types:
        - **Exchange-to-Queue** (destination_type="queue"): Most common binding type for
          message routing from exchange to queue for consumption.
        - **Exchange-to-Exchange** (destination_type="exchange"): Federation and routing
          patterns where one exchange forwards messages to another exchange.

    Routing Key Patterns:
        - **Fanout Exchange**: routing_key ignored (can be empty string), broadcasts to
          all bound queues regardless of routing key.
        - **Direct Exchange**: routing_key must match exactly (no wildcards), used for
          point-to-point messaging.
        - **Topic Exchange**: routing_key supports wildcards:
            * `*` matches exactly one word (e.g., "logs.*.error" matches "logs.app.error")
            * `#` matches zero or more words (e.g., "logs.#" matches "logs", "logs.app", "logs.app.error")
            * `.` separates words in routing key
        - **Headers Exchange**: routing_key optional, uses arguments for header-based
          matching with x-match: "all" (AND logic) or "any" (OR logic).

    Properties Key:
        Unique binding identifier (hash of routing_key + arguments) generated by RabbitMQ
        server. Used in delete operations to identify specific binding when multiple bindings
        exist between same source/destination pair with different routing keys.

    Examples:
        Exchange-to-queue binding (direct):
            BindingInfo(
                source="events",
                destination="orders-queue",
                destination_type="queue",
                routing_key="order.created",
                vhost="/",
                properties_key="~order.created"
            )

        Exchange-to-queue binding (fanout):
            BindingInfo(
                source="notifications",
                destination="email-queue",
                destination_type="queue",
                routing_key="",
                vhost="/"
            )

        Exchange-to-queue binding (topic with wildcards):
            BindingInfo(
                source="logs",
                destination="error-queue",
                destination_type="queue",
                routing_key="logs.*.error",
                vhost="/"
            )

        Exchange-to-exchange binding:
            BindingInfo(
                source="upstream-exchange",
                destination="downstream-exchange",
                destination_type="exchange",
                routing_key="events.*",
                vhost="/"
            )

        Headers exchange binding:
            BindingInfo(
                source="headers-exchange",
                destination="priority-queue",
                destination_type="queue",
                routing_key="",
                arguments={"x-match": "all", "priority": "high", "type": "urgent"},
                vhost="/"
            )
    """

    source: str = Field(..., description="Exchange name that sends messages")
    destination: str = Field(
        ..., description="Queue or exchange name that receives messages"
    )
    destination_type: Literal["queue", "exchange"] = Field(
        ...,
        description="Type of destination: 'queue' (exchange-to-queue) or 'exchange' (exchange-to-exchange)",
    )
    routing_key: str = Field(
        ...,
        description="Routing key for binding (empty for fanout, exact for direct, pattern for topic)",
    )
    arguments: dict[str, Any] | None = Field(
        default=None,
        description="Header matching rules for headers exchange (x-match: all|any) or other binding arguments",
    )
    vhost: str = Field(..., description="Virtual host name")
    properties_key: str | None = Field(
        default=None,
        description="Unique binding identifier (hash of routing_key + arguments) used in delete operations",
    )

    model_config = {"extra": "ignore"}


class ListBindingsRequest(BaseModel):
    """Request schema for listing bindings in a vhost (Story 3.8, AC #1, #2, #7).

    Validates list bindings parameters including vhost, optional filters
    (source, destination, destination_type), and sort options.

    Client-side filtering is used after fetching all bindings from API to minimize
    HTTP requests and improve performance (Epic 3 optimization strategy).

    Examples:
        List all bindings in default vhost:
            ListBindingsRequest(vhost="/")

        List bindings from specific exchange:
            ListBindingsRequest(vhost="/", source="events")

        List bindings to specific queue:
            ListBindingsRequest(vhost="/", destination="orders-queue")

        List only exchange-to-exchange bindings:
            ListBindingsRequest(vhost="/", destination_type="exchange")

        List and sort by routing key:
            ListBindingsRequest(vhost="/", sort_by="routing_key")

        Combined filters:
            ListBindingsRequest(
                vhost="/prod",
                source="events",
                destination_type="queue",
                sort_by="destination"
            )
    """

    vhost: str = Field(default="/", description="Virtual host name")
    source: str | None = Field(
        default=None,
        description="Filter by source exchange name (optional)",
    )
    destination: str | None = Field(
        default=None,
        description="Filter by destination queue or exchange name (optional)",
    )
    destination_type: Literal["queue", "exchange"] | None = Field(
        default=None,
        description="Filter by destination type: 'queue' or 'exchange' (optional)",
    )
    sort_by: Literal["source", "destination", "routing_key"] | None = Field(
        default=None,
        description="Sort field: source, destination, or routing_key (default: API order)",
    )

    model_config = {"extra": "ignore"}

    @field_validator("vhost")
    @classmethod
    def validate_vhost(cls, v: str) -> str:
        """Validate vhost format (AC #1, Task 1).

        Basic format check, actual validation delegated to VhostValidator middleware.

        Args:
            v: Virtual host name to validate

        Returns:
            Validated vhost name

        Raises:
            ValueError: If vhost is invalid
        """
        if not v:
            raise ValueError("Virtual host name cannot be empty")

        return v


class CreateBindingRequest(BaseModel):
    """Request schema for creating a binding (Story 3.9, AC #1, #2, #3, #7).

    Validates binding creation parameters including source exchange, destination
    (queue or exchange), routing key, and arguments for headers exchange.

    Binding Types:
        - **Exchange-to-Queue** (destination_type="queue"): Most common binding type
          for message routing from exchange to queue.
        - **Exchange-to-Exchange** (destination_type="exchange"): Federation and
          routing patterns where one exchange forwards to another.

    Routing Key Patterns by Exchange Type:
        - **Fanout Exchange**: routing_key ignored (can be empty string)
        - **Direct Exchange**: routing_key must match exactly (no wildcards)
        - **Topic Exchange**: routing_key supports wildcards:
            * `*` matches one word (e.g., "order.*" matches "order.created")
            * `#` matches zero or more words (e.g., "order.#" matches "order", "order.created", "order.payment.completed")
        - **Headers Exchange**: routing_key optional, uses arguments with x-match

    Examples:
        Exchange-to-queue binding (topic):
            CreateBindingRequest(
                source="events",
                destination="orders-queue",
                routing_key="order.*",
                vhost="/"
            )

        Exchange-to-queue binding (fanout):
            CreateBindingRequest(
                source="notifications",
                destination="email-queue",
                routing_key="",
                vhost="/"
            )

        Exchange-to-exchange binding:
            CreateBindingRequest(
                source="upstream",
                destination="downstream",
                destination_type="exchange",
                routing_key="events.*",
                vhost="/"
            )

        Headers exchange binding:
            CreateBindingRequest(
                source="headers-exchange",
                destination="priority-queue",
                routing_key="",
                arguments={"x-match": "all", "priority": "high"},
                vhost="/"
            )
    """

    source: str = Field(
        ...,
        description="Source exchange name (alphanumeric, hyphen, underscore, period; max 255 chars)",
    )
    destination: str = Field(
        ...,
        description="Destination queue or exchange name (alphanumeric, hyphen, underscore, period; max 255 chars)",
    )
    vhost: str = Field(default="/", description="Virtual host name")
    routing_key: str = Field(
        default="",
        description="Routing key for binding (empty for fanout, exact for direct, pattern for topic)",
    )
    destination_type: Literal["queue", "exchange"] = Field(
        default="queue",
        description="Type of destination: 'queue' (exchange-to-queue) or 'exchange' (exchange-to-exchange)",
    )
    arguments: dict[str, Any] | None = Field(
        default=None,
        description="Header matching rules for headers exchange (x-match: all|any) or other binding arguments",
    )

    model_config = {"extra": "ignore"}

    @field_validator("source", "destination")
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Validate exchange/queue name (AC #7, Task 1).

        Names must:
        - Be 1-255 characters long
        - Contain only alphanumeric, hyphen, underscore, period

        Args:
            v: Name to validate

        Returns:
            Validated name

        Raises:
            ValueError: If name is invalid
        """
        if not v:
            raise ValueError("Name cannot be empty")

        if len(v) > 255:
            raise ValueError(f"Name exceeds 255 characters (length: {len(v)})")

        import re

        if not re.match(r"^[a-zA-Z0-9._-]+$", v):
            raise ValueError(
                f"Name '{v}' contains invalid characters. "
                f"Only alphanumeric, hyphen, underscore, and period are allowed."
            )

        return v

    @field_validator("vhost")
    @classmethod
    def validate_vhost(cls, v: str) -> str:
        """Validate vhost format (basic check, actual validation delegated to middleware).

        Args:
            v: Virtual host name to validate

        Returns:
            Validated vhost name

        Raises:
            ValueError: If vhost is empty
        """
        if not v:
            raise ValueError("Virtual host name cannot be empty")

        return v


class DeleteBindingRequest(BaseModel):
    """Request schema for deleting a binding (Story 3.10, AC #1, #4).

    Validates binding deletion parameters including source exchange, destination
    (queue or exchange), routing key, and vhost. The routing_key must match
    the exact binding to be deleted.

    Unlike creation, deletion requires an exact match - the routing_key cannot
    be empty as it's needed to identify the specific binding when multiple
    bindings exist between the same source and destination.

    Binding Types:
        - **Exchange-to-Queue** (destination_type="queue"): Delete binding from
          exchange to queue.
        - **Exchange-to-Exchange** (destination_type="exchange"): Delete binding
          between two exchanges.

    Safety Notes:
        - Deleting a binding does NOT delete the source exchange or destination
        - Multiple bindings can exist between same source/destination with
          different routing keys - this operation only deletes the exact match
        - No confirmation required (bindings don't store data, low risk operation)

    Examples:
        Delete exchange-to-queue binding:
            DeleteBindingRequest(
                source="events",
                destination="orders-queue",
                routing_key="order.*",
                vhost="/"
            )

        Delete exchange-to-exchange binding:
            DeleteBindingRequest(
                source="upstream",
                destination="downstream",
                destination_type="exchange",
                routing_key="events.*",
                vhost="/"
            )
    """

    source: str = Field(
        ...,
        description="Source exchange name (alphanumeric, hyphen, underscore, period; max 255 chars)",
    )
    destination: str = Field(
        ...,
        description="Destination queue or exchange name (alphanumeric, hyphen, underscore, period; max 255 chars)",
    )
    routing_key: str = Field(
        ...,
        description="Routing key for binding (must match exact binding, cannot be empty)",
    )
    vhost: str = Field(default="/", description="Virtual host name")
    destination_type: Literal["queue", "exchange"] = Field(
        default="queue",
        description="Type of destination: 'queue' (exchange-to-queue) or 'exchange' (exchange-to-exchange)",
    )

    model_config = {"extra": "ignore"}

    @field_validator("source", "destination")
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Validate exchange/queue name (AC #4, Task 1).

        Names must:
        - Be 1-255 characters long
        - Contain only alphanumeric, hyphen, underscore, period

        Args:
            v: Name to validate

        Returns:
            Validated name

        Raises:
            ValueError: If name is invalid
        """
        if not v:
            raise ValueError("Name cannot be empty")

        if len(v) > 255:
            raise ValueError(f"Name exceeds 255 characters (length: {len(v)})")

        import re

        if not re.match(r"^[a-zA-Z0-9._-]+$", v):
            raise ValueError(
                f"Name '{v}' contains invalid characters. "
                f"Only alphanumeric, hyphen, underscore, and period are allowed."
            )

        return v

    @field_validator("routing_key")
    @classmethod
    def validate_routing_key(cls, v: str) -> str:
        """Validate routing key is not empty for delete operation (AC #4, Task 1).

        For deletion, the routing_key must match the exact binding. An empty
        routing_key is ambiguous when multiple bindings exist.

        Args:
            v: Routing key to validate

        Returns:
            Validated routing key

        Raises:
            ValueError: If routing_key is empty
        """
        if not v:
            raise ValueError(
                "Routing key cannot be empty for delete operation. "
                "Must provide exact routing key to identify the binding."
            )

        return v

    @field_validator("vhost")
    @classmethod
    def validate_vhost(cls, v: str) -> str:
        """Validate vhost format (basic check, actual validation delegated to middleware).

        Args:
            v: Virtual host name to validate

        Returns:
            Validated vhost name

        Raises:
            ValueError: If vhost is empty
        """
        if not v:
            raise ValueError("Virtual host name cannot be empty")

        return v


class DeleteBindingResponse(BaseModel):
    """Response schema for delete binding operation (Story 3.10, AC #3).

    Returns status, message, and binding details after deletion attempt.

    Examples:
        Success:
            DeleteBindingResponse(
                status="success",
                message="Binding deleted",
                source="events",
                destination="orders-queue",
                routing_key="order.*",
                vhost="/",
                destination_type="queue",
                correlation_id="abc-123",
                duration_ms=45.2
            )

        Error (binding not found):
            DeleteBindingResponse(
                status="error",
                message="Binding not found: source='events', destination='orders-queue', routing_key='order.invalid'",
                source="events",
                destination="orders-queue",
                routing_key="order.invalid",
                vhost="/",
                destination_type="queue",
                correlation_id="abc-123"
            )
    """

    status: Literal["success", "error"] = Field(
        ..., description="Operation status: success or error"
    )
    message: str = Field(
        ...,
        description="'Binding deleted' on success, error details on failure",
    )
    source: str = Field(..., description="Source exchange name")
    destination: str = Field(..., description="Destination queue or exchange name")
    routing_key: str = Field(..., description="Routing key of the binding")
    vhost: str = Field(..., description="Virtual host name")
    destination_type: Literal["queue", "exchange"] = Field(
        ..., description="Type of destination"
    )
    correlation_id: str = Field(..., description="Request correlation ID for tracing")
    duration_ms: float | None = Field(
        default=None, description="Operation duration in milliseconds"
    )

    model_config = {"extra": "ignore"}
