# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Feature Instance Management Interface.

Multi-Tenant Design:
- Feature-Instanzen gehören zu Mandanten
- Template-Rollen werden bei Erstellung kopiert
- Synchronisation von Templates ist explizit (nicht automatisch)
"""

import logging
import uuid
from typing import List, Dict, Any, Optional

from modules.datamodels.datamodelFeatures import Feature, FeatureInstance
from modules.datamodels.datamodelRbac import Role, AccessRule
from modules.connectors.connectorDbPostgre import DatabaseConnector

logger = logging.getLogger(__name__)


class FeatureInterface:
    """
    Interface for Feature and FeatureInstance management.
    
    Responsibilities:
    - CRUD operations for Features and FeatureInstances
    - Template role copying on instance creation
    - Template synchronization for existing instances
    """

    def __init__(self, db: DatabaseConnector):
        """
        Initialize Feature interface.
        
        Args:
            db: DatabaseConnector instance (DbApp database)
        """
        self.db = db

    # ============================================
    # Feature Methods (Global Feature Definitions)
    # ============================================

    def getFeature(self, featureCode: str) -> Optional[Feature]:
        """
        Get a feature by code.
        
        Args:
            featureCode: Feature code (e.g., "trustee", "chatbot")
            
        Returns:
            Feature object or None
        """
        try:
            records = self.db.getRecordset(Feature, recordFilter={"code": featureCode})
            if not records:
                return None
            cleanedRecord = {k: v for k, v in records[0].items() if not k.startswith("_")}
            return Feature(**cleanedRecord)
        except Exception as e:
            logger.error(f"Error getting feature {featureCode}: {e}")
            return None

    def getAllFeatures(self) -> List[Feature]:
        """
        Get all available features.
        
        Returns:
            List of Feature objects
        """
        try:
            records = self.db.getRecordset(Feature)
            result = []
            for record in records:
                cleanedRecord = {k: v for k, v in record.items() if not k.startswith("_")}
                result.append(Feature(**cleanedRecord))
            return result
        except Exception as e:
            logger.error(f"Error getting all features: {e}")
            return []

    def createFeature(self, code: str, label: Dict[str, str], icon: str = "mdi-puzzle") -> Feature:
        """
        Create a new feature definition.
        
        Args:
            code: Unique feature code (e.g., "trustee")
            label: I18n labels (e.g., {"en": "Trustee", "de": "Treuhand"})
            icon: Icon identifier
            
        Returns:
            Created Feature object
        """
        try:
            feature = Feature(code=code, label=label, icon=icon)
            createdRecord = self.db.recordCreate(Feature, feature.model_dump())
            cleanedRecord = {k: v for k, v in createdRecord.items() if not k.startswith("_")}
            return Feature(**cleanedRecord)
        except Exception as e:
            logger.error(f"Error creating feature {code}: {e}")
            raise ValueError(f"Failed to create feature: {e}")

    # ============================================
    # Feature Instance Methods
    # ============================================

    def getFeatureInstance(self, instanceId: str) -> Optional[FeatureInstance]:
        """
        Get a feature instance by ID.
        
        Args:
            instanceId: FeatureInstance ID
            
        Returns:
            FeatureInstance object or None
        """
        try:
            records = self.db.getRecordset(FeatureInstance, recordFilter={"id": instanceId})
            if not records:
                return None
            cleanedRecord = {k: v for k, v in records[0].items() if not k.startswith("_")}
            return FeatureInstance(**cleanedRecord)
        except Exception as e:
            logger.error(f"Error getting feature instance {instanceId}: {e}")
            return None

    def getFeatureInstancesForMandate(self, mandateId: str, featureCode: Optional[str] = None) -> List[FeatureInstance]:
        """
        Get all feature instances for a mandate.
        
        Args:
            mandateId: Mandate ID
            featureCode: Optional filter by feature code
            
        Returns:
            List of FeatureInstance objects
        """
        try:
            recordFilter = {"mandateId": mandateId}
            if featureCode:
                recordFilter["featureCode"] = featureCode
            records = self.db.getRecordset(FeatureInstance, recordFilter=recordFilter)
            result = []
            for record in records:
                cleanedRecord = {k: v for k, v in record.items() if not k.startswith("_")}
                result.append(FeatureInstance(**cleanedRecord))
            return result
        except Exception as e:
            logger.error(f"Error getting feature instances for mandate {mandateId}: {e}")
            return []

    def createFeatureInstance(
        self, 
        featureCode: str, 
        mandateId: str, 
        label: str,
        enabled: bool = True,
        copyTemplateRoles: bool = True,
        config: Optional[Dict[str, Any]] = None
    ) -> FeatureInstance:
        """
        Create a new feature instance for a mandate.
        
        Optionally copies global template roles for this feature.
        
        WICHTIG: Templates werden NUR bei Erstellung kopiert.
        Spätere Template-Änderungen werden NICHT automatisch propagiert.
        Für manuelle Nachsynchronisation siehe syncRolesFromTemplate().
        
        Args:
            featureCode: Feature code (e.g., "trustee")
            mandateId: Mandate ID
            label: Instance label (e.g., "Buchhaltung 2025")
            enabled: Whether the instance is enabled
            copyTemplateRoles: Whether to copy template roles
            
        Returns:
            Created FeatureInstance object
        """
        try:
            # Create instance
            instance = FeatureInstance(
                featureCode=featureCode,
                mandateId=mandateId,
                label=label,
                enabled=enabled,
                config=config
            )
            createdInstance = self.db.recordCreate(FeatureInstance, instance.model_dump())
            
            if not createdInstance:
                raise ValueError("Failed to create feature instance record")
            
            instanceId = createdInstance.get("id")
            
            # Copy template roles if requested
            if copyTemplateRoles:
                self._copyTemplateRoles(featureCode, mandateId, instanceId)
            
            cleanedRecord = {k: v for k, v in createdInstance.items() if not k.startswith("_")}
            return FeatureInstance(**cleanedRecord)
            
        except Exception as e:
            logger.error(f"Error creating feature instance: {e}")
            raise ValueError(f"Failed to create feature instance: {e}")

    def _copyTemplateRoles(self, featureCode: str, mandateId: str, instanceId: str) -> int:
        """
        Copy global template roles for a feature to a new instance.
        
        Args:
            featureCode: Feature code
            mandateId: Mandate ID
            instanceId: New FeatureInstance ID
            
        Returns:
            Number of roles copied
        """
        try:
            # Find global template roles for this feature (mandateId=None)
            globalRoles = self.db.getRecordset(
                Role,
                recordFilter={"featureCode": featureCode, "mandateId": None}
            )
            
            if not globalRoles:
                logger.debug(f"No template roles found for feature {featureCode}")
                return 0
            
            templateRoleIds = [r.get("id") for r in globalRoles]
            
            # BULK: Load all template AccessRules in one query
            allTemplateRules = []
            for roleId in templateRoleIds:
                rules = self.db.getRecordset(AccessRule, recordFilter={"roleId": roleId})
                allTemplateRules.extend([(roleId, r) for r in rules])
            
            # Index for fast lookup: roleId -> rules
            rulesByRoleId = {}
            for roleId, rule in allTemplateRules:
                if roleId not in rulesByRoleId:
                    rulesByRoleId[roleId] = []
                rulesByRoleId[roleId].append(rule)
            
            # Copy roles and their AccessRules
            copiedCount = 0
            for templateRole in globalRoles:
                newRoleId = str(uuid.uuid4())
                
                # Create new role for this instance
                newRole = Role(
                    id=newRoleId,
                    roleLabel=templateRole.get("roleLabel"),
                    description=templateRole.get("description", {}),
                    featureCode=featureCode,
                    mandateId=mandateId,
                    featureInstanceId=instanceId,
                    isSystemRole=False
                )
                self.db.recordCreate(Role, newRole.model_dump())
                
                # Copy AccessRules for this role
                templateRulesForRole = rulesByRoleId.get(templateRole.get("id"), [])
                for rule in templateRulesForRole:
                    newRule = AccessRule(
                        id=str(uuid.uuid4()),
                        roleId=newRoleId,
                        context=rule.get("context"),
                        item=rule.get("item"),
                        view=rule.get("view", False),
                        read=rule.get("read"),
                        create=rule.get("create"),
                        update=rule.get("update"),
                        delete=rule.get("delete")
                    )
                    self.db.recordCreate(AccessRule, newRule.model_dump())
                
                copiedCount += 1
            
            logger.info(f"Copied {copiedCount} template roles for instance {instanceId}")
            return copiedCount
            
        except Exception as e:
            logger.error(f"Error copying template roles: {e}")
            return 0

    def syncRolesFromTemplate(self, featureInstanceId: str, addOnly: bool = True) -> Dict[str, int]:
        """
        Synchronize roles of a feature instance with current templates.
        
        WICHTIG: Templates werden NUR bei Erstellung einer neuen FeatureInstance kopiert.
        Diese Sync-Funktion ist für manuelle Nachsynchronisation gedacht, nicht für
        automatische Propagation von Template-Änderungen.
        
        Args:
            featureInstanceId: ID of the instance to sync
            addOnly: If True, only add missing roles. If False, also remove extras.
            
        Returns:
            Dict with added/removed/unchanged counts
        """
        try:
            instance = self.getFeatureInstance(featureInstanceId)
            if not instance:
                raise ValueError(f"FeatureInstance {featureInstanceId} not found")
            
            featureCode = instance.featureCode
            mandateId = instance.mandateId
            
            # Get current template roles
            templateRoles = self.db.getRecordset(
                Role,
                recordFilter={"featureCode": featureCode, "mandateId": None}
            )
            templateLabels = {r.get("roleLabel") for r in templateRoles}
            
            # Get current instance roles
            instanceRoles = self.db.getRecordset(
                Role,
                recordFilter={"featureInstanceId": featureInstanceId}
            )
            instanceLabels = {r.get("roleLabel") for r in instanceRoles}
            
            result = {"added": 0, "removed": 0, "unchanged": 0}
            
            # Add missing roles
            for templateRole in templateRoles:
                if templateRole.get("roleLabel") not in instanceLabels:
                    # Copy this role
                    newRoleId = str(uuid.uuid4())
                    newRole = Role(
                        id=newRoleId,
                        roleLabel=templateRole.get("roleLabel"),
                        description=templateRole.get("description", {}),
                        featureCode=featureCode,
                        mandateId=mandateId,
                        featureInstanceId=featureInstanceId,
                        isSystemRole=False
                    )
                    self.db.recordCreate(Role, newRole.model_dump())
                    
                    # Copy AccessRules
                    templateRules = self.db.getRecordset(
                        AccessRule,
                        recordFilter={"roleId": templateRole.get("id")}
                    )
                    for rule in templateRules:
                        newRule = AccessRule(
                            id=str(uuid.uuid4()),
                            roleId=newRoleId,
                            context=rule.get("context"),
                            item=rule.get("item"),
                            view=rule.get("view", False),
                            read=rule.get("read"),
                            create=rule.get("create"),
                            update=rule.get("update"),
                            delete=rule.get("delete")
                        )
                        self.db.recordCreate(AccessRule, newRule.model_dump())
                    
                    result["added"] += 1
                else:
                    result["unchanged"] += 1
            
            # Remove extra roles (optional)
            if not addOnly:
                from modules.datamodels.datamodelMembership import FeatureAccessRole
                
                for instanceRole in instanceRoles:
                    if instanceRole.get("roleLabel") not in templateLabels:
                        # Check if role is still in use
                        usages = self.db.getRecordset(
                            FeatureAccessRole,
                            recordFilter={"roleId": instanceRole.get("id")}
                        )
                        if not usages:
                            self.db.recordDelete(Role, instanceRole.get("id"))
                            result["removed"] += 1
            
            logger.info(f"Synced roles for instance {featureInstanceId}: {result}")
            return result
            
        except Exception as e:
            logger.error(f"Error syncing roles from template: {e}")
            raise ValueError(f"Failed to sync roles: {e}")

    def updateFeatureInstance(self, instanceId: str, updateData: Dict[str, Any]) -> Optional[FeatureInstance]:
        """
        Update a feature instance.
        
        Only label and enabled fields can be updated.
        featureCode and mandateId are immutable.
        
        Args:
            instanceId: FeatureInstance ID
            updateData: Dictionary with fields to update (label, enabled)
            
        Returns:
            Updated FeatureInstance object or None if not found
        """
        try:
            instance = self.getFeatureInstance(instanceId)
            if not instance:
                return None
            
            # Only allow updating specific fields
            allowedFields = {"label", "enabled", "config"}
            filteredData = {k: v for k, v in updateData.items() if k in allowedFields}
            
            if not filteredData:
                return instance
            
            updated = self.db.recordModify(FeatureInstance, instanceId, filteredData)
            if updated:
                cleanedRecord = {k: v for k, v in updated.items() if not k.startswith("_")}
                return FeatureInstance(**cleanedRecord)
            return None
        except Exception as e:
            logger.error(f"Error updating feature instance {instanceId}: {e}")
            raise ValueError(f"Failed to update feature instance: {e}")

    def deleteFeatureInstance(self, instanceId: str) -> bool:
        """
        Delete a feature instance.
        CASCADE will delete associated roles and access records.
        
        Args:
            instanceId: FeatureInstance ID
            
        Returns:
            True if deleted
        """
        try:
            instance = self.getFeatureInstance(instanceId)
            if not instance:
                return False
            
            return self.db.recordDelete(FeatureInstance, instanceId)
        except Exception as e:
            logger.error(f"Error deleting feature instance {instanceId}: {e}")
            raise ValueError(f"Failed to delete feature instance: {e}")

    # ============================================
    # Template Role Methods (Global)
    # ============================================

    def getTemplateRoles(self, featureCode: Optional[str] = None) -> List[Role]:
        """
        Get global template roles (mandateId=None).
        
        Args:
            featureCode: Optional filter by feature code
            
        Returns:
            List of Role objects
        """
        try:
            recordFilter = {"mandateId": None}
            if featureCode:
                recordFilter["featureCode"] = featureCode
            records = self.db.getRecordset(Role, recordFilter=recordFilter)
            result = []
            for record in records:
                cleanedRecord = {k: v for k, v in record.items() if not k.startswith("_")}
                result.append(Role(**cleanedRecord))
            return result
        except Exception as e:
            logger.error(f"Error getting template roles: {e}")
            return []

    def createTemplateRole(
        self, 
        roleLabel: str, 
        featureCode: str,
        description: Dict[str, str] = None
    ) -> Role:
        """
        Create a global template role for a feature.
        
        Args:
            roleLabel: Role label (e.g., "admin", "viewer")
            featureCode: Feature code this role belongs to
            description: I18n descriptions
            
        Returns:
            Created Role object
        """
        try:
            role = Role(
                roleLabel=roleLabel,
                description=description or {},
                featureCode=featureCode,
                mandateId=None,  # Global template
                featureInstanceId=None,
                isSystemRole=False
            )
            createdRecord = self.db.recordCreate(Role, role.model_dump())
            cleanedRecord = {k: v for k, v in createdRecord.items() if not k.startswith("_")}
            return Role(**cleanedRecord)
        except Exception as e:
            logger.error(f"Error creating template role: {e}")
            raise ValueError(f"Failed to create template role: {e}")


def getFeatureInterface(db: DatabaseConnector) -> FeatureInterface:
    """
    Factory function to get a FeatureInterface instance.
    
    Args:
        db: DatabaseConnector instance (DbApp database)
        
    Returns:
        FeatureInterface instance
    """
    return FeatureInterface(db)
