# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
System Routes - Navigation and system-level API endpoints.

Navigation API Konzept:
- Single Source of Truth für Navigation im Gateway
- UI rendert nur was es erhält (keine Permission-Logik im UI)
- Keine Icons in API Response - UI mappt selbst via uiComponent
- Blocks statt Sections mit order auf allen Ebenen
"""

import logging
import time
from collections import Counter
from typing import Dict, List, Any, Optional, Set
from fastapi import APIRouter, Depends, Request
from slowapi import Limiter
from slowapi.util import get_remote_address

from modules.auth.authentication import getRequestContext, RequestContext
from modules.system.mainSystem import NAVIGATION_SECTIONS, _objectKeyToUiComponent
from modules.shared.i18nRegistry import resolveText, t
from modules.interfaces.interfaceDbApp import getRootInterface
from modules.interfaces.interfaceFeatures import getFeatureInterface
from modules.datamodels.datamodelRbac import AccessRule, AccessRuleContext
from modules.datamodels.datamodelMembership import UserMandate, UserMandateRole, FeatureAccess, FeatureAccessRole

logger = logging.getLogger(__name__)
limiter = Limiter(key_func=get_remote_address)

# Main system router (for other system endpoints if needed)
router = APIRouter(prefix="/api/system", tags=["System"])

# Navigation router at /api/navigation (gemäss Navigation-API-Konzept)
navigationRouter = APIRouter(prefix="/api", tags=["Navigation"])


def _getUserRoleIds(userId: str) -> List[str]:
    """Get all role IDs for a user across all their mandates."""
    rootInterface = getRootInterface()
    roleIds = []
    
    # Get UserMandates as Pydantic models
    userMandates = rootInterface.getUserMandates(userId)
    
    for um in userMandates:
        if not um.enabled:
            continue
        mandateRoleIds = rootInterface.getRoleIdsForUserMandate(str(um.id))
        for rid in mandateRoleIds:
            if rid not in roleIds:
                roleIds.append(rid)
    
    return roleIds


def _checkUiPermission(roleIds: List[str], objectKey: str) -> bool:
    """Check if any of the given roles has view permission for the UI object."""
    if not roleIds:
        return False
    
    rootInterface = getRootInterface()
    
    for roleId in roleIds:
        # Get UI rules for this role (returns Pydantic AccessRule models)
        rules = rootInterface.getAccessRules(roleId=roleId, context=AccessRuleContext.UI)
        
        for rule in rules:
            if not rule.view:
                continue
            
            # Global rule (item=None) grants access to all UI
            if rule.item is None:
                return True
            
            # Exact match
            if rule.item == objectKey:
                return True
            
            # Wildcard match (e.g., ui.admin.* matches ui.admin.mandates)
            if rule.item.endswith(".*"):
                prefix = rule.item[:-2]
                if objectKey.startswith(prefix):
                    return True
    
    return False


# =============================================================================
# Navigation API (gemäss Navigation-API-Konzept)
# Endpoint: GET /api/navigation
# =============================================================================

def _getFeatureUiObjects(featureCode: str) -> List[Dict[str, Any]]:
    """
    Get UI objects for a feature from its main module.
    Returns list of UI objects with objectKey, label, meta (including path).
    """
    try:
        # Dynamic import based on feature code
        if featureCode == "trustee":
            from modules.features.trustee.mainTrustee import UI_OBJECTS
            return UI_OBJECTS
        elif featureCode == "realestate":
            from modules.features.realEstate.mainRealEstate import UI_OBJECTS
            return UI_OBJECTS
        elif featureCode == "graphicalEditor":
            from modules.features.graphicalEditor.mainGraphicalEditor import UI_OBJECTS
            return UI_OBJECTS
        elif featureCode == "teamsbot":
            from modules.features.teamsbot.mainTeamsbot import UI_OBJECTS
            return UI_OBJECTS
        elif featureCode == "neutralization":
            from modules.features.neutralization.mainNeutralization import UI_OBJECTS
            return UI_OBJECTS
        elif featureCode == "chatbot":
            from modules.features.chatbot.mainChatbot import UI_OBJECTS
            return UI_OBJECTS
        elif featureCode == "commcoach":
            from modules.features.commcoach.mainCommcoach import UI_OBJECTS
            return UI_OBJECTS
        elif featureCode == "workspace":
            from modules.features.workspace.mainWorkspace import UI_OBJECTS
            return UI_OBJECTS
        else:
            logger.debug(f"Skipping removed feature code: {featureCode}")
            return []
    except ImportError as e:
        logger.error(f"Failed to import UI_OBJECTS for feature {featureCode}: {e}")
        return []


def _buildDynamicBlock(
    userId: str,
    isSysAdmin: bool
) -> Optional[Dict[str, Any]]:
    """
    Build the dynamic features block with mandates, features, and instances.
    View and feature labels use resolveText() for the current request language
    (same contract as static navigation items in _formatBlockItem).
    Instance and mandate display names are user-defined and passed through as-is.
    
    Returns None if user has no feature instances.
    """
    try:
        rootInterface = getRootInterface()
        featureInterface = getFeatureInterface(rootInterface.db)
        
        # Get all feature accesses for this user
        featureAccesses = rootInterface.getFeatureAccessesForUser(userId)
        
        if not featureAccesses:
            return None
        
        # Build hierarchical structure: mandate -> feature -> instances
        mandatesMap: Dict[str, Dict[str, Any]] = {}
        featuresMap: Dict[str, Dict[str, Any]] = {}  # key: mandateId_featureCode
        
        mandateOrder = 10
        for access in featureAccesses:
            if not access.enabled:
                continue
            
            instance = featureInterface.getFeatureInstance(str(access.featureInstanceId))
            if not instance or not instance.enabled:
                continue
            
            # Get mandate info
            mandateId = str(instance.mandateId)
            if mandateId not in mandatesMap:
                mandate = rootInterface.getMandate(mandateId)
                if not mandate or not getattr(mandate, "enabled", True):
                    continue
                mandateName = (mandate.label or mandate.name) if mandate else mandateId
                mandatesMap[mandateId] = {
                    "id": mandateId,
                    "uiLabel": mandateName,
                    "order": mandateOrder,
                    "features": []
                }
                mandateOrder += 10
            
            # Get feature info
            featureKey = f"{mandateId}_{instance.featureCode}"
            if featureKey not in featuresMap:
                feature = featureInterface.getFeature(instance.featureCode)
                
                # Handle featureLabel — TextMultilingual dict, plain str (German key), or legacy object
                if feature and hasattr(feature, 'label'):
                    featureLabel = feature.label
                    if hasattr(featureLabel, 'model_dump'):
                        featureLabel = featureLabel.model_dump()
                    elif isinstance(featureLabel, str):
                        pass
                    elif not isinstance(featureLabel, dict):
                        featureLabel = {
                            "de": getattr(featureLabel, 'de', instance.featureCode),
                            "en": getattr(featureLabel, 'en', instance.featureCode),
                        }
                else:
                    featureLabel = {"de": instance.featureCode, "en": instance.featureCode}
                
                resolvedFeatureLabel = resolveText(featureLabel)
                
                featuresMap[featureKey] = {
                    "uiComponent": f"feature.{instance.featureCode}",
                    "uiLabel": resolvedFeatureLabel,
                    "order": 10,
                    "instances": [],
                    "_mandateId": mandateId,
                    "_featureCode": instance.featureCode
                }
            
            # Get user's permissions for this instance to filter views
            permissions = _getInstanceViewPermissions(rootInterface, userId, str(instance.id), isSysAdmin)
            
            # Get feature UI objects to build views
            featureUiObjects = _getFeatureUiObjects(instance.featureCode)
            
            # Build views for this instance
            views = []
            viewOrder = 10
            for uiObj in featureUiObjects:
                objectKey = uiObj.get("objectKey", "")
                # Extract view name from objectKey for path building
                viewName = objectKey.split(".")[-1] if objectKey else ""
                
                # Check permission using full objectKey (as per Navigation-API-Konzept)
                if not isSysAdmin and not permissions.get("_all") and not permissions.get(objectKey, False):
                    continue
                
                # Skip admin-only views for non-admins
                meta = uiObj.get("meta", {})
                if meta.get("admin_only") and not isSysAdmin and not permissions.get("isAdmin", False):
                    continue
                
                # Build path for this view
                viewPath = f"/mandates/{mandateId}/{instance.featureCode}/{instance.id}/{viewName}"
                
                rawViewLabel = uiObj.get("label")
                uiLabel = resolveText(rawViewLabel) if rawViewLabel not in (None, "") else ""
                
                views.append({
                    "uiComponent": f"page.feature.{instance.featureCode}.{viewName}",
                    "uiLabel": uiLabel,
                    "uiPath": viewPath,
                    "order": viewOrder,
                    "objectKey": objectKey
                })
                viewOrder += 10
            
            # Sort views by order
            views.sort(key=lambda v: v["order"])
            
            featuresMap[featureKey]["instances"].append({
                "id": str(instance.id),
                "uiLabel": instance.label,
                "featureCode": instance.featureCode,
                "order": 10,
                "views": views,
                "isAdmin": permissions.get("isAdmin", False),
            })
        
        # Build final structure
        for featureKey, featureData in featuresMap.items():
            mandateId = featureData.pop("_mandateId")
            featureData.pop("_featureCode")
            mandatesMap[mandateId]["features"].append(featureData)
        
        # Sort features within each mandate
        for mandate in mandatesMap.values():
            mandate["features"].sort(key=lambda f: f["order"])
        
        # Convert to list and sort by order
        mandatesList = list(mandatesMap.values())
        mandatesList.sort(key=lambda m: m["order"])
        
        if not mandatesList:
            return None
        
        return {
            "type": "dynamic",
            "id": "features",
            "title": "MEINE FEATURES",
            "order": 15,  # Between system (10) and workflows (20)
            "mandates": mandatesList
        }
        
    except Exception as e:
        logger.error(f"Error building dynamic block: {e}")
        return None


def _getInstanceViewPermissions(
    rootInterface,
    userId: str,
    instanceId: str,
    isSysAdmin: bool
) -> Dict[str, Any]:
    """
    Get view permissions for a user in a feature instance.
    Returns dict with view names as keys and True/False as values.
    Also includes "_all" if user has global view access.
    """
    if isSysAdmin:
        return {"_all": True, "isAdmin": True}
    
    permissions = {"_all": False, "isAdmin": False}
    
    try:
        # Get FeatureAccess for this user and instance (Pydantic model)
        featureAccess = rootInterface.getFeatureAccess(userId, instanceId)
        
        if not featureAccess:
            return permissions
        
        # Get role IDs via interface method
        roleIds = rootInterface.getRoleIdsForFeatureAccess(str(featureAccess.id))
        
        if not roleIds:
            return permissions
        
        # Check if user has admin role
        for roleId in roleIds:
            role = rootInterface.getRole(roleId)
            if role and "admin" in role.roleLabel.lower():
                permissions["isAdmin"] = True
                break
        
        # Get UI permissions from AccessRules (Pydantic models)
        for roleId in roleIds:
            accessRules = rootInterface.getAccessRules(roleId=roleId, context=AccessRuleContext.UI)
            
            logger.debug(f"_getInstanceViewPermissions: roleId={roleId}, UI rules count={len(accessRules)}")
            
            for rule in accessRules:
                if not rule.view:
                    continue
                
                logger.debug(f"_getInstanceViewPermissions: rule item={rule.item}, view={rule.view}")
                
                if rule.item is None:
                    # item=None means all views
                    permissions["_all"] = True
                else:
                    # Store full objectKey as per Navigation-API-Konzept
                    permissions[rule.item] = True
        
        logger.debug(f"_getInstanceViewPermissions: final permissions={permissions}")
        return permissions
        
    except Exception as e:
        logger.debug(f"Error getting instance view permissions: {e}")
        return permissions  # Fail-safe: no permissions on error


def _filterItems(
    items: List[Dict[str, Any]],
    isSysAdmin: bool,
    roleIds: List[str],
    hasGlobalPermission: bool
) -> List[Dict[str, Any]]:
    """Filter and format navigation items based on permissions."""
    filteredItems = []
    for item in items:
        if item.get("adminOnly") and not isSysAdmin:
            if not hasGlobalPermission and not _checkUiPermission(roleIds, item["objectKey"]):
                continue
        if item.get("sysAdminOnly") and not isSysAdmin:
            continue
        if item.get("public"):
            filteredItems.append(_formatBlockItem(item))
            continue
        if isSysAdmin:
            filteredItems.append(_formatBlockItem(item))
            continue
        if hasGlobalPermission or _checkUiPermission(roleIds, item["objectKey"]):
            filteredItems.append(_formatBlockItem(item))
    filteredItems.sort(key=lambda i: i["order"])
    return filteredItems


def _buildStaticBlocks(
    isSysAdmin: bool,
    roleIds: List[str],
    hasGlobalPermission: bool
) -> List[Dict[str, Any]]:
    """
    Build static navigation blocks from NAVIGATION_SECTIONS.
    
    Keys are registered at import time via t() in mainSystem.py.
    At request time, resolveText() translates them to the current language.
    """
    blocks = []
    
    for section in NAVIGATION_SECTIONS:
        if section.get("adminOnly") and not isSysAdmin:
            continue
        
        hasSubgroups = "subgroups" in section
        hasItems = "items" in section and len(section["items"]) > 0

        if hasSubgroups:
            filteredSubgroups = []
            for subgroup in section["subgroups"]:
                subItems = _filterItems(
                    subgroup.get("items", []), isSysAdmin, roleIds, hasGlobalPermission
                )
                if subItems:
                    filteredSubgroups.append({
                        "id": subgroup["id"],
                        "title": resolveText(subgroup["title"]),
                        "order": subgroup.get("order", 50),
                        "items": subItems,
                    })

            filteredSubgroups.sort(key=lambda s: s["order"])

            topLevelItems = []
            if hasItems:
                topLevelItems = _filterItems(
                    section["items"], isSysAdmin, roleIds, hasGlobalPermission
                )

            if filteredSubgroups or topLevelItems:
                blocks.append({
                    "type": "static",
                    "id": section["id"],
                    "title": resolveText(section["title"]),
                    "order": section.get("order", 50),
                    "items": topLevelItems,
                    "subgroups": filteredSubgroups,
                })
        else:
            filteredItems = _filterItems(
                section.get("items", []), isSysAdmin, roleIds, hasGlobalPermission
            )

            if filteredItems:
                blocks.append({
                    "type": "static",
                    "id": section["id"],
                    "title": resolveText(section["title"]),
                    "order": section.get("order", 50),
                    "items": filteredItems,
                })
    
    return blocks


def _formatBlockItem(item: Dict[str, Any]) -> Dict[str, Any]:
    """Format a navigation item for the API response."""
    objectKey = item["objectKey"]
    uiComponent = _objectKeyToUiComponent(objectKey)
    
    return {
        "uiComponent": uiComponent,
        "uiLabel": resolveText(item["label"]),
        "uiPath": item["path"],
        "order": item.get("order", 50),
        "objectKey": objectKey,
    }


@navigationRouter.get("/navigation")
@limiter.limit("60/minute")
def get_navigation(
    request: Request,
    reqContext: RequestContext = Depends(getRequestContext)
) -> Dict[str, Any]:
    """
    Get unified navigation structure with blocks.
    
    Static items and dynamic feature/view labels are resolved with resolveText()
    for the current request language. User-defined instance/mandate names are raw.
    
    Endpoint: GET /api/navigation
    """
    try:
        isSysAdmin = reqContext.hasSysAdminRole
        userId = str(reqContext.user.id) if reqContext.user else None
        
        # Get user's role IDs for permission checking
        roleIds = []
        if userId and not isSysAdmin:
            roleIds = _getUserRoleIds(userId)
        
        # Check if user has global UI permission
        hasGlobalPermission = isSysAdmin
        if not hasGlobalPermission and roleIds:
            hasGlobalPermission = _checkUiPermission(roleIds, "_global_check")
        
        # Build static blocks from NAVIGATION_SECTIONS
        blocks = _buildStaticBlocks(isSysAdmin, roleIds, hasGlobalPermission)
        
        # Build dynamic block (features) if user has feature instances
        if userId:
            dynamicBlock = _buildDynamicBlock(userId, isSysAdmin)
            if dynamicBlock:
                blocks.append(dynamicBlock)
        
        # Sort all blocks by order
        blocks.sort(key=lambda b: b["order"])
        
        return {
            "blocks": blocks,
        }
        
    except Exception as e:
        logger.error(f"Error getting navigation: {e}")
        return {
            "blocks": [],
            "error": str(e),
        }


# =============================================================================
# AI models (integrations overview)
# =============================================================================


def _buildIntegrationsOverviewPayload(userId: str, user=None) -> Dict[str, Any]:
    """
    Single payload for the Integrations architecture page: real UserConnections,
    DataSource / FeatureDataSource rows, trustee accounting bindings, AICore
    connector modules (not individual models), extractor extensions and renderer
    formats from registries, platform infra tools, and live KPI stats.
    """
    root = getRootInterface()
    out: Dict[str, Any] = {
        "aicoreModules": [],
        "infraTools": [],
        "extractorExtensions": [],
        "extractorClasses": [],
        "rendererFormats": [],
        "rendererClasses": [],
        "dataLayerItems": [],
        "liveStats": {},
        "errors": [],
    }

    _PROVIDER_LABELS = {
        "anthropic": "Anthropic (Claude)",
        "openai": "OpenAI (GPT)",
        "mistral": "Mistral (Le Chat)",
        "perplexity": "Perplexity",
        "tavily": "Tavily (Websuche)",
        "privatellm": "Private LLM",
        "internal": "Intern",
    }

    # --- AICore: one entry per connector module + model counts ---
    try:
        from modules.aicore.aicoreModelRegistry import modelRegistry

        modelRegistry.ensureConnectorsRegistered()
        modelRegistry.refreshModels(force=False)
        counts = Counter()
        for m in modelRegistry.getModels():
            if not getattr(m, "isAvailable", True):
                continue
            counts[str(getattr(m, "connectorType", "") or "")] += 1
        modules: List[Dict[str, Any]] = []
        for conn in modelRegistry.discoverConnectors():
            ct = conn.getConnectorType()
            modules.append(
                {
                    "connectorType": ct,
                    "label": _PROVIDER_LABELS.get(ct, ct),
                    "modelCount": int(counts.get(ct, 0)),
                }
            )
        out["aicoreModules"] = modules
    except Exception as e:
        logger.error(f"integrations-overview aicore: {e}")
        out["errors"].append(f"aicore: {e}")

    # --- Extractors (registered extensions, unique + per-class rows) ---
    try:
        from modules.serviceCenter.services.serviceExtraction.mainServiceExtraction import ExtractionService
        from modules.serviceCenter.services.serviceExtraction.subRegistry import ExtractorRegistry

        if ExtractionService._sharedExtractorRegistry is None:
            ExtractionService._sharedExtractorRegistry = ExtractorRegistry()
        reg = ExtractionService._sharedExtractorRegistry
        ext_map = reg.getExtensionToMimeMap()
        uniq = sorted({str(k).upper() for k in ext_map.keys() if k and "." not in str(k)})
        out["extractorExtensions"] = uniq

        seen_ext: Set[int] = set()
        class_rows: List[Dict[str, Any]] = []
        for extractor in reg._map.values():
            eid = id(extractor)
            if eid in seen_ext:
                continue
            seen_ext.add(eid)
            if not hasattr(extractor, "getSupportedExtensions"):
                continue
            raw_exts = extractor.getSupportedExtensions()
            if not raw_exts:
                continue
            norm = sorted({str(x).lstrip(".").lower() for x in raw_exts if x})
            if norm:
                class_rows.append({"className": extractor.__class__.__name__, "extensions": norm})
        class_rows.sort(key=lambda r: r["className"])
        out["extractorClasses"] = class_rows
        fb = getattr(reg, "_fallback", None)
        if fb and hasattr(fb, "getSupportedExtensions") and id(fb) not in seen_ext:
            raw_exts = fb.getSupportedExtensions()
            if raw_exts:
                norm = sorted({str(x).lstrip(".").lower() for x in raw_exts if x})
                if norm:
                    out["extractorClasses"].append({"className": fb.__class__.__name__, "extensions": norm})
                    out["extractorClasses"].sort(key=lambda r: r["className"])
    except Exception as e:
        logger.error(f"integrations-overview extractors: {e}")
        out["errors"].append(f"extractors: {e}")

    # --- Renderers (registered output formats + per-class rows) ---
    try:
        from modules.serviceCenter.services.serviceGeneration.renderers.registry import getSupportedFormats, getRendererInfo

        out["rendererFormats"] = sorted(getSupportedFormats())

        by_renderer_class: Dict[str, Dict[str, Any]] = {}
        for composite_key, meta in getRendererInfo().items():
            cn = meta.get("class_name") or ""
            if not cn:
                continue
            fmt = composite_key.split(":")[0] if ":" in composite_key else composite_key
            if cn not in by_renderer_class:
                by_renderer_class[cn] = {"className": cn, "formats": set()}
            by_renderer_class[cn]["formats"].add(fmt)
        renderer_rows = [
            {"className": d["className"], "formats": sorted(d["formats"])}
            for _, d in sorted(by_renderer_class.items(), key=lambda x: x[0])
        ]
        out["rendererClasses"] = renderer_rows
    except Exception as e:
        logger.error(f"integrations-overview renderers: {e}")
        out["errors"].append(f"renderers: {e}")

    # --- Platform infra tools (only routes that exist in this deployment) ---
    out["infraTools"] = [
        {"id": "voice", "label": t("Voice / STT")},
    ]

    accessible_instance_ids: Set[str] = set()
    try:
        for access in root.getFeatureAccessesForUser(userId):
            if not getattr(access, "enabled", True):
                continue
            accessible_instance_ids.add(str(access.featureInstanceId))
    except Exception as e:
        logger.debug(f"integrations-overview feature accesses: {e}")

    # --- UserConnection (active only) ---
    try:
        from modules.datamodels.datamodelUam import ConnectionStatus

        for c in root.getUserConnections(userId):
            st = c.status
            st_val = st.value if hasattr(st, "value") else str(st)
            if st_val != ConnectionStatus.ACTIVE.value:
                continue
            dumped = c.model_dump(mode="json")
            dumped["kind"] = "userConnection"
            out["dataLayerItems"].append(dumped)
    except Exception as e:
        logger.error(f"integrations-overview connections: {e}")
        out["errors"].append(f"connections: {e}")

    # --- instance label lookup (shared by DataSource & Trustee blocks) ---
    _instLabelCache: Dict[str, str] = {}
    def _getInstanceLabel(iid: str) -> str:
        if iid in _instLabelCache:
            return _instLabelCache[iid]
        try:
            _fi = getFeatureInterface(root.db)
            inst = _fi.getFeatureInstance(iid)
            lbl = getattr(inst, "label", None) or getattr(inst, "uiLabel", None) or ""
            _instLabelCache[iid] = lbl
        except Exception:
            _instLabelCache[iid] = ""
        return _instLabelCache[iid]

    # --- DataSource & FeatureDataSource ---
    try:
        from modules.datamodels.datamodelDataSource import DataSource
        from modules.datamodels.datamodelFeatureDataSource import FeatureDataSource

        seen_ds: Set[str] = set()

        for row in root.db.getRecordset(DataSource, recordFilter={"userId": userId}) or []:
            rid = str(row.get("id", ""))
            if not rid or rid in seen_ds:
                continue
            seen_ds.add(rid)
            out["dataLayerItems"].append(
                {
                    "kind": "dataSource",
                    "id": rid,
                    "label": row.get("label") or row.get("displayPath") or rid,
                    "sourceType": row.get("sourceType") or "",
                    "featureInstanceId": row.get("featureInstanceId"),
                    "mandateId": row.get("mandateId"),
                    "connectionId": row.get("connectionId"),
                }
            )

        for iid in accessible_instance_ids:
            for row in root.db.getRecordset(DataSource, recordFilter={"featureInstanceId": iid}) or []:
                rid = str(row.get("id", ""))
                if not rid or rid in seen_ds:
                    continue
                seen_ds.add(rid)
                out["dataLayerItems"].append(
                    {
                        "kind": "dataSource",
                        "id": rid,
                        "label": row.get("label") or row.get("displayPath") or rid,
                        "sourceType": row.get("sourceType") or "",
                        "featureInstanceId": row.get("featureInstanceId"),
                        "mandateId": row.get("mandateId"),
                        "connectionId": row.get("connectionId"),
                    }
                )

        seen_fds: Set[str] = set()
        for row in root.db.getRecordset(FeatureDataSource, recordFilter={"userId": userId}) or []:
            rid = str(row.get("id", ""))
            if not rid or rid in seen_fds:
                continue
            seen_fds.add(rid)
            fds_iid = row.get("featureInstanceId") or ""
            out["dataLayerItems"].append(
                {
                    "kind": "featureDataSource",
                    "id": rid,
                    "label": row.get("label") or rid,
                    "featureCode": row.get("featureCode") or "",
                    "tableName": row.get("tableName") or "",
                    "featureInstanceId": fds_iid,
                    "mandateId": row.get("mandateId"),
                    "instanceLabel": _getInstanceLabel(fds_iid) if fds_iid else "",
                }
            )

        for iid in accessible_instance_ids:
            for row in root.db.getRecordset(FeatureDataSource, recordFilter={"featureInstanceId": iid}) or []:
                rid = str(row.get("id", ""))
                if not rid or rid in seen_fds:
                    continue
                seen_fds.add(rid)
                out["dataLayerItems"].append(
                    {
                        "kind": "featureDataSource",
                        "id": rid,
                        "label": row.get("label") or rid,
                        "featureCode": row.get("featureCode") or "",
                        "tableName": row.get("tableName") or "",
                        "featureInstanceId": iid,
                        "mandateId": row.get("mandateId"),
                        "instanceLabel": _getInstanceLabel(iid),
                    }
                )
    except Exception as e:
        logger.error(f"integrations-overview datasources: {e}")
        out["errors"].append(f"datasources: {e}")

    # --- Trustee accounting systems (configured integrations per instance) ---
    try:
        from modules.features.trustee.datamodelFeatureTrustee import TrusteeAccountingConfig

        fi = getFeatureInterface(root.db)
        seen_acc: Set[str] = set()
        for iid in accessible_instance_ids:
            inst = fi.getFeatureInstance(iid)
            if not inst or inst.featureCode != "trustee":
                continue
            for row in root.db.getRecordset(
                TrusteeAccountingConfig,
                recordFilter={"featureInstanceId": iid, "isActive": True},
            ) or []:
                rid = str(row.get("id", ""))
                if not rid or rid in seen_acc:
                    continue
                seen_acc.add(rid)
                out["dataLayerItems"].append(
                    {
                        "kind": "trusteeAccounting",
                        "id": rid,
                        "featureInstanceId": iid,
                        "instanceLabel": getattr(inst, "label", None) or "",
                        "mandateId": str(getattr(inst, "mandateId", "") or ""),
                        "connectorType": row.get("connectorType") or "",
                        "displayLabel": row.get("displayLabel") or row.get("connectorType") or rid,
                    }
                )
    except Exception as e:
        logger.error(f"integrations-overview trustee accounting: {e}")
        out["errors"].append(f"trusteeAccounting: {e}")

    # --- Live stats (billing AI calls + workflow metrics) ---
    liveStats: Dict[str, Any] = {
        "aiCallCount": 0,
        "aiCallPeriodDays": 30,
        "totalWorkflows": 0,
        "activeWorkflows": 0,
        "totalRuns": 0,
        "totalTokens": 0,
    }

    # Billing: count AI transactions in the last 30 days
    if user is not None:
        try:
            from modules.interfaces.interfaceDbBilling import getInterface as getBillingInterface

            mandateIds: List[str] = []
            for um in root.getUserMandates(userId):
                mid = getattr(um, "mandateId", None)
                if mid and getattr(um, "enabled", True):
                    mandateIds.append(str(mid))

            if mandateIds:
                bi = getBillingInterface(user, mandateIds[0])
                now = time.time()
                startTs = now - 30 * 86400
                stats = bi.getTransactionStatisticsAggregated(
                    mandateIds=mandateIds,
                    scope="all",
                    userId=userId,
                    startTs=startTs,
                    endTs=now,
                    period="month",
                )
                liveStats["aiCallCount"] = stats.get("transactionCount", 0)
        except Exception as e:
            logger.debug(f"integrations-overview billing stats: {e}")

    # Workflow metrics (same logic as routeWorkflowDashboard.get_workflow_metrics)
    try:
        from modules.shared.configuration import APP_CONFIG
        from modules.connectors.connectorDbPostgre import DatabaseConnector
        from modules.datamodels.datamodelPagination import PaginationParams
        from modules.features.graphicalEditor.datamodelFeatureGraphicalEditor import (
            AutoWorkflow, AutoRun,
        )

        wfDb = DatabaseConnector(
            dbHost=APP_CONFIG.get("DB_HOST", "localhost"),
            dbDatabase="poweron_graphicaleditor",
            dbUser=APP_CONFIG.get("DB_USER"),
            dbPassword=APP_CONFIG.get("DB_PASSWORD_SECRET") or APP_CONFIG.get("DB_PASSWORD"),
            dbPort=int(APP_CONFIG.get("DB_PORT", 5432)),
            userId=None,
        )

        if wfDb._ensureTableExists(AutoWorkflow):
            mandateIds_wf: List[str] = []
            for um in root.getUserMandates(userId):
                mid = getattr(um, "mandateId", None)
                if mid and getattr(um, "enabled", True):
                    mandateIds_wf.append(str(mid))

            wfFilter: dict = {"isTemplate": False}
            if mandateIds_wf:
                wfFilter["mandateId"] = mandateIds_wf
            else:
                wfFilter["mandateId"] = "__impossible__"

            wfCount = wfDb.getRecordsetPaginated(
                AutoWorkflow,
                pagination=PaginationParams(page=1, pageSize=1),
                recordFilter=wfFilter,
            )
            liveStats["totalWorkflows"] = (
                wfCount.get("totalItems", 0) if isinstance(wfCount, dict) else wfCount.totalItems
            )

            activeFilter = dict(wfFilter)
            activeFilter["active"] = True
            activeCount = wfDb.getRecordsetPaginated(
                AutoWorkflow,
                pagination=PaginationParams(page=1, pageSize=1),
                recordFilter=activeFilter,
            )
            liveStats["activeWorkflows"] = (
                activeCount.get("totalItems", 0) if isinstance(activeCount, dict) else activeCount.totalItems
            )

        if wfDb._ensureTableExists(AutoRun):
            runFilter: dict = {}
            if mandateIds_wf:
                runFilter["mandateId"] = mandateIds_wf
            else:
                runFilter["ownerId"] = userId

            runCount = wfDb.getRecordsetPaginated(
                AutoRun,
                pagination=PaginationParams(page=1, pageSize=1),
                recordFilter=runFilter,
            )
            liveStats["totalRuns"] = (
                runCount.get("totalItems", 0) if isinstance(runCount, dict) else runCount.totalItems
            )

            totalTokens = 0
            totalRuns = liveStats["totalRuns"]
            if 0 < totalRuns <= 10000:
                allRuns = wfDb.getRecordset(
                    AutoRun, recordFilter=runFilter, fieldFilter=["costTokens"],
                ) or []
                for r in allRuns:
                    totalTokens += r.get("costTokens", 0) or 0
            liveStats["totalTokens"] = totalTokens
    except Exception as e:
        logger.debug(f"integrations-overview workflow stats: {e}")

    out["liveStats"] = liveStats

    return out


@router.get("/integrations-overview")
@limiter.limit("30/minute")
def get_integrations_overview(
    request: Request,
    reqContext: RequestContext = Depends(getRequestContext),
) -> Dict[str, Any]:
    """Aggregated, non-fictitious data for the PORTA integrations diagram."""
    user_id = str(reqContext.user.id)
    return _buildIntegrationsOverviewPayload(user_id, user=reqContext.user)


@router.get("/ai-models")
@limiter.limit("60/minute")
def get_ai_models_for_integrations(
    request: Request,
    reqContext: RequestContext = Depends(getRequestContext),
) -> Dict[str, Any]:
    """
    Registered AI models for the Integrations architecture page.
    Returns unique displayName entries with connector metadata (no callables).
    """
    try:
        from modules.aicore.aicoreModelRegistry import modelRegistry

        modelRegistry.ensureConnectorsRegistered()
        modelRegistry.refreshModels(force=False)
        models = modelRegistry.getModels()
        out: List[Dict[str, Any]] = []
        seen: set = set()
        for m in models:
            if not getattr(m, "isAvailable", True):
                continue
            key = (m.displayName, m.connectorType)
            if key in seen:
                continue
            seen.add(key)
            dumped = m.model_dump(
                exclude={"functionCall", "functionCallStream", "calculatepriceCHF"},
                mode="json",
            )
            out.append(dumped)
        return {"models": out}
    except Exception as e:
        logger.error(f"Error listing AI models: {e}")
        return {"models": [], "error": str(e)}
