"""
incident_manager_utils.py

Manages incident publishing to Redis/Kafka when severity levels change.
Implements 5-consecutive-frame validation before publishing.
Does NOT listen to any input topic - only publishes to output topic.

PRODUCTION-READY VERSION
"""

import json
import time
import threading
import logging
import os
import urllib.request
import base64
import re
from typing import Dict, List, Optional, Any
from datetime import datetime, timezone
from dataclasses import dataclass, field
from pathlib import Path


# Severity level ordering for comparison (none = no incident)
SEVERITY_LEVELS = ["none", "low", "medium", "significant", "critical"]


@dataclass
class IncidentState:
    """Tracks the current incident state for a camera/usecase."""
    current_level: str = "none"        # Current confirmed severity level
    pending_level: str = "none"        # Level being validated (needs 5 frames)
    consecutive_count: int = 0         # Consecutive frames with pending_level
    last_published_level: str = "none" # Last level that was published (for spam prevention)
    incident_cycle_id: int = 0         # Incremented when cycle resets


class INCIDENT_MANAGER:
    """
    Manages incident severity level tracking and publishing.
    
    Key behaviors:
    - Publishes incidents ONLY when severity level changes
    - Requires 5 consecutive frames at same level before publishing
    - NEVER publishes "low" level incidents
    - Supports both Redis and Kafka transports
    - Thread-safe operations
    
    Usage:
        manager = INCIDENT_MANAGER(redis_client=..., kafka_client=...)
        manager.process_incident(camera_id, incident_data, stream_info)
    """
    
    CONSECUTIVE_FRAMES_REQUIRED = 5
    
    def __init__(
        self,
        redis_client: Optional[Any] = None,
        kafka_client: Optional[Any] = None,
        incident_topic: str = "incident_res",
        logger: Optional[logging.Logger] = None
    ):
        """
        Initialize INCIDENT_MANAGER.
        
        Args:
            redis_client: MatriceStream instance configured for Redis
            kafka_client: MatriceStream instance configured for Kafka
            incident_topic: Topic/stream name for publishing incidents
            logger: Python logger instance
        """
        self.redis_client = redis_client
        self.kafka_client = kafka_client
        self.incident_topic = incident_topic
        self.logger = logger or logging.getLogger(__name__)
        
        # Per-camera incident state tracking: {camera_id: IncidentState}
        self._incident_states: Dict[str, IncidentState] = {}
        self._states_lock = threading.Lock()
        
        self.logger.info(
            f"[INCIDENT_MANAGER] Initialized with incident_topic={incident_topic}, "
            f"consecutive_frames_required={self.CONSECUTIVE_FRAMES_REQUIRED}"
        )
    
    def process_incident(
        self,
        camera_id: str,
        incident_data: Dict[str, Any],
        stream_info: Optional[Dict[str, Any]] = None
    ) -> bool:
        """
        Process an incident and publish if severity level changed.
        
        This method should be called for every frame where an incident is detected.
        It tracks the severity level and publishes ONLY when:
        1. Level changes (after 5 consecutive frames validation)
        2. Level is not "low" or "none"
        
        Args:
            camera_id: Unique camera identifier
            incident_data: Incident dictionary from usecase (e.g., from fire_detection._generate_incidents)
            stream_info: Stream metadata
            
        Returns:
            True if incident was published, False otherwise
        """
        try:
            self.logger.debug(f"[INCIDENT_MANAGER] Processing incident for camera: {camera_id}")
            self.logger.debug(f"[INCIDENT_MANAGER] Incident data keys: {list(incident_data.keys()) if incident_data else 'None'}")
            
            # Handle empty incident data
            if not incident_data or incident_data == {}:
                self.logger.debug("[INCIDENT_MANAGER] Empty incident data, treating as level='none'")
                severity_level = "none"
            else:
                # Extract severity level from incident
                severity_level = incident_data.get("severity_level", "none")
                if not severity_level or severity_level == "":
                    severity_level = "none"
            
            severity_level = severity_level.lower().strip()
            
            self.logger.debug(f"[INCIDENT_MANAGER] Extracted severity_level: '{severity_level}'")
            
            # Validate severity level
            if severity_level not in SEVERITY_LEVELS:
                self.logger.warning(
                    f"[INCIDENT_MANAGER] Unknown severity level '{severity_level}', treating as 'none'"
                )
                severity_level = "none"
            
            # Get or create state for this camera
            with self._states_lock:
                if camera_id not in self._incident_states:
                    self._incident_states[camera_id] = IncidentState()
                    self.logger.info(f"[INCIDENT_MANAGER] Created new state for camera: {camera_id}")
                
                state = self._incident_states[camera_id]
                
                self.logger.debug(
                    f"[INCIDENT_MANAGER] Current state - "
                    f"current_level={state.current_level}, "
                    f"pending_level={state.pending_level}, "
                    f"consecutive_count={state.consecutive_count}, "
                    f"last_published_level={state.last_published_level}"
                )
                
                # Check if this is a new pending level or continuation
                if severity_level == state.pending_level:
                    # Same level, increment counter
                    state.consecutive_count += 1
                    self.logger.debug(
                        f"[INCIDENT_MANAGER] Same pending level, "
                        f"consecutive_count now: {state.consecutive_count}"
                    )
                else:
                    # Different level, reset counter
                    state.pending_level = severity_level
                    state.consecutive_count = 1
                    self.logger.debug(
                        f"[INCIDENT_MANAGER] New pending level: {severity_level}, "
                        f"reset consecutive_count to 1"
                    )
                
                # Check if we've reached the threshold for confirmation
                if state.consecutive_count >= self.CONSECUTIVE_FRAMES_REQUIRED:
                    # Level is confirmed after 5 consecutive frames
                    old_level = state.current_level
                    new_level = state.pending_level
                    
                    self.logger.info(
                        f"[INCIDENT_MANAGER] Level confirmed after {state.consecutive_count} frames: "
                        f"{old_level} -> {new_level}"
                    )
                    
                    # Check if level actually changed
                    if new_level != state.current_level:
                        state.current_level = new_level
                        
                        # Reset last_published when going to low/none (cycle reset)
                        # This allows publishing again when level rises in a new cycle
                        if new_level in ("low", "none"):
                            state.last_published_level = "none"
                            state.incident_cycle_id += 1
                            self.logger.info(
                                f"[INCIDENT_MANAGER] Cycle reset - level dropped to {new_level}, "
                                f"cycle_id now: {state.incident_cycle_id}"
                            )
                        
                        # Check if we should publish
                        # 1. Don't publish "low" level incidents (per requirement)
                        # 2. Don't publish "none" level (no incident)
                        # 3. Don't publish same level again (spam prevention)
                        should_publish = (
                            new_level not in ("low", "none") and
                            new_level != state.last_published_level
                        )
                        
                        self.logger.info(
                            f"[INCIDENT_MANAGER] Level changed: {old_level} -> {new_level}, "
                            f"should_publish={should_publish} "
                            f"(last_published={state.last_published_level})"
                        )
                        
                        if should_publish:
                            # Publish the incident
                            success = self._publish_incident(
                                camera_id, incident_data, stream_info
                            )
                            if success:
                                state.last_published_level = new_level
                                self.logger.info(
                                    f"[INCIDENT_MANAGER] ✓ Published incident for level: {new_level}"
                                )
                            return success
                        else:
                            self.logger.debug(
                                f"[INCIDENT_MANAGER] Skipping publish - "
                                f"level={new_level}, excluded or already published"
                            )
                    else:
                        self.logger.debug(
                            f"[INCIDENT_MANAGER] No level change, staying at: {state.current_level}"
                        )
                    
                    # Keep consecutive count at threshold (don't reset to 0)
                    # This prevents re-triggering on continued same-level frames
                
                return False
            
        except Exception as e:
            self.logger.error(
                f"[INCIDENT_MANAGER] Error processing incident: {e}", 
                exc_info=True
            )
            return False
    
    def _publish_incident(
        self,
        camera_id: str,
        incident_data: Dict[str, Any],
        stream_info: Optional[Dict[str, Any]] = None
    ) -> bool:
        """
        Publish incident to Redis/Kafka topic.
        
        Args:
            camera_id: Camera identifier
            incident_data: Incident dictionary
            stream_info: Stream metadata
            
        Returns:
            True if published successfully, False otherwise
        """
        self.logger.info(f"[INCIDENT_MANAGER] ========== PUBLISHING INCIDENT ==========")
        
        try:
            # Build the incident message
            message = self._build_incident_message(camera_id, incident_data, stream_info)
            
            self.logger.info(f"[INCIDENT_MANAGER] Built incident message: {json.dumps(message, default=str)[:500]}...")
            
            success = False
            
            # Try Redis first (primary)
            if self.redis_client:
                try:
                    self.logger.debug(
                        f"[INCIDENT_MANAGER] Publishing to Redis stream: {self.incident_topic}"
                    )
                    self._publish_to_redis(self.incident_topic, message)
                    self.logger.info(
                        f"[INCIDENT_MANAGER] ✓ Incident published to Redis"
                    )
                    success = True
                except Exception as e:
                    self.logger.error(
                        f"[INCIDENT_MANAGER] ❌ Redis publish failed: {e}", 
                        exc_info=True
                    )
            
            # Fallback to Kafka if Redis failed or no Redis client
            if not success and self.kafka_client:
                try:
                    self.logger.debug(
                        f"[INCIDENT_MANAGER] Publishing to Kafka topic: {self.incident_topic}"
                    )
                    self._publish_to_kafka(self.incident_topic, message)
                    self.logger.info(
                        f"[INCIDENT_MANAGER] ✓ Incident published to Kafka"
                    )
                    success = True
                except Exception as e:
                    self.logger.error(
                        f"[INCIDENT_MANAGER] ❌ Kafka publish failed: {e}", 
                        exc_info=True
                    )
            
            if success:
                self.logger.info(f"[INCIDENT_MANAGER] ========== INCIDENT PUBLISHED ==========")
            else:
                self.logger.error(
                    f"[INCIDENT_MANAGER] ❌ INCIDENT NOT PUBLISHED (both transports failed)"
                )
            
            return success
            
        except Exception as e:
            self.logger.error(
                f"[INCIDENT_MANAGER] Error publishing incident: {e}", 
                exc_info=True
            )
            return False
    
    def _build_incident_message(
        self,
        camera_id: str,
        incident_data: Dict[str, Any],
        stream_info: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """Build the incident message in the required format."""
        
        # Extract camera and stream info
        camera_name = ""
        inference_pipeline_id = ""
        camera_group = ""
        location = ""
        app_deployment_id = ""
        
        if stream_info:
            camera_info = stream_info.get("camera_info", {})
            camera_name = camera_info.get("camera_name", "")
            location = camera_info.get("location", "")
            inference_pipeline_id = stream_info.get("inferencePipelineId", 
                                                     stream_info.get("inference_pipeline_id", ""))
            camera_group = stream_info.get("camera_group", "")
            app_deployment_id = stream_info.get("app_deployment_id", "")
        
        # Build incident in required format
        incident = {
            "incident_id": incident_data.get("incident_id", ""),
            "incident_type": incident_data.get("incident_type", ""),
            "severity_level": incident_data.get("severity_level", ""),
            "human_text": incident_data.get("human_text", ""),
            "start_time": incident_data.get("start_time", ""),
            "end_time": incident_data.get("end_time", ""),
            "camera_info": incident_data.get("camera_info", ""),
            "level_settings": incident_data.get("level_settings", {}),
        }
        
        # Get application name from incident type
        incident_type = incident_data.get("incident_type", "")
        application_name = incident_type.replace("_", " ").title() if incident_type else ""
        application_key_name = incident_type if incident_type else ""
        
        message = {
            "camera_id": camera_id,
            "camera_name": camera_name,
            "inferencePipelineId": inference_pipeline_id,
            "camera_group": camera_group,
            "location": location,
            "app_deployment_id": app_deployment_id,
            "application_name": application_name,
            "application_key_name": application_key_name,
            "application_version": "1.3",
            "published_at": datetime.now(timezone.utc).isoformat(),
            "incidents": [incident]
        }
        
        return message
    
    def _publish_to_redis(self, topic: str, message: Dict[str, Any]):
        """Publish message to Redis stream."""
        try:
            self.redis_client.add_message(
                topic_or_channel=topic,
                message=json.dumps(message),
                key=message.get("camera_id", "")
            )
        except Exception as e:
            self.logger.error(f"[INCIDENT_MANAGER] Redis publish error: {e}")
            raise
    
    def _publish_to_kafka(self, topic: str, message: Dict[str, Any]):
        """Publish message to Kafka topic."""
        try:
            self.kafka_client.add_message(
                topic_or_channel=topic,
                message=json.dumps(message),
                key=message.get("camera_id", "")
            )
        except Exception as e:
            self.logger.error(f"[INCIDENT_MANAGER] Kafka publish error: {e}")
            raise
    
    def reset_camera_state(self, camera_id: str):
        """Reset incident state for a specific camera."""
        with self._states_lock:
            if camera_id in self._incident_states:
                self._incident_states[camera_id] = IncidentState()
                self.logger.info(f"[INCIDENT_MANAGER] Reset state for camera: {camera_id}")
    
    def get_camera_state(self, camera_id: str) -> Optional[Dict[str, Any]]:
        """Get current incident state for a camera (for debugging)."""
        with self._states_lock:
            state = self._incident_states.get(camera_id)
            if state:
                return {
                    "current_level": state.current_level,
                    "pending_level": state.pending_level,
                    "consecutive_count": state.consecutive_count,
                    "last_published_level": state.last_published_level,
                    "incident_cycle_id": state.incident_cycle_id
                }
            return None
    
    def get_all_camera_states(self) -> Dict[str, Dict[str, Any]]:
        """Get all camera states for debugging/monitoring."""
        with self._states_lock:
            return {
                cam_id: {
                    "current_level": state.current_level,
                    "pending_level": state.pending_level,
                    "consecutive_count": state.consecutive_count,
                    "last_published_level": state.last_published_level,
                    "incident_cycle_id": state.incident_cycle_id
                }
                for cam_id, state in self._incident_states.items()
            }


class IncidentManagerFactory:
    """
    Factory class for creating INCIDENT_MANAGER instances.
    
    Handles session initialization and Redis/Kafka client creation
    following the same pattern as license_plate_monitoring.py.
    """
    
    ACTION_ID_PATTERN = re.compile(r"^[0-9a-f]{8,}$", re.IGNORECASE)
    
    def __init__(self, logger: Optional[logging.Logger] = None):
        self.logger = logger or logging.getLogger(__name__)
        self._initialized = False
        self._incident_manager: Optional[INCIDENT_MANAGER] = None
        
        # Store these for later access
        self._session = None
        self._action_id: Optional[str] = None
        self._instance_id: Optional[str] = None
        self._deployment_id: Optional[str] = None
        self._app_deployment_id: Optional[str] = None
        self._external_ip: Optional[str] = None
    
    def initialize(self, config: Any) -> Optional[INCIDENT_MANAGER]:
        """
        Initialize and return INCIDENT_MANAGER with Redis/Kafka clients.
        
        This follows the same pattern as license_plate_monitoring.py for
        session initialization and Redis/Kafka client creation.
        
        Args:
            config: Configuration object with session, server_id, etc.
            
        Returns:
            INCIDENT_MANAGER instance or None if initialization failed
        """
        if self._initialized and self._incident_manager is not None:
            self.logger.debug("[INCIDENT_MANAGER_FACTORY] Already initialized, returning existing instance")
            return self._incident_manager
        
        try:
            # Import required modules
            from matrice_common.stream.matrice_stream import MatriceStream, StreamType
            from matrice_common.session import Session
            
            self.logger.info("[INCIDENT_MANAGER_FACTORY] ===== STARTING INITIALIZATION =====")
            
            # Get or create session
            self._session = getattr(config, 'session', None)
            if not self._session:
                self.logger.info("[INCIDENT_MANAGER_FACTORY] No session in config, creating from environment...")
                account_number = os.getenv("MATRICE_ACCOUNT_NUMBER", "")
                access_key_id = os.getenv("MATRICE_ACCESS_KEY_ID", "")
                secret_key = os.getenv("MATRICE_SECRET_ACCESS_KEY", "")
                project_id = os.getenv("MATRICE_PROJECT_ID", "")
                
                self.logger.debug(f"[INCIDENT_MANAGER_FACTORY] Env vars - account: {'SET' if account_number else 'NOT SET'}, "
                                  f"access_key: {'SET' if access_key_id else 'NOT SET'}, "
                                  f"secret: {'SET' if secret_key else 'NOT SET'}")
                
                
                self._session = Session(
                    account_number=account_number,
                    access_key=access_key_id,
                    secret_key=secret_key,
                    project_id=project_id,
                )
                self.logger.info("[INCIDENT_MANAGER_FACTORY] ✓ Created session from environment")
            else:
                self.logger.info("[INCIDENT_MANAGER_FACTORY] ✓ Using session from config")
            
            rpc = self._session.rpc
            
            # Discover action_id
            self._action_id = self._discover_action_id()
            if not self._action_id:
                self.logger.error("[INCIDENT_MANAGER_FACTORY] ❌ Could not discover action_id")
                print("----- INCIDENT MANAGER ACTION DISCOVERY -----")
                print("action_id: NOT FOUND")
                print("---------------------------------------------")
                self._initialized = True
                return None
            
            self.logger.info(f"[INCIDENT_MANAGER_FACTORY] ✓ Discovered action_id: {self._action_id}")
            
            # Fetch action details
            try:
                action_url = f"/v1/actions/action/{self._action_id}/details"
                action_resp = rpc.get(action_url)
                if not (action_resp and action_resp.get("success", False)):
                    raise RuntimeError(
                        action_resp.get("message", "Unknown error") 
                        if isinstance(action_resp, dict) else "Unknown error"
                    )
                action_doc = action_resp.get("data", {}) if isinstance(action_resp, dict) else {}
                action_details = action_doc.get("actionDetails", {}) if isinstance(action_doc, dict) else {}
                
                # Extract server details
                server_id = (
                    action_details.get("serverId")
                    or action_details.get("server_id")
                    or action_details.get("serverID")
                    or action_details.get("redis_server_id")
                    or action_details.get("kafka_server_id")
                )
                server_type = (
                    action_details.get("serverType")
                    or action_details.get("server_type")
                    or action_details.get("type")
                )
                
                # Store identifiers
                self._deployment_id = action_details.get("_idDeployment") or action_details.get("deployment_id")
                self._app_deployment_id = action_details.get("app_deployment_id")
                self._instance_id = action_details.get("instanceID") or action_details.get("instanceId")
                self._external_ip = action_details.get("externalIP") or action_details.get("externalIp")
                
                print("----- INCIDENT MANAGER ACTION DETAILS -----")
                print(f"action_id: {self._action_id}")
                print(f"server_type: {server_type}")
                print(f"server_id: {server_id}")
                print(f"deployment_id: {self._deployment_id}")
                print(f"app_deployment_id: {self._app_deployment_id}")
                print(f"instance_id: {self._instance_id}")
                print(f"external_ip: {self._external_ip}")
                print("--------------------------------------------")
                
                self.logger.info(
                    f"[INCIDENT_MANAGER_FACTORY] Action details - server_type={server_type}, "
                    f"instance_id={self._instance_id}"
                )
                
            except Exception as e:
                self.logger.error(f"[INCIDENT_MANAGER_FACTORY] ❌ Failed to fetch action details: {e}", exc_info=True)
                print("----- INCIDENT MANAGER ACTION DETAILS ERROR -----")
                print(f"action_id: {self._action_id}")
                print(f"error: {e}")
                print("-------------------------------------------------")
                self._initialized = True
                return None
            
            # Determine localhost vs cloud
            is_localhost = False
            public_ip = self._get_public_ip()
            
            # Try to get server info to determine if localhost
            # Check for any server_id in config (LPR, facial recognition, etc.)
            server_info_id = (
                getattr(config, 'lpr_server_id', None) or 
                getattr(config, 'server_id', None) or
                getattr(config, 'facial_recognition_server_id', None)
            )
            
            if server_info_id:
                try:
                    # Try LPR server endpoint first
                    response = rpc.get(f"/v1/actions/lpr_servers/{server_info_id}")
                    if response.get("success", False) and response.get("data"):
                        server_data = response.get("data", {})
                        server_host = server_data.get("host", "")
                        
                        localhost_indicators = ["localhost", "127.0.0.1", "0.0.0.0"]
                        if server_host in localhost_indicators or server_host == public_ip:
                            is_localhost = True
                            self.logger.info(
                                f"[INCIDENT_MANAGER_FACTORY] Detected Localhost environment "
                                f"(Public IP={public_ip}, Server IP={server_host})"
                            )
                        else:
                            is_localhost = False
                            self.logger.info(
                                f"[INCIDENT_MANAGER_FACTORY] Detected Cloud environment "
                                f"(Public IP={public_ip}, Server IP={server_host})"
                            )
                    else:
                        self.logger.warning("[INCIDENT_MANAGER_FACTORY] Failed to fetch server info, defaulting to Cloud mode")
                except Exception as e:
                    self.logger.warning(f"[INCIDENT_MANAGER_FACTORY] Error detecting environment: {e}, defaulting to Cloud mode")
            else:
                self.logger.info("[INCIDENT_MANAGER_FACTORY] No server ID in config, defaulting to Cloud mode")
            
            redis_client = None
            kafka_client = None
            
            # STRICT SWITCH: Only Redis if localhost, Only Kafka if cloud
            if is_localhost:
                # Initialize Redis client (ONLY) using instance_id
                if not self._instance_id:
                    self.logger.error("[INCIDENT_MANAGER_FACTORY] ❌ Localhost mode but instance_id missing")
                else:
                    try:
                        url = f"/v1/actions/get_redis_server_by_instance_id/{self._instance_id}"
                        self.logger.info(f"[INCIDENT_MANAGER_FACTORY] Fetching Redis server info for instance: {self._instance_id}")
                        response = rpc.get(url)
                        
                        if isinstance(response, dict) and response.get("success", False):
                            data = response.get("data", {})
                            host = data.get("host")
                            port = data.get("port")
                            username = data.get("username")
                            password = data.get("password", "")
                            db_index = data.get("db", 0)
                            conn_timeout = data.get("connection_timeout", 120)
                            
                            print("----- INCIDENT MANAGER REDIS SERVER PARAMS -----")
                            print(f"instance_id: {self._instance_id}")
                            print(f"host: {host}")
                            print(f"port: {port}")
                            print(f"username: {username}")
                            print(f"password: {'*' * len(password) if password else ''}")
                            print(f"db: {db_index}")
                            print(f"connection_timeout: {conn_timeout}")
                            print("------------------------------------------------")
                            
                            self.logger.info(
                                f"[INCIDENT_MANAGER_FACTORY] Redis params - host={host}, port={port}, user={username}"
                            )
                            
                            redis_client = MatriceStream(
                                StreamType.REDIS,
                                host=host,
                                port=int(port),
                                password=password,
                                username=username,
                                db=db_index,
                                connection_timeout=conn_timeout
                            )
                            redis_client.setup("incident_res")
                            self.logger.info("[INCIDENT_MANAGER_FACTORY] ✓ Redis client initialized")
                        else:
                            self.logger.warning(
                                f"[INCIDENT_MANAGER_FACTORY] Failed to fetch Redis server info: "
                                f"{response.get('message', 'Unknown error') if isinstance(response, dict) else 'Unknown error'}"
                            )
                    except Exception as e:
                        self.logger.warning(f"[INCIDENT_MANAGER_FACTORY] Redis initialization failed: {e}")
            
            else:
                # Initialize Kafka client (ONLY) using global info endpoint
                try:
                    url = f"/v1/actions/get_kafka_info"
                    self.logger.info("[INCIDENT_MANAGER_FACTORY] Fetching Kafka server info for Cloud mode")
                    response = rpc.get(url)
                    
                    if isinstance(response, dict) and response.get("success", False):
                        data = response.get("data", {})
                        enc_ip = data.get("ip")
                        enc_port = data.get("port")
                        
                        # Decode base64 encoded values
                        ip_addr = None
                        port = None
                        try:
                            ip_addr = base64.b64decode(str(enc_ip)).decode("utf-8")
                        except Exception:
                            ip_addr = enc_ip
                        try:
                            port = base64.b64decode(str(enc_port)).decode("utf-8")
                        except Exception:
                            port = enc_port
                        
                        print("----- INCIDENT MANAGER KAFKA SERVER PARAMS -----")
                        print(f"ipAddress: {ip_addr}")
                        print(f"port: {port}")
                        print("------------------------------------------------")
                        
                        self.logger.info(f"[INCIDENT_MANAGER_FACTORY] Kafka params - ip={ip_addr}, port={port}")
                        
                        bootstrap_servers = f"{ip_addr}:{port}"
                        kafka_client = MatriceStream(
                            StreamType.KAFKA,
                            bootstrap_servers=bootstrap_servers,
                            sasl_mechanism="SCRAM-SHA-256",
                            sasl_username="matrice-sdk-user",
                            sasl_password="matrice-sdk-password",
                            security_protocol="SASL_PLAINTEXT"
                        )
                        kafka_client.setup("incident_res", consumer_group_id="py_analytics_incidents")
                        self.logger.info(f"[INCIDENT_MANAGER_FACTORY] ✓ Kafka client initialized (servers={bootstrap_servers})")
                    else:
                        self.logger.warning(
                            f"[INCIDENT_MANAGER_FACTORY] Failed to fetch Kafka server info: "
                            f"{response.get('message', 'Unknown error') if isinstance(response, dict) else 'Unknown error'}"
                        )
                except Exception as e:
                    self.logger.warning(f"[INCIDENT_MANAGER_FACTORY] Kafka initialization failed: {e}")
            
            # Create incident manager if we have at least one transport
            if redis_client or kafka_client:
                self._incident_manager = INCIDENT_MANAGER(
                    redis_client=redis_client,
                    kafka_client=kafka_client,
                    incident_topic="incident_res",
                    logger=self.logger
                )
                transport = "Redis" if redis_client else "Kafka"
                self.logger.info(f"[INCIDENT_MANAGER_FACTORY] ✓ Incident manager created with {transport}")
                print(f"----- INCIDENT MANAGER INITIALIZED ({transport}) -----")
            else:
                self.logger.warning(
                    f"[INCIDENT_MANAGER_FACTORY] No {'Redis' if is_localhost else 'Kafka'} client available, "
                    f"incident manager not created"
                )
            
            self._initialized = True
            self.logger.info("[INCIDENT_MANAGER_FACTORY] ===== INITIALIZATION COMPLETE =====")
            return self._incident_manager
            
        except ImportError as e:
            self.logger.error(f"[INCIDENT_MANAGER_FACTORY] Import error: {e}")
            self._initialized = True
            return None
        except Exception as e:
            self.logger.error(f"[INCIDENT_MANAGER_FACTORY] Initialization failed: {e}", exc_info=True)
            self._initialized = True
            return None
    
    def _discover_action_id(self) -> Optional[str]:
        """Discover action_id from current working directory name (and parents)."""
        try:
            candidates: List[str] = []
            
            try:
                cwd = Path.cwd()
                candidates.append(cwd.name)
                for parent in cwd.parents:
                    candidates.append(parent.name)
            except Exception:
                pass
            
            try:
                usr_src = Path("/usr/src")
                if usr_src.exists():
                    for child in usr_src.iterdir():
                        if child.is_dir():
                            candidates.append(child.name)
            except Exception:
                pass
            
            for candidate in candidates:
                if candidate and len(candidate) >= 8 and self.ACTION_ID_PATTERN.match(candidate):
                    return candidate
        except Exception:
            pass
        return None
    
    def _get_public_ip(self) -> str:
        """Get the public IP address of this machine."""
        self.logger.info("[INCIDENT_MANAGER_FACTORY] Fetching public IP address...")
        try:
            public_ip = urllib.request.urlopen(
                "https://v4.ident.me", timeout=120
            ).read().decode("utf8").strip()
            self.logger.debug(f"[INCIDENT_MANAGER_FACTORY] Public IP: {public_ip}")
            return public_ip
        except Exception as e:
            self.logger.warning(f"[INCIDENT_MANAGER_FACTORY] Error fetching public IP: {e}")
            return "localhost"
    
    def _get_backend_base_url(self) -> str:
        """Resolve backend base URL based on ENV variable."""
        env = os.getenv("ENV", "prod").strip().lower()
        if env in ("prod", "production"):
            host = "prod.backend.app.matrice.ai"
        elif env in ("dev", "development"):
            host = "dev.backend.app.matrice.ai"
        else:
            host = "staging.backend.app.matrice.ai"
        return f"https://{host}"
    
    @property
    def is_initialized(self) -> bool:
        return self._initialized
    
    @property
    def incident_manager(self) -> Optional[INCIDENT_MANAGER]:
        return self._incident_manager


# Module-level factory instance for convenience
_default_factory: Optional[IncidentManagerFactory] = None


def get_incident_manager(config: Any, logger: Optional[logging.Logger] = None) -> Optional[INCIDENT_MANAGER]:
    """
    Get or create INCIDENT_MANAGER instance.
    
    This is a convenience function that uses a module-level factory.
    For more control, use IncidentManagerFactory directly.
    
    Args:
        config: Configuration object with session, server_id, etc.
        logger: Logger instance
        
    Returns:
        INCIDENT_MANAGER instance or None
    """
    global _default_factory
    
    if _default_factory is None:
        _default_factory = IncidentManagerFactory(logger=logger)
    
    return _default_factory.initialize(config)

