# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Service Hub.
Consumer-facing aggregation layer for services, DB interfaces, and runtime state.

Architecture:
- serviceHub delegates service resolution to serviceCenter (DI container)
- serviceHub owns DB interface initialization and runtime state
- serviceCenter knows nothing about serviceHub (one-way dependency)

Import-Regelwerk:
- Zentrale Module (wie dieses) duerfen KEINE Feature-Container importieren
- Feature-spezifische Services werden dynamisch geladen
- Shared Services werden via serviceCenter resolved
"""

import os
import importlib
import glob
from typing import Any, Optional, TYPE_CHECKING
import logging

from modules.datamodels.datamodelUam import User

if TYPE_CHECKING:
    from modules.datamodels.datamodelChat import ChatWorkflow

logger = logging.getLogger(__name__)

_FEATURES_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "features")


class PublicService:
    """Lightweight proxy exposing only public callable attributes of a target."""

    def __init__(self, target: Any, functionsOnly: bool = True, nameFilter=None):
        self._target = target
        self._functionsOnly = functionsOnly
        self._nameFilter = nameFilter

    def __getattr__(self, name: str):
        if name.startswith('_'):
            raise AttributeError(f"'{type(self._target).__name__}' attribute '{name}' is private")
        if self._nameFilter and not self._nameFilter(name):
            raise AttributeError(f"'{name}' not exposed by policy")
        attr = getattr(self._target, name)
        if self._functionsOnly and not callable(attr):
            raise AttributeError(f"'{name}' is not a function")
        return attr

    def __dir__(self):
        return sorted([
            n for n in dir(self._target)
            if not n.startswith('_')
            and (not self._functionsOnly or callable(getattr(self._target, n, None)))
            and (self._nameFilter(n) if self._nameFilter else True)
        ])


class ServiceHub:
    """
    Consumer-facing aggregation of services, DB interfaces, and runtime state.

    Services are lazy-resolved via serviceCenter on first access.
    DB interfaces and runtime state are initialized eagerly.
    Feature services/interfaces are discovered dynamically from features/.
    """

    _SERVICE_CENTER_WRAPPING = {
        "ai": {"functionsOnly": False},
    }

    def __init__(self, user: User, workflow: "ChatWorkflow" = None, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None):
        self.user: User = user
        self.workflow = workflow
        self.mandateId: Optional[str] = mandateId
        self.featureInstanceId: Optional[str] = featureInstanceId
        self.currentUserPrompt: str = ""
        self.rawUserPrompt: str = ""

        from modules.serviceCenter.context import ServiceCenterContext
        self._serviceCenterContext = ServiceCenterContext(
            user=user,
            workflow=workflow,
            mandate_id=mandateId,
            feature_instance_id=featureInstanceId,
        )

        from modules.interfaces.interfaceDbApp import getInterface as getAppInterface
        self.interfaceDbApp = getAppInterface(user, mandateId=mandateId)

        from modules.interfaces.interfaceDbManagement import getInterface as getComponentInterface
        self.interfaceDbComponent = getComponentInterface(user, mandateId=mandateId)

        self.rbac = self.interfaceDbApp.rbac if self.interfaceDbApp else None

        from modules.interfaces.interfaceDbChat import getInterface as getChatInterface
        self.interfaceDbChat = getChatInterface(user, mandateId=mandateId, featureInstanceId=featureInstanceId)

        self._loadFeatureInterfaces()
        self._loadFeatureServices()

    def __getattr__(self, name: str):
        """Lazy-resolve services via serviceCenter on first access."""
        if name.startswith('_'):
            raise AttributeError(name)
        try:
            from modules.serviceCenter import getService
            service = getService(name, self._serviceCenterContext)
            wrapping = self._SERVICE_CENTER_WRAPPING.get(name, {})
            functionsOnly = wrapping.get("functionsOnly", True)
            wrapped = PublicService(service, functionsOnly=functionsOnly)
            setattr(self, name, wrapped)
            return wrapped
        except KeyError:
            raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")

    def _loadFeatureInterfaces(self):
        """Dynamically load interfaces from feature containers by filename pattern."""
        pattern = os.path.join(_FEATURES_DIR, "*", "interfaceFeature*.py")
        for filepath in glob.glob(pattern):
            try:
                featureDir = os.path.basename(os.path.dirname(filepath))
                filename = os.path.basename(filepath)[:-3]

                modulePath = f"modules.features.{featureDir}.{filename}"
                module = importlib.import_module(modulePath)

                if hasattr(module, "getInterface"):
                    interface = module.getInterface(self.user, mandateId=self.mandateId, featureInstanceId=self.featureInstanceId)
                    attrName = filename.replace("interfaceFeature", "interfaceDb")
                    setattr(self, attrName, interface)
                    logger.debug(f"Loaded interface: {attrName} from {modulePath}")
            except Exception as e:
                logger.debug(f"Could not load interface from {filepath}: {e}")

    def _loadFeatureServices(self):
        """Dynamically load services from feature containers by filename pattern."""
        pattern = os.path.join(_FEATURES_DIR, "*", "service*", "mainService*.py")
        for filepath in glob.glob(pattern):
            try:
                serviceDir = os.path.basename(os.path.dirname(filepath))
                featureDir = os.path.basename(os.path.dirname(os.path.dirname(filepath)))
                filename = os.path.basename(filepath)[:-3]

                modulePath = f"modules.features.{featureDir}.{serviceDir}.{filename}"
                module = importlib.import_module(modulePath)

                serviceClass = None
                for attrName in dir(module):
                    if attrName.endswith("Service") and not attrName.startswith("_"):
                        cls = getattr(module, attrName)
                        if isinstance(cls, type):
                            serviceClass = cls
                            break

                if serviceClass:
                    attrName = serviceDir.replace("service", "").lower()
                    if not attrName:
                        attrName = serviceDir.lower()

                    functionsOnly = attrName != "ai"

                    def _makeServiceResolver(hub):
                        def _resolver(depKey: str):
                            return getattr(hub, depKey)
                        return _resolver

                    import inspect
                    sig = inspect.signature(serviceClass.__init__)
                    paramCount = len([p for p in sig.parameters if p != 'self'])
                    if paramCount >= 2:
                        serviceInstance = serviceClass(self, _makeServiceResolver(self))
                    else:
                        serviceInstance = serviceClass(self)
                    setattr(self, attrName, PublicService(serviceInstance, functionsOnly=functionsOnly))
                    logger.debug(f"Loaded service: {attrName} from {modulePath}")
            except Exception as e:
                logger.debug(f"Could not load service from {filepath}: {e}")


# Backward-compatible alias
Services = ServiceHub


def getInterface(user: User, workflow: "ChatWorkflow" = None, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None) -> ServiceHub:
    """Get ServiceHub instance for the given user, mandate, and feature instance context."""
    return ServiceHub(user, workflow, mandateId=mandateId, featureInstanceId=featureInstanceId)
