from __future__ import annotations

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

from src.connectors.base import ConnectorResult
from src.connectors.registry import connector_registry
from src.connectors.sdk.context import ConnectorContext
from src.core.exceptions import ConnectorException
from src.core.logging import get_logger
from src.domain.entities import ConnectorConfigEntity, ConnectorEntity, ConnectorStatus
from src.domain.interfaces import IConnectorConfigRepository, IConnectorRepository
from src.infrastructure.messaging.rabbitmq import rabbitmq_client

logger = get_logger("connector_service")


class ConnectorService:
    def __init__(self, connector_repo: IConnectorRepository, config_repo: IConnectorConfigRepository) -> None:
        self._connector_repo = connector_repo
        self._config_repo = config_repo

    async def create_connector(self, entity: ConnectorEntity, configs: list[ConnectorConfigEntity] | None = None) -> ConnectorEntity:
        created = await self._connector_repo.create(entity)

        if configs:
            for cfg in configs:
                cfg.connector_id = created.id
                await self._config_repo.upsert(cfg)

        await self._publish_event("connector.created", created.id, {"connector_type": created.connector_type, "pharmacy_id": str(created.pharmacy_id), "erp_system_id": str(created.erp_system_id), "status": created.status})
        logger.info("connector_created", extra={"connector_id": str(created.id), "connector_type": created.connector_type})
        return created

    async def get_connector(self, connector_id: UUID) -> ConnectorEntity | None:
        return await self._connector_repo.get_by_id(connector_id)

    async def list_connectors(self, *, erp_system_id: UUID | None = None, pharmacy_id: UUID | None = None, status: str | None = None, offset: int = 0, limit: int = 50) -> tuple[list[ConnectorEntity], int]:
        return await self._connector_repo.list_connectors(erp_system_id=erp_system_id, pharmacy_id=pharmacy_id, status=status, offset=offset, limit=limit)

    async def update_connector(self, entity: ConnectorEntity) -> ConnectorEntity:
        updated = await self._connector_repo.update(entity)
        await self._publish_event("connector.updated", updated.id, {"connector_type": updated.connector_type, "status": updated.status})
        return updated

    async def delete_connector(self, connector_id: UUID) -> bool:
        connector = await self._connector_repo.get_by_id(connector_id)
        deleted = await self._connector_repo.delete(connector_id)
        if deleted:
            connector_registry.remove_instance(connector_id)
            if connector:
                await self._publish_event("connector.deleted", connector_id, {"connector_type": connector.connector_type, "pharmacy_id": str(connector.pharmacy_id)})
        return deleted

    async def test_connection(self, connector_id: UUID) -> ConnectorResult:
        connector_entity = await self._connector_repo.get_by_id(connector_id)
        if not connector_entity:
            raise ConnectorException("unknown", f"Connector '{connector_id}' not found")

        configs = await self._config_repo.list_by_connector(connector_id)
        config_dict = {c.key: c.value for c in configs}
        secrets_dict = {c.key: c.value for c in configs if c.encrypted}

        context = ConnectorContext(
            connector_id=connector_entity.id,
            pharmacy_id=connector_entity.pharmacy_id,
            erp_system_id=connector_entity.erp_system_id,
            connector_type=connector_entity.connector_type,
            config=config_dict,
            secrets=secrets_dict,
        )

        instance = connector_registry.get_or_create_instance(connector_id, connector_entity.connector_type, config_dict)
        if hasattr(instance, "set_context"):
            instance.set_context(context)

        result = await instance.heartbeat(connector_entity.pharmacy_id)

        if result.success:
            connector_entity.status = ConnectorStatus.ACTIVE
            connector_entity.last_heartbeat_at = datetime.now(timezone.utc)
            connector_entity.last_error = None
            await self._connector_repo.update(connector_entity)
            await self._publish_event("connector.heartbeat", connector_id, {"status": "healthy"})
        else:
            connector_entity.status = ConnectorStatus.ERROR
            connector_entity.last_error = result.error
            await self._connector_repo.update(connector_entity)
            await self._publish_event("connector.error", connector_id, {"error": result.error})

        return result

    async def deploy_connector(self, connector_id: UUID) -> ConnectorEntity:
        entity = await self._connector_repo.get_by_id(connector_id)
        if not entity:
            raise ConnectorException("unknown", f"Connector '{connector_id}' not found")

        if entity.status == ConnectorStatus.ACTIVE:
            raise ConnectorException(entity.connector_type, "Connector is already active. Deactivate first to redeploy.")

        configs = await self._config_repo.list_by_connector(connector_id)
        config_dict = {c.key: c.value for c in configs}

        connector_registry.remove_instance(connector_id)
        connector_registry.get_or_create_instance(connector_id, entity.connector_type, config_dict)

        entity.status = ConnectorStatus.ACTIVE
        entity.last_heartbeat_at = datetime.now(timezone.utc)
        entity.last_error = None
        updated = await self._connector_repo.update(entity)

        await self._publish_event("connector.deployed", connector_id, {"connector_type": entity.connector_type, "status": "active"})
        logger.info("connector_deployed", extra={"connector_id": str(connector_id)})
        return updated

    async def deactivate_connector(self, connector_id: UUID) -> ConnectorEntity:
        entity = await self._connector_repo.get_by_id(connector_id)
        if not entity:
            raise ConnectorException("unknown", f"Connector '{connector_id}' not found")

        connector_registry.remove_instance(connector_id)

        entity.status = ConnectorStatus.INACTIVE
        updated = await self._connector_repo.update(entity)

        await self._publish_event("connector.deactivated", connector_id, {"connector_type": entity.connector_type, "status": "inactive"})
        return updated

    async def get_configs(self, connector_id: UUID) -> list[ConnectorConfigEntity]:
        return await self._config_repo.list_by_connector(connector_id)

    async def upsert_config(self, entity: ConnectorConfigEntity) -> ConnectorConfigEntity:
        return await self._config_repo.upsert(entity)

    async def delete_config(self, config_id: UUID) -> bool:
        return await self._config_repo.delete(config_id)

    async def _publish_event(self, event_type: str, connector_id: UUID, payload: dict[str, Any]) -> None:
        try:
            routing_key = f"connector.{event_type.split('.', 1)[1]}" if "." in event_type else f"connector.{event_type}"
            full_payload = {"event_type": event_type, "connector_id": str(connector_id), "timestamp": datetime.now(timezone.utc).isoformat(), **payload}
            await rabbitmq_client.publish(routing_key, full_payload)
        except Exception as exc:
            logger.warning("connector_event_publish_failed", extra={"event_type": event_type, "error": str(exc)})
