"""
Ferramentas MCP para gerenciamento de exchanges RabbitMQ.

Este módulo implementa as ferramentas MCP para criar, deletar,
vincular e desvincular exchanges RabbitMQ.
"""

from datetime import datetime
from typing import Any

from pika.exceptions import AMQPError

from mcp.schemas.exchange_schemas import (
    BindingInfo,
    ExchangeBindResponse,
    ExchangeBindSchema,
    ExchangeCreateResponse,
    ExchangeCreateSchema,
    ExchangeDeleteResponse,
    ExchangeDeleteSchema,
    ExchangeUnbindResponse,
    ExchangeUnbindSchema,
)
from mcp.tools.connection_tools import connection_manager
from shared.models.exchange import Exchange, ExchangeType
from shared.utils.logging import log_mcp_request
from shared.utils.validation import validate_exchange_params


class ExchangeManager:
    """Gerenciador de exchanges RabbitMQ."""

    def __init__(self):
        """Inicializa o gerenciador de exchanges."""
        self.exchanges: dict[str, Exchange] = {}
        self.bindings: dict[str, list[BindingInfo]] = {}  # exchange_name -> list of bindings

    def create_exchange(self, params: ExchangeCreateSchema) -> ExchangeCreateResponse:
        """
        Cria um novo exchange no RabbitMQ.

        Args:
            params: Parâmetros de criação do exchange

        Returns:
            Resposta com informações do exchange criado
        """
        logger = log_mcp_request("exchange_create", params.connection_id)

        try:
            # Validar parâmetros
            validate_exchange_params(params.dict())

            # Obter conexão
            connection = connection_manager.get_connection(params.connection_id)
            if not connection:
                raise ValueError(f"Conexão {params.connection_id} não encontrada")

            pika_connection = connection_manager.get_pika_connection(params.connection_id)
            if not pika_connection or pika_connection.is_closed:
                raise ValueError(f"Conexão {params.connection_id} não está ativa")

            # Criar canal
            channel = pika_connection.channel()

            # Declarar exchange
            logger.info("Criando exchange",
                       exchange_name=params.exchange_name,
                       exchange_type=params.exchange_type,
                       durable=params.durable,
                       auto_delete=params.auto_delete,
                       internal=params.internal)

            channel.exchange_declare(
                exchange=params.exchange_name,
                exchange_type=params.exchange_type,
                durable=params.durable,
                auto_delete=params.auto_delete,
                internal=params.internal,
                arguments=params.arguments
            )

            # Criar modelo do exchange
            exchange = Exchange(
                name=params.exchange_name,
                type=ExchangeType(params.exchange_type),
                durable=params.durable,
                auto_delete=params.auto_delete,
                internal=params.internal,
                arguments=params.arguments,
                vhost=connection.virtual_host
            )

            # Armazenar exchange
            exchange_key = f"{params.connection_id}:{params.exchange_name}"
            self.exchanges[exchange_key] = exchange

            # Inicializar lista de bindings
            self.bindings[params.exchange_name] = []

            logger.info("Exchange criado com sucesso",
                       exchange_name=params.exchange_name,
                       exchange_type=params.exchange_type)

            return ExchangeCreateResponse(
                exchange_name=params.exchange_name,
                exchange_type=params.exchange_type,
                status="created",
                durable=params.durable,
                auto_delete=params.auto_delete,
                internal=params.internal,
                created_at=exchange.created_at.isoformat()
            )

        except AMQPError as e:
            logger.error("Erro AMQP ao criar exchange",
                        exchange_name=params.exchange_name,
                        error=str(e))
            raise ValueError(f"Erro AMQP ao criar exchange: {e}") from e
        except Exception as e:
            logger.error("Erro ao criar exchange",
                        exchange_name=params.exchange_name,
                        error=str(e))
            raise ValueError(f"Erro ao criar exchange: {e}") from e

    def delete_exchange(self, params: ExchangeDeleteSchema) -> ExchangeDeleteResponse:
        """
        Deleta um exchange do RabbitMQ.

        Args:
            params: Parâmetros de deleção do exchange

        Returns:
            Resposta com informações da deleção
        """
        logger = log_mcp_request("exchange_delete", params.connection_id)

        try:
            # Obter conexão
            connection = connection_manager.get_connection(params.connection_id)
            if not connection:
                raise ValueError(f"Conexão {params.connection_id} não encontrada")

            pika_connection = connection_manager.get_pika_connection(params.connection_id)
            if not pika_connection or pika_connection.is_closed:
                raise ValueError(f"Conexão {params.connection_id} não está ativa")

            # Criar canal
            channel = pika_connection.channel()

            # Deletar exchange
            logger.info("Deletando exchange",
                       exchange_name=params.exchange_name,
                       if_unused=params.if_unused)

            channel.exchange_delete(
                exchange=params.exchange_name,
                if_unused=params.if_unused
            )

            # Remover exchange do cache
            exchange_key = f"{params.connection_id}:{params.exchange_name}"
            if exchange_key in self.exchanges:
                del self.exchanges[exchange_key]

            # Remover bindings do cache
            if params.exchange_name in self.bindings:
                del self.bindings[params.exchange_name]

            logger.info("Exchange deletado com sucesso",
                       exchange_name=params.exchange_name)

            return ExchangeDeleteResponse(
                exchange_name=params.exchange_name,
                status="deleted",
                deleted_at=datetime.utcnow().isoformat()
            )

        except AMQPError as e:
            logger.error("Erro AMQP ao deletar exchange",
                        exchange_name=params.exchange_name,
                        error=str(e))
            raise ValueError(f"Erro AMQP ao deletar exchange: {e}") from e
        except Exception as e:
            logger.error("Erro ao deletar exchange",
                        exchange_name=params.exchange_name,
                        error=str(e))
            raise ValueError(f"Erro ao deletar exchange: {e}") from e

    def bind_queue(self, params: ExchangeBindSchema) -> ExchangeBindResponse:
        """
        Vincula uma fila a um exchange.

        Args:
            params: Parâmetros de binding

        Returns:
            Resposta com informações do binding
        """
        logger = log_mcp_request("exchange_bind", params.connection_id)

        try:
            # Obter conexão
            connection = connection_manager.get_connection(params.connection_id)
            if not connection:
                raise ValueError(f"Conexão {params.connection_id} não encontrada")

            pika_connection = connection_manager.get_pika_connection(params.connection_id)
            if not pika_connection or pika_connection.is_closed:
                raise ValueError(f"Conexão {params.connection_id} não está ativa")

            # Criar canal
            channel = pika_connection.channel()

            # Criar binding
            logger.info("Criando binding",
                       exchange_name=params.exchange_name,
                       queue_name=params.queue_name,
                       routing_key=params.routing_key)

            channel.queue_bind(
                exchange=params.exchange_name,
                queue=params.queue_name,
                routing_key=params.routing_key,
                arguments=params.arguments
            )

            # Criar informação do binding
            binding = BindingInfo(
                exchange_name=params.exchange_name,
                queue_name=params.queue_name,
                routing_key=params.routing_key,
                arguments=params.arguments,
                created_at=datetime.utcnow().isoformat()
            )

            # Armazenar binding
            if params.exchange_name not in self.bindings:
                self.bindings[params.exchange_name] = []
            self.bindings[params.exchange_name].append(binding)

            logger.info("Binding criado com sucesso",
                       exchange_name=params.exchange_name,
                       queue_name=params.queue_name)

            return ExchangeBindResponse(
                binding_created=True,
                exchange_name=params.exchange_name,
                queue_name=params.queue_name,
                routing_key=params.routing_key,
                bound_at=binding.created_at
            )

        except AMQPError as e:
            logger.error("Erro AMQP ao criar binding",
                        exchange_name=params.exchange_name,
                        error=str(e))
            raise ValueError(f"Erro AMQP ao criar binding: {e}") from e
        except Exception as e:
            logger.error("Erro ao criar binding",
                        exchange_name=params.exchange_name,
                        error=str(e))
            raise ValueError(f"Erro ao criar binding: {e}") from e

    def unbind_queue(self, params: ExchangeUnbindSchema) -> ExchangeUnbindResponse:
        """
        Desvincula uma fila de um exchange.

        Args:
            params: Parâmetros de unbinding

        Returns:
            Resposta com informações do unbinding
        """
        logger = log_mcp_request("exchange_unbind", params.connection_id)

        try:
            # Obter conexão
            connection = connection_manager.get_connection(params.connection_id)
            if not connection:
                raise ValueError(f"Conexão {params.connection_id} não encontrada")

            pika_connection = connection_manager.get_pika_connection(params.connection_id)
            if not pika_connection or pika_connection.is_closed:
                raise ValueError(f"Conexão {params.connection_id} não está ativa")

            # Criar canal
            channel = pika_connection.channel()

            # Remover binding
            logger.info("Removendo binding",
                       exchange_name=params.exchange_name,
                       queue_name=params.queue_name,
                       routing_key=params.routing_key)

            channel.queue_unbind(
                exchange=params.exchange_name,
                queue=params.queue_name,
                routing_key=params.routing_key,
                arguments=params.arguments
            )

            # Remover binding do cache
            if params.exchange_name in self.bindings:
                self.bindings[params.exchange_name] = [
                    binding for binding in self.bindings[params.exchange_name]
                    if not (binding.queue_name == params.queue_name and
                           binding.routing_key == params.routing_key)
                ]

            logger.info("Binding removido com sucesso",
                       exchange_name=params.exchange_name,
                       queue_name=params.queue_name)

            return ExchangeUnbindResponse(
                binding_removed=True,
                exchange_name=params.exchange_name,
                queue_name=params.queue_name,
                routing_key=params.routing_key,
                unbound_at=datetime.utcnow().isoformat()
            )

        except AMQPError as e:
            logger.error("Erro AMQP ao remover binding",
                        exchange_name=params.exchange_name,
                        error=str(e))
            raise ValueError(f"Erro AMQP ao remover binding: {e}") from e
        except Exception as e:
            logger.error("Erro ao remover binding",
                        exchange_name=params.exchange_name,
                        error=str(e))
            raise ValueError(f"Erro ao remover binding: {e}") from e

    def get_exchange(self, connection_id: str, exchange_name: str) -> Exchange | None:
        """
        Obtém um exchange por nome.

        Args:
            connection_id: ID da conexão
            exchange_name: Nome do exchange

        Returns:
            Instância do exchange ou None se não encontrado
        """
        exchange_key = f"{connection_id}:{exchange_name}"
        return self.exchanges.get(exchange_key)

    def get_bindings(self, exchange_name: str) -> list[BindingInfo]:
        """
        Obtém os bindings de um exchange.

        Args:
            exchange_name: Nome do exchange

        Returns:
            Lista de bindings
        """
        return self.bindings.get(exchange_name, [])


# Instância global do gerenciador de exchanges
exchange_manager = ExchangeManager()


def exchange_create(params: dict[str, Any]) -> dict[str, Any]:
    """
    Ferramenta MCP para criar um exchange.

    Args:
        params: Parâmetros de criação do exchange

    Returns:
        Resposta da criação do exchange
    """
    schema = ExchangeCreateSchema(**params)
    response = exchange_manager.create_exchange(schema)
    return response.dict()


def exchange_delete(params: dict[str, Any]) -> dict[str, Any]:
    """
    Ferramenta MCP para deletar um exchange.

    Args:
        params: Parâmetros de deleção do exchange

    Returns:
        Resposta da deleção do exchange
    """
    schema = ExchangeDeleteSchema(**params)
    response = exchange_manager.delete_exchange(schema)
    return response.dict()


def exchange_bind(params: dict[str, Any]) -> dict[str, Any]:
    """
    Ferramenta MCP para vincular uma fila a um exchange.

    Args:
        params: Parâmetros de binding

    Returns:
        Resposta do binding
    """
    schema = ExchangeBindSchema(**params)
    response = exchange_manager.bind_queue(schema)
    return response.dict()


def exchange_unbind(params: dict[str, Any]) -> dict[str, Any]:
    """
    Ferramenta MCP para desvincular uma fila de um exchange.

    Args:
        params: Parâmetros de unbinding

    Returns:
        Resposta do unbinding
    """
    schema = ExchangeUnbindSchema(**params)
    response = exchange_manager.unbind_queue(schema)
    return response.dict()
