# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Audit Logging System for PowerOn Gateway

This module provides centralized audit logging functionality for security events,
user actions, and system access patterns. Logs are stored in the database for
GDPR compliance and security monitoring.

GDPR Requirements Addressed:
- Article 5(1)(f): Integrity and confidentiality - secure audit trail
- Article 17: Right to erasure - audit log retention with automatic cleanup
- Article 30: Records of processing activities - comprehensive event logging
"""

import logging
from datetime import datetime
from typing import Optional, Dict, Any

from modules.shared.configuration import APP_CONFIG
from modules.shared.timeUtils import getUtcTimestamp

logger = logging.getLogger(__name__)


class AuditLogger:
    """
    Centralized audit logging system with database storage.
    
    Logs security-relevant events to PostgreSQL for:
    - GDPR compliance
    - Security monitoring
    - Access tracking
    - Incident investigation
    """
    
    def __init__(self):
        self._db = None
        self._modelClass = None
        self._initialized = False
        self._fallbackToStdout = True
        
    def _ensureInitialized(self) -> bool:
        """Lazily initialize database connection to avoid circular imports."""
        if self._initialized:
            return self._db is not None
            
        self._initialized = True
        
        try:
            from modules.datamodels.datamodelAudit import AuditLogEntry
            from modules.connectors.connectorDbPostgre import DatabaseConnector
            
            self._modelClass = AuditLogEntry
            
            # Get database configuration
            dbHost = APP_CONFIG.get("DB_HOST", "_no_config_default_data")
            dbDatabase = "poweron_app"  # Store audit logs in the main app database
            dbUser = APP_CONFIG.get("DB_USER")
            dbPassword = APP_CONFIG.get("DB_PASSWORD_SECRET")
            dbPort = int(APP_CONFIG.get("DB_PORT", 5432))
            
            # Create database connector with system user context
            self._db = DatabaseConnector(
                dbHost=dbHost,
                dbDatabase=dbDatabase,
                dbUser=dbUser,
                dbPassword=dbPassword,
                dbPort=dbPort,
                userId="system"  # Audit logs are created by system
            )
            
            # Initialize database and ensure table exists
            self._db.initDbSystem()
            self._db._ensureTableExists(AuditLogEntry)
            
            logger.info("AuditLogger database connection initialized successfully")
            return True
            
        except Exception as e:
            logger.warning(f"AuditLogger database initialization failed, using fallback logging: {e}")
            self._db = None
            return False
    
    def _logToFallback(self, entry: Dict[str, Any]) -> None:
        """Log to standard logger as fallback when database is unavailable."""
        if self._fallbackToStdout:
            fallbackMsg = (
                f"AUDIT | {entry.get('timestamp', '')} | "
                f"{entry.get('userId', '')} | {entry.get('mandateId', '')} | "
                f"{entry.get('category', '')} | {entry.get('action', '')} | "
                f"{entry.get('details', '')}"
            )
            logging.getLogger('audit.fallback').info(fallbackMsg)
    
    def logEvent(
        self,
        userId: str,
        mandateId: Optional[str] = None,
        category: str = "system",
        action: str = "",
        details: str = "",
        featureInstanceId: Optional[str] = None,
        resourceType: Optional[str] = None,
        resourceId: Optional[str] = None,
        ipAddress: Optional[str] = None,
        userAgent: Optional[str] = None,
        success: bool = True,
        errorMessage: Optional[str] = None,
        username: Optional[str] = None,
        timestamp: Optional[float] = None
    ) -> Optional[str]:
        """
        Log an audit event to the database.
        
        Args:
            userId: User identifier (or 'system' for system events)
            mandateId: Mandate context (can be None for system-level events)
            category: Event category (access, key, data, security, gdpr, permission, system)
            action: Specific action performed
            details: Additional details about the event
            featureInstanceId: Feature instance context (if applicable)
            resourceType: Type of resource affected
            resourceId: ID of the affected resource
            ipAddress: Client IP address
            userAgent: Client user agent
            success: Whether the action was successful
            errorMessage: Error message if action failed
            username: Username at the time of event (for historical reference)
            timestamp: Optional custom timestamp (defaults to current time)
            
        Returns:
            ID of the created audit log entry, or None if logging failed
        """
        try:
            # Prepare the entry data
            entryData = {
                "timestamp": timestamp if timestamp else getUtcTimestamp(),
                "userId": userId or "unknown",
                "username": username,
                "mandateId": mandateId,
                "featureInstanceId": featureInstanceId,
                "category": category,
                "action": action,
                "resourceType": resourceType,
                "resourceId": resourceId,
                "details": details if details else None,
                "ipAddress": ipAddress,
                "userAgent": userAgent,
                "success": success,
                "errorMessage": errorMessage
            }
            
            # Try to write to database
            if self._ensureInitialized() and self._db:
                from modules.datamodels.datamodelAudit import AuditLogEntry
                
                entry = AuditLogEntry(**entryData)
                created = self._db.recordCreate(AuditLogEntry, entry.model_dump())
                
                if created and created.get("id"):
                    return created["id"]
                else:
                    self._logToFallback(entryData)
                    return None
            else:
                # Use fallback logging
                self._logToFallback(entryData)
                return None
                
        except Exception as e:
            logger.error(f"Failed to log audit event: {e}")
            # Try fallback
            try:
                self._logToFallback(entryData)
            except Exception:
                pass
            return None
    
    # ===== Convenience Methods for Common Event Types =====
    
    def logKeyAccess(
        self,
        userId: str,
        mandateId: str,
        keyName: str,
        action: str,
        ipAddress: Optional[str] = None
    ) -> Optional[str]:
        """Log key access events (encode/decode)."""
        return self.logEvent(
            userId=userId,
            mandateId=mandateId,
            category="key",
            action=action,
            details=f"Key: {keyName}",
            resourceType="EncryptionKey",
            resourceId=keyName,
            ipAddress=ipAddress
        )
    
    def logUserAccess(
        self,
        userId: str,
        mandateId: str,
        action: str,
        successInfo: str = "",
        ipAddress: Optional[str] = None,
        userAgent: Optional[str] = None,
        success: bool = True
    ) -> Optional[str]:
        """Log user access events (login/logout)."""
        return self.logEvent(
            userId=userId,
            mandateId=mandateId,
            category="access",
            action=action,
            details=successInfo,
            ipAddress=ipAddress,
            userAgent=userAgent,
            success=success
        )
    
    def logDataAccess(
        self,
        userId: str,
        mandateId: str,
        action: str,
        details: str = "",
        resourceType: Optional[str] = None,
        resourceId: Optional[str] = None,
        featureInstanceId: Optional[str] = None
    ) -> Optional[str]:
        """Log data access events (CRUD operations)."""
        return self.logEvent(
            userId=userId,
            mandateId=mandateId,
            category="data",
            action=action,
            details=details,
            resourceType=resourceType,
            resourceId=resourceId,
            featureInstanceId=featureInstanceId
        )
    
    def logSecurityEvent(
        self,
        userId: str,
        mandateId: str,
        action: str,
        details: str = "",
        ipAddress: Optional[str] = None,
        success: bool = True,
        errorMessage: Optional[str] = None
    ) -> Optional[str]:
        """Log security-related events."""
        return self.logEvent(
            userId=userId,
            mandateId=mandateId,
            category="security",
            action=action,
            details=details,
            ipAddress=ipAddress,
            success=success,
            errorMessage=errorMessage
        )
    
    def logGdprEvent(
        self,
        userId: str,
        mandateId: str,
        action: str,
        details: str = "",
        ipAddress: Optional[str] = None
    ) -> Optional[str]:
        """Log GDPR-specific events (data export, deletion, etc.)."""
        return self.logEvent(
            userId=userId,
            mandateId=mandateId,
            category="gdpr",
            action=action,
            details=details,
            ipAddress=ipAddress
        )
    
    def logPermissionChange(
        self,
        userId: str,
        mandateId: str,
        action: str,
        targetUserId: str,
        details: str = "",
        resourceType: Optional[str] = None,
        resourceId: Optional[str] = None
    ) -> Optional[str]:
        """Log permission/role changes."""
        return self.logEvent(
            userId=userId,
            mandateId=mandateId,
            category="permission",
            action=action,
            details=f"Target user: {targetUserId}. {details}",
            resourceType=resourceType,
            resourceId=resourceId
        )
    
    # ===== Audit Log Query Methods =====
    
    def getAuditLogs(
        self,
        userId: Optional[str] = None,
        mandateId: Optional[str] = None,
        category: Optional[str] = None,
        action: Optional[str] = None,
        fromTimestamp: Optional[float] = None,
        toTimestamp: Optional[float] = None,
        limit: int = 100
    ) -> list:
        """
        Query audit logs from database.
        
        Args:
            userId: Filter by user ID
            mandateId: Filter by mandate ID
            category: Filter by category
            action: Filter by action
            fromTimestamp: Filter events after this timestamp
            toTimestamp: Filter events before this timestamp
            limit: Maximum number of records to return
            
        Returns:
            List of audit log entries
        """
        if not self._ensureInitialized() or not self._db:
            return []
            
        try:
            from modules.datamodels.datamodelAudit import AuditLogEntry
            
            # Build filter
            recordFilter = {}
            if userId:
                recordFilter["userId"] = userId
            if mandateId:
                recordFilter["mandateId"] = mandateId
            if category:
                recordFilter["category"] = category
            if action:
                recordFilter["action"] = action
            
            # Query database
            records = self._db.getRecordset(
                AuditLogEntry,
                recordFilter=recordFilter if recordFilter else None,
                orderBy="timestamp DESC"
            )
            
            # Apply timestamp filtering in Python (PostgreSQL connector may not support $gt/$lt)
            if fromTimestamp or toTimestamp:
                filteredRecords = []
                for record in records:
                    ts = record.get("timestamp", 0)
                    if fromTimestamp and ts < fromTimestamp:
                        continue
                    if toTimestamp and ts > toTimestamp:
                        continue
                    filteredRecords.append(record)
                records = filteredRecords
            
            # Apply limit
            return records[:limit]
            
        except Exception as e:
            logger.error(f"Failed to query audit logs: {e}")
            return []
    
    # ===== Cleanup Methods =====
    
    def cleanupOldEntries(self, retentionDays: int = 365) -> int:
        """
        Remove audit log entries older than the retention period.
        
        GDPR Note: Audit logs should be retained for a reasonable period
        for security and compliance purposes, but not indefinitely.
        Default retention is 1 year (365 days).
        
        Args:
            retentionDays: Number of days to retain audit logs
            
        Returns:
            Number of entries deleted
        """
        if not self._ensureInitialized() or not self._db:
            logger.warning("Cannot cleanup audit logs: database not initialized")
            return 0
            
        try:
            from modules.datamodels.datamodelAudit import AuditLogEntry
            import time
            
            # Calculate cutoff timestamp
            cutoffTimestamp = time.time() - (retentionDays * 24 * 60 * 60)
            
            # Query old entries
            allRecords = self._db.getRecordset(AuditLogEntry)
            oldRecords = [r for r in allRecords if r.get("timestamp", 0) < cutoffTimestamp]
            
            # Delete old entries
            deletedCount = 0
            for record in oldRecords:
                recordId = record.get("id")
                if recordId:
                    if self._db.recordDelete(AuditLogEntry, recordId):
                        deletedCount += 1
            
            logger.info(f"Audit log cleanup: removed {deletedCount} entries older than {retentionDays} days")
            
            # Log the cleanup action itself
            self.logEvent(
                userId="system",
                mandateId="system",
                category="system",
                action="audit_cleanup",
                details=f"Removed {deletedCount} entries older than {retentionDays} days"
            )
            
            return deletedCount
            
        except Exception as e:
            logger.error(f"Failed to cleanup audit logs: {e}")
            return 0


# Global audit logger instance
audit_logger = AuditLogger()


# ===== Scheduler Integration =====

async def runAuditLogCleanup() -> None:
    """
    Scheduled task to cleanup old audit log entries.
    Called by the event scheduler.
    """
    try:
        retentionDays = int(APP_CONFIG.get("AUDIT_LOG_RETENTION_DAYS", 365))
        deletedCount = audit_logger.cleanupOldEntries(retentionDays=retentionDays)
        logger.info(f"Scheduled audit log cleanup completed: {deletedCount} entries removed")
    except Exception as e:
        logger.error(f"Scheduled audit log cleanup failed: {e}")


def registerAuditLogCleanupScheduler() -> None:
    """
    Register the audit log cleanup job with the event scheduler.
    Should be called during application startup.
    """
    try:
        from modules.shared.eventManagement import eventManager
        
        # Run cleanup daily at 3 AM
        eventManager.registerCron(
            jobId="audit_log_cleanup",
            func=runAuditLogCleanup,
            cronKwargs={
                "hour": "3",
                "minute": "0"
            }
        )
        
        logger.info("Audit log cleanup scheduler registered (daily at 03:00)")
        
    except Exception as e:
        logger.error(f"Failed to register audit log cleanup scheduler: {e}")
