from __future__ import annotations

import importlib
from datetime import datetime, timezone
from typing import Any
from uuid import UUID

from src.connectors.base import BaseInventoryConnector, ConnectorResult
from src.core.exceptions import ConnectorException
from src.core.logging import get_logger
from src.core.metrics import CONNECTOR_OPERATIONS
from src.domain.entities import ConnectorStatus, ERPType
from src.infrastructure.cache.redis_cache import redis_cache
from src.infrastructure.database.repositories import ConnectorConfigRepository, ConnectorRepository
from src.infrastructure.messaging.rabbitmq import rabbitmq_client

logger = get_logger("connector_registry")

_BUILTIN_CONNECTORS: dict[str, str] = {
    ERPType.MOCK.value: "src.connectors.mock.mock_connector.MockConnector",
    ERPType.FARMATIC.value: "src.connectors.farmatic.farmatic_connector.FarmaticConnector",
    ERPType.CUSTOM_REST.value: "src.connectors.rest.rest_connector.RESTConnector",
}


class ConnectorRegistry:
    _instance: ConnectorRegistry | None = None
    _connectors: dict[str, BaseInventoryConnector]
    _instances: dict[UUID, BaseInventoryConnector]

    def __init__(self) -> None:
        self._connectors = {}
        self._instances = {}

    @classmethod
    def get_registry(cls) -> ConnectorRegistry:
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    def register(self, connector_type: str, connector: BaseInventoryConnector) -> None:
        self._connectors[connector_type] = connector
        logger.info("connector_registered", extra={"connector_type": connector_type})

    def unregister(self, connector_type: str) -> None:
        self._connectors.pop(connector_type, None)
        logger.info("connector_unregistered", extra={"connector_type": connector_type})

    def get_connector_class(self, connector_type: str) -> type[BaseInventoryConnector]:
        if connector_type in self._connectors:
            return type(self._connectors[connector_type])

        class_path = _BUILTIN_CONNECTORS.get(connector_type)
        if not class_path:
            raise ConnectorException(connector_type, f"No connector registered for type '{connector_type}'")

        try:
            module_path, class_name = class_path.rsplit(".", 1)
            module = importlib.import_module(module_path)
            cls = getattr(module, class_name)
            self._connectors[connector_type] = cls()
            return cls
        except (ImportError, AttributeError) as e:
            raise ConnectorException(connector_type, f"Failed to load connector '{connector_type}': {e}") from e

    def create_instance(self, connector_type: str, config: dict[str, Any] | None = None) -> BaseInventoryConnector:
        cls = self.get_connector_class(connector_type)
        instance = cls()
        if config and hasattr(instance, "configure"):
            instance.configure(config)
        return instance

    def get_or_create_instance(self, connector_id: UUID, connector_type: str, config: dict[str, Any] | None = None) -> BaseInventoryConnector:
        if connector_id in self._instances:
            return self._instances[connector_id]
        instance = self.create_instance(connector_type, config)
        self._instances[connector_id] = instance
        return instance

    def remove_instance(self, connector_id: UUID) -> None:
        self._instances.pop(connector_id, None)

    def list_registered_types(self) -> list[str]:
        all_types = set(self._connectors.keys()) | set(_BUILTIN_CONNECTORS.keys())
        return sorted(all_types)

    @staticmethod
    async def record_heartbeat(repo: ConnectorRepository, connector_id: UUID) -> None:
        entity = await repo.get_by_id(connector_id)
        if entity:
            entity.last_heartbeat_at = datetime.now(timezone.utc)
            entity.status = ConnectorStatus.ACTIVE
            entity.last_error = None
            await repo.update(entity)

    @staticmethod
    async def record_error(repo: ConnectorRepository, connector_id: UUID, error: str) -> None:
        entity = await repo.get_by_id(connector_id)
        if entity:
            entity.last_error = error
            entity.status = ConnectorStatus.ERROR
            await repo.update(entity)


connector_registry = ConnectorRegistry.get_registry()
