"""
Audit logging and change tracking for Meta Ads operations.

This module provides comprehensive logging of all operations,
change tracking, and performance monitoring.
"""

from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from dataclasses import dataclass, asdict
from enum import Enum

from .logging import get_logger

logger = get_logger(__name__)


class OperationType(str, Enum):
    """Types of operations that can be audited."""
    CREATE = "CREATE"
    UPDATE = "UPDATE"
    DELETE = "DELETE"
    READ = "READ"
    BULK_UPDATE = "BULK_UPDATE"
    BULK_DELETE = "BULK_DELETE"
    INSIGHTS = "INSIGHTS"
    BATCH = "BATCH"


class AuditLevel(str, Enum):
    """Audit logging levels."""
    INFO = "INFO"
    WARNING = "WARNING"
    ERROR = "ERROR"
    CRITICAL = "CRITICAL"


@dataclass
class AuditEntry:
    """Represents a single audit log entry."""
    timestamp: str
    operation_type: OperationType
    resource_type: str  # e.g., "campaign", "adset", "ad"
    resource_id: str
    user_id: Optional[str]
    ad_account_id: Optional[str]
    operation_details: Dict[str, Any]
    request_data: Optional[Dict[str, Any]] = None
    response_data: Optional[Dict[str, Any]] = None
    error_details: Optional[Dict[str, Any]] = None
    duration_ms: Optional[float] = None
    level: AuditLevel = AuditLevel.INFO
    correlation_id: Optional[str] = None
    ip_address: Optional[str] = None
    user_agent: Optional[str] = None


@dataclass
class ChangeRecord:
    """Represents a change to a resource."""
    field_name: str
    old_value: Any
    new_value: Any
    change_type: str  # "added", "modified", "removed"


@dataclass
class PerformanceMetrics:
    """Performance metrics for operations."""
    operation_type: OperationType
    resource_type: str
    duration_ms: float
    success: bool
    timestamp: str
    ad_account_id: Optional[str] = None
    error_type: Optional[str] = None


class AuditLogger:
    """Handles audit logging and change tracking."""
    
    def __init__(self, enable_file_logging: bool = True, enable_database_logging: bool = False):
        self.enable_file_logging = enable_file_logging
        self.enable_database_logging = enable_database_logging
        self._audit_entries: List[AuditEntry] = []
        self._performance_metrics: List[PerformanceMetrics] = []
    
    def log_operation(
        self,
        operation_type: OperationType,
        resource_type: str,
        resource_id: str,
        operation_details: Dict[str, Any],
        user_id: Optional[str] = None,
        ad_account_id: Optional[str] = None,
        request_data: Optional[Dict[str, Any]] = None,
        response_data: Optional[Dict[str, Any]] = None,
        error_details: Optional[Dict[str, Any]] = None,
        duration_ms: Optional[float] = None,
        level: AuditLevel = AuditLevel.INFO,
        correlation_id: Optional[str] = None,
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None,
    ) -> AuditEntry:
        """
        Log an operation for audit purposes.
        
        Args:
            operation_type: Type of operation performed
            resource_type: Type of resource (campaign, adset, etc.)
            resource_id: ID of the resource
            operation_details: Details about the operation
            user_id: ID of the user performing the operation
            ad_account_id: Ad account ID
            request_data: Request payload (redacted for sensitive data)
            response_data: Response data (redacted for sensitive data)
            error_details: Error information if operation failed
            duration_ms: Operation duration in milliseconds
            level: Audit level
            correlation_id: Correlation ID for tracking related operations
            ip_address: IP address of the request
            user_agent: User agent string
        
        Returns:
            AuditEntry: The created audit entry
        """
        entry = AuditEntry(
            timestamp=datetime.now(timezone.utc).isoformat(),
            operation_type=operation_type,
            resource_type=resource_type,
            resource_id=resource_id,
            user_id=user_id,
            ad_account_id=ad_account_id,
            operation_details=operation_details,
            request_data=self._redact_sensitive_data(request_data) if request_data else None,
            response_data=self._redact_sensitive_data(response_data) if response_data else None,
            error_details=error_details,
            duration_ms=duration_ms,
            level=level,
            correlation_id=correlation_id,
            ip_address=ip_address,
            user_agent=user_agent,
        )
        
        # Store in memory
        self._audit_entries.append(entry)
        
        # Log to file if enabled
        if self.enable_file_logging:
            self._log_to_file(entry)
        
        # Log to database if enabled
        if self.enable_database_logging:
            self._log_to_database(entry)
        
        # Log to structured logger
        logger.info(
            "audit_operation",
            operation_type=operation_type.value,
            resource_type=resource_type,
            resource_id=resource_id,
            user_id=user_id,
            ad_account_id=ad_account_id,
            duration_ms=duration_ms,
            level=level.value,
            correlation_id=correlation_id,
        )
        
        return entry
    
    def log_performance_metrics(
        self,
        operation_type: OperationType,
        resource_type: str,
        duration_ms: float,
        success: bool,
        ad_account_id: Optional[str] = None,
        error_type: Optional[str] = None,
    ) -> PerformanceMetrics:
        """
        Log performance metrics for an operation.
        
        Args:
            operation_type: Type of operation
            resource_type: Type of resource
            duration_ms: Operation duration in milliseconds
            success: Whether the operation was successful
            ad_account_id: Ad account ID
            error_type: Type of error if operation failed
        
        Returns:
            PerformanceMetrics: The created performance metrics
        """
        metrics = PerformanceMetrics(
            operation_type=operation_type,
            resource_type=resource_type,
            duration_ms=duration_ms,
            success=success,
            timestamp=datetime.now(timezone.utc).isoformat(),
            ad_account_id=ad_account_id,
            error_type=error_type,
        )
        
        self._performance_metrics.append(metrics)
        
        # Log performance metrics
        logger.info(
            "performance_metrics",
            operation_type=operation_type.value,
            resource_type=resource_type,
            duration_ms=duration_ms,
            success=success,
            ad_account_id=ad_account_id,
            error_type=error_type,
        )
        
        return metrics
    
    def track_changes(
        self,
        resource_type: str,
        resource_id: str,
        old_data: Dict[str, Any],
        new_data: Dict[str, Any],
        user_id: Optional[str] = None,
        ad_account_id: Optional[str] = None,
    ) -> List[ChangeRecord]:
        """
        Track changes between old and new data.
        
        Args:
            resource_type: Type of resource
            resource_id: ID of the resource
            old_data: Previous state of the resource
            new_data: New state of the resource
            user_id: ID of the user making changes
            ad_account_id: Ad account ID
        
        Returns:
            List[ChangeRecord]: List of changes detected
        """
        changes = []
        
        # Find added and modified fields
        for key, new_value in new_data.items():
            if key not in old_data:
                changes.append(ChangeRecord(
                    field_name=key,
                    old_value=None,
                    new_value=new_value,
                    change_type="added"
                ))
            elif old_data[key] != new_value:
                changes.append(ChangeRecord(
                    field_name=key,
                    old_value=old_data[key],
                    new_value=new_value,
                    change_type="modified"
                ))
        
        # Find removed fields
        for key, old_value in old_data.items():
            if key not in new_data:
                changes.append(ChangeRecord(
                    field_name=key,
                    old_value=old_value,
                    new_value=None,
                    change_type="removed"
                ))
        
        # Log the changes
        if changes:
            self.log_operation(
                operation_type=OperationType.UPDATE,
                resource_type=resource_type,
                resource_id=resource_id,
                operation_details={
                    "changes": [asdict(change) for change in changes],
                    "change_count": len(changes),
                },
                user_id=user_id,
                ad_account_id=ad_account_id,
            )
        
        return changes
    
    def get_audit_entries(
        self,
        resource_type: Optional[str] = None,
        resource_id: Optional[str] = None,
        operation_type: Optional[OperationType] = None,
        user_id: Optional[str] = None,
        ad_account_id: Optional[str] = None,
        start_time: Optional[str] = None,
        end_time: Optional[str] = None,
        limit: int = 100,
    ) -> List[AuditEntry]:
        """
        Retrieve audit entries with filtering.
        
        Args:
            resource_type: Filter by resource type
            resource_id: Filter by resource ID
            operation_type: Filter by operation type
            user_id: Filter by user ID
            ad_account_id: Filter by ad account ID
            start_time: Filter by start time (ISO 8601)
            end_time: Filter by end time (ISO 8601)
            limit: Maximum number of entries to return
        
        Returns:
            List[AuditEntry]: Filtered audit entries
        """
        filtered_entries = self._audit_entries
        
        if resource_type:
            filtered_entries = [e for e in filtered_entries if e.resource_type == resource_type]
        
        if resource_id:
            filtered_entries = [e for e in filtered_entries if e.resource_id == resource_id]
        
        if operation_type:
            filtered_entries = [e for e in filtered_entries if e.operation_type == operation_type]
        
        if user_id:
            filtered_entries = [e for e in filtered_entries if e.user_id == user_id]
        
        if ad_account_id:
            filtered_entries = [e for e in filtered_entries if e.ad_account_id == ad_account_id]
        
        if start_time:
            filtered_entries = [e for e in filtered_entries if e.timestamp >= start_time]
        
        if end_time:
            filtered_entries = [e for e in filtered_entries if e.timestamp <= end_time]
        
        # Sort by timestamp (newest first) and limit
        filtered_entries.sort(key=lambda x: x.timestamp, reverse=True)
        return filtered_entries[:limit]
    
    def get_performance_metrics(
        self,
        operation_type: Optional[OperationType] = None,
        resource_type: Optional[str] = None,
        ad_account_id: Optional[str] = None,
        start_time: Optional[str] = None,
        end_time: Optional[str] = None,
        limit: int = 100,
    ) -> List[PerformanceMetrics]:
        """
        Retrieve performance metrics with filtering.
        
        Args:
            operation_type: Filter by operation type
            resource_type: Filter by resource type
            ad_account_id: Filter by ad account ID
            start_time: Filter by start time (ISO 8601)
            end_time: Filter by end time (ISO 8601)
            limit: Maximum number of metrics to return
        
        Returns:
            List[PerformanceMetrics]: Filtered performance metrics
        """
        filtered_metrics = self._performance_metrics
        
        if operation_type:
            filtered_metrics = [m for m in filtered_metrics if m.operation_type == operation_type]
        
        if resource_type:
            filtered_metrics = [m for m in filtered_metrics if m.resource_type == resource_type]
        
        if ad_account_id:
            filtered_metrics = [m for m in filtered_metrics if m.ad_account_id == ad_account_id]
        
        if start_time:
            filtered_metrics = [m for m in filtered_metrics if m.timestamp >= start_time]
        
        if end_time:
            filtered_metrics = [m for m in filtered_metrics if m.timestamp <= end_time]
        
        # Sort by timestamp (newest first) and limit
        filtered_metrics.sort(key=lambda x: x.timestamp, reverse=True)
        return filtered_metrics[:limit]
    
    def _redact_sensitive_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """Redact sensitive data from request/response data."""
        if not data:
            return data
        
        redacted = data.copy()
        sensitive_fields = [
            "access_token", "app_secret", "password", "secret", "token",
            "api_key", "private_key", "client_secret", "refresh_token"
        ]
        
        for field in sensitive_fields:
            if field in redacted:
                redacted[field] = "[REDACTED]"
        
        return redacted
    
    def _log_to_file(self, entry: AuditEntry) -> None:
        """Log audit entry to file."""
        try:
            # In a real implementation, this would write to a structured log file
            # For now, we'll just use the structured logger
            logger.info(
                "audit_file_entry",
                entry=asdict(entry),
            )
        except Exception as e:
            logger.error("Failed to log audit entry to file", error=str(e))
    
    def _log_to_database(self, entry: AuditEntry) -> None:
        """Log audit entry to database."""
        try:
            # In a real implementation, this would write to a database
            # For now, we'll just use the structured logger
            logger.info(
                "audit_database_entry",
                entry=asdict(entry),
            )
        except Exception as e:
            logger.error("Failed to log audit entry to database", error=str(e))


# Global audit logger instance
_audit_logger: Optional[AuditLogger] = None


def get_audit_logger() -> AuditLogger:
    """Get the global audit logger instance."""
    global _audit_logger
    if _audit_logger is None:
        _audit_logger = AuditLogger()
    return _audit_logger
