"""
alert_instance_utils.py

THIS IS PRODUCTION. HIGH-QUALITY ACCURACY REQUIRED. CHECK EVERYTHING CAREFULLY.

Instant Alert System - Python Integration Utility

This module provides the ALERT_INSTANCE class for managing real-time alerts
in the Matrice analytics pipeline. It handles:
- Alert configuration management (create/update/delete via Kafka/Redis)
- Detection event evaluation against alert criteria
- Trigger message publishing to backend

Usage Example:
    ```python
    from matrice_analytics.post_processing.utils.alert_instance_utils import ALERT_INSTANCE
    from matrice_common.stream.matrice_stream import MatriceStream, StreamType

    # Initialize with Redis (primary) and Kafka (fallback)
    alert_manager = ALERT_INSTANCE(
        redis_client=redis_stream,
        kafka_client=kafka_stream,
        config_topic="alert_instant_config_request",
        trigger_topic="alert_instant_triggered",
        polling_interval=100,  # seconds
        logger=logger
    )

    # Start monitoring for config updates
    alert_manager.start()

    # Process detection events from inference pipeline
    detection_event = {
        "camera_id": "673d8c177481e811e530ad51",
        "app_deployment_id": "673d8c177481e811e530ad52",
        "application_id": "673d8c177481e811e530ad53",
        "detectionType": "license_plate",
        "plateNumber": "ABC123",
        "confidence": 0.92,
        "frameUrl": "s3://bucket/frames/frame_12345.jpg",
        "coordinates": {"x": 120, "y": 450, "width": 200, "height": 80},
        "cameraName": "Entry Gate Camera"
    }
    alert_manager.process_detection_event(detection_event)

    # Stop gracefully
    alert_manager.stop()
    ```

Topics (exact names from documentation):
    - Config messages (Go → Python): alert_instant_config_request
    - Trigger messages (Python → Go): alert_instant_triggered
"""

import json
import time
import threading
import logging
from typing import Dict, List, Optional, Any
from datetime import datetime, timezone
from dataclasses import dataclass, field


@dataclass
class AlertConfig:
    """Represents an instant alert configuration."""
    instant_alert_id: str
    camera_id: str
    app_deployment_id: str
    application_id: str
    alert_name: str
    detection_config: Dict[str, Any]
    severity_level: str
    is_active: bool
    action: str
    timestamp: str
    last_updated: float = field(default_factory=time.time)

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "AlertConfig":
        """Create AlertConfig from dictionary."""
        return cls(
            instant_alert_id=data.get("instant_alert_id", ""),
            camera_id=data.get("camera_id", ""),
            app_deployment_id=data.get("app_deployment_id", ""),
            application_id=data.get("application_id", ""),
            alert_name=data.get("alert_name", ""),
            detection_config=data.get("detection_config", {}),
            severity_level=data.get("severity_level", "medium"),
            is_active=data.get("is_active", True),
            action=data.get("action", "create"),
            timestamp=data.get("timestamp", datetime.now(timezone.utc).isoformat()),
            last_updated=time.time()
        )


class ALERT_INSTANCE:
    """
    Manages instant alert configurations and evaluates detection events.

    This class handles:
    - Polling alert configs from Redis/Kafka every 100 seconds
    - Maintaining in-memory alert state
    - Evaluating detection events against alert criteria
    - Publishing trigger messages when matches occur

    Transport Priority:
    - Redis is primary for both config reading and trigger publishing
    - Kafka is fallback when Redis operations fail
    """

    def __init__(
        self,
        redis_client: Optional[Any] = None,
        kafka_client: Optional[Any] = None,
        config_topic: str = "alert_instant_config_request",
        trigger_topic: str = "alert_instant_triggered",
        polling_interval: int = 100,
        logger: Optional[logging.Logger] = None
    ):
        """
        Initialize ALERT_INSTANCE.

        Args:
            redis_client: MatriceStream instance configured for Redis (primary transport)
            kafka_client: MatriceStream instance configured for Kafka (fallback transport)
            config_topic: Topic/stream name for receiving alert configs (default: alert_instant_config_request)
            trigger_topic: Topic/stream name for publishing triggers (default: alert_instant_triggered)
            polling_interval: Seconds between config polling (default: 100)
            logger: Python logger instance
        """
        self.redis_client = redis_client
        self.kafka_client = kafka_client
        self.config_topic = config_topic
        self.trigger_topic = trigger_topic
        self.polling_interval = polling_interval
        self.logger = logger or logging.getLogger(__name__)

        # In-memory alert storage: {instant_alert_id: AlertConfig}
        self._alerts: Dict[str, AlertConfig] = {}
        self._alerts_lock = threading.Lock()

        # Polling thread control
        self._polling_thread: Optional[threading.Thread] = None
        self._stop_event = threading.Event()
        self._running = False

        self.logger.info(
            f"Initialized ALERT_INSTANCE with config_topic={config_topic}, "
            f"trigger_topic={trigger_topic}, polling_interval={polling_interval}s"
        )

    def start(self):
        """Start the background polling thread for config updates."""
        if self._running:
            self.logger.warning("ALERT_INSTANCE already running")
            return

        self._running = True
        self._stop_event.clear()
        self._polling_thread = threading.Thread(
            target=self._polling_loop,
            daemon=True,
            name="AlertConfigPoller"
        )
        self._polling_thread.start()
        self.logger.info("Started alert config polling thread")

    def stop(self):
        """Stop the background polling thread gracefully."""
        if not self._running:
            return

        self.logger.info("Stopping ALERT_INSTANCE...")
        self._running = False
        self._stop_event.set()

        if self._polling_thread and self._polling_thread.is_alive():
            self._polling_thread.join(timeout=5)

        self.logger.info("ALERT_INSTANCE stopped")

    def _polling_loop(self):
        """Background thread that polls for config updates every polling_interval seconds."""
        self.logger.info(f"Alert config polling loop started (interval: {self.polling_interval}s)")

        while not self._stop_event.is_set():
            try:
                self._fetch_and_update_configs()
            except Exception as e:
                self.logger.error(f"Error in polling loop: {e}", exc_info=True)

            # Sleep in small increments to allow quick shutdown
            for _ in range(self.polling_interval):
                if self._stop_event.is_set():
                    break
                time.sleep(1)

        self.logger.info("Alert config polling loop exited")

    def _fetch_and_update_configs(self):
        """Fetch config messages from Redis (primary) or Kafka (fallback)."""
        configs = []

        # Try Redis first (primary)
        if self.redis_client:
            try:
                self.logger.debug(f"Fetching configs from Redis stream: {self.config_topic}")
                configs = self._read_from_redis(self.config_topic)
                if configs:
                    self.logger.info(f"Fetched {len(configs)} config(s) from Redis")
            except Exception as e:
                self.logger.error(f"Redis config fetch failed: {e}", exc_info=True)
                # Fall through to Kafka fallback

        # Fallback to Kafka if Redis failed or no client
        if not configs and self.kafka_client:
            try:
                self.logger.debug(f"Falling back to Kafka topic: {self.config_topic}")
                configs = self._read_from_kafka(self.config_topic)
                if configs:
                    self.logger.info(f"Fetched {len(configs)} config(s) from Kafka")
            except Exception as e:
                self.logger.error(f"Kafka config fetch failed: {e}", exc_info=True)

        # Update in-memory alert configs
        for config_data in configs:
            try:
                self._handle_config_message(config_data)
            except Exception as e:
                self.logger.error(f"Error handling config message: {e}", exc_info=True)

    def _read_from_redis(self, topic: str, max_messages: int = 100) -> List[Dict[str, Any]]:
        """
        Read messages from Redis stream.

        Args:
            topic: Redis stream name
            max_messages: Maximum messages to fetch

        Returns:
            List of parsed message dictionaries
        """
        messages = []
        try:
            for _ in range(max_messages):
                msg = self.redis_client.get_message(timeout=0.1)
                if not msg:
                    break

                value = msg.get('value') or msg.get('data')
                if value:
                    if isinstance(value, bytes):
                        value = value.decode('utf-8')
                    if isinstance(value, str):
                        value = json.loads(value)
                    messages.append(value)
        except Exception as e:
            self.logger.error(f"Error reading from Redis: {e}")
            raise

        return messages

    def _read_from_kafka(self, topic: str, max_messages: int = 100) -> List[Dict[str, Any]]:
        """
        Read messages from Kafka topic.

        Args:
            topic: Kafka topic name
            max_messages: Maximum messages to fetch

        Returns:
            List of parsed message dictionaries
        """
        messages = []
        try:
            for _ in range(max_messages):
                msg = self.kafka_client.get_message(timeout=0.1)
                if not msg:
                    break

                value = msg.get('value') or msg.get('data')
                if value:
                    if isinstance(value, bytes):
                        value = value.decode('utf-8')
                    if isinstance(value, str):
                        value = json.loads(value)
                    messages.append(value)
        except Exception as e:
            self.logger.error(f"Error reading from Kafka: {e}")
            raise

        return messages

    def _handle_config_message(self, config_data: Dict[str, Any]):
        """
        Handle a single config message (create/update/delete).

        Args:
            config_data: Alert configuration dictionary
        """
        try:
            alert_config = AlertConfig.from_dict(config_data)
            action = alert_config.action.lower()
            alert_id = alert_config.instant_alert_id

            with self._alerts_lock:
                if action == "create":
                    # Idempotency: avoid duplicate entries
                    if alert_id in self._alerts:
                        self.logger.info(f"Alert {alert_id} already exists, treating as update")
                    self._alerts[alert_id] = alert_config
                    self.logger.info(f"Created/Updated alert: {alert_id} ({alert_config.alert_name})")

                elif action == "update":
                    self._alerts[alert_id] = alert_config
                    self.logger.info(f"Updated alert: {alert_id} ({alert_config.alert_name})")

                elif action == "delete":
                    if alert_id in self._alerts:
                        del self._alerts[alert_id]
                        self.logger.info(f"Deleted alert: {alert_id}")
                    else:
                        self.logger.warning(f"Delete requested for non-existent alert: {alert_id}")

                # Also deactivate if is_active is False
                if not alert_config.is_active and alert_id in self._alerts:
                    del self._alerts[alert_id]
                    self.logger.info(f"Deactivated alert: {alert_id}")

        except Exception as e:
            self.logger.error(f"Error handling config message: {e}", exc_info=True)

    def process_detection_event(self, detection_payload: Dict[str, Any]):
        """
        Process a detection event and evaluate against active alerts.

        This is the main entry point for inference services to feed detection events.

        Args:
            detection_payload: Detection event data with keys like:
                - camera_id (required)
                - app_deployment_id (optional, for additional filtering)
                - application_id (optional, for additional filtering)
                - detectionType (e.g., "license_plate", "object_count")
                - plateNumber, confidence, frameUrl, coordinates, etc.
        """
        try:
            camera_id = detection_payload.get("camera_id")
            if not camera_id:
                self.logger.warning("Detection event missing camera_id, skipping")
                return

            # Get all active alerts for this camera
            matching_alerts = self._get_alerts_for_camera(camera_id)

            if not matching_alerts:
                self.logger.debug(f"No active alerts for camera: {camera_id}")
                return

            # Evaluate each alert
            for alert in matching_alerts:
                try:
                    if self._evaluate_alert(alert, detection_payload):
                        # Alert criteria matched, publish trigger
                        self._publish_trigger(alert, detection_payload)
                except Exception as e:
                    self.logger.error(
                        f"Error evaluating alert {alert.instant_alert_id}: {e}",
                        exc_info=True
                    )

        except Exception as e:
            self.logger.error(f"Error processing detection event: {e}", exc_info=True)

    def _get_alerts_for_camera(self, camera_id: str) -> List[AlertConfig]:
        """Get all active alerts for a specific camera."""
        with self._alerts_lock:
            return [
                alert for alert in self._alerts.values()
                if alert.camera_id == camera_id and alert.is_active
            ]

    def _evaluate_alert(self, alert: AlertConfig, detection: Dict[str, Any]) -> bool:
        """
        Evaluate if a detection matches alert criteria.

        Args:
            alert: Alert configuration
            detection: Detection event data

        Returns:
            True if alert criteria are met, False otherwise
        """
        detection_type = detection.get("detectionType", "").lower()
        config = alert.detection_config

        # License Plate Recognition (LPR)
        if detection_type == "license_plate":
            return self._evaluate_lpr_alert(alert, detection, config)

        # Object Count
        elif detection_type == "object_count":
            return self._evaluate_count_alert(alert, detection, config)

        # Fire/Smoke Detection
        elif detection_type == "fire_smoke":
            return self._evaluate_fire_smoke_alert(alert, detection, config)

        # Intrusion Detection
        elif detection_type == "intrusion":
            return self._evaluate_intrusion_alert(alert, detection, config)

        else:
            self.logger.warning(f"Unknown detection type: {detection_type}")
            return False

    def _evaluate_lpr_alert(
        self,
        alert: AlertConfig,
        detection: Dict[str, Any],
        config: Dict[str, Any]
    ) -> bool:
        """Evaluate license plate detection against alert criteria."""
        target_plates = config.get("targetPlates", [])
        min_confidence = config.get("minConfidence", 0.0)

        plate_number = detection.get("plateNumber", "").upper().strip()
        confidence = detection.get("confidence", 0.0)

        # Check if plate matches target list (case-insensitive)
        plate_match = any(
            plate_number == str(target).upper().strip()
            for target in target_plates
        )

        # Check confidence threshold
        confidence_match = confidence >= min_confidence

        if plate_match and confidence_match:
            self.logger.info(
                f"LPR alert triggered: {alert.alert_name} - "
                f"Plate: {plate_number}, Confidence: {confidence:.2f}"
            )
            return True

        return False

    def _evaluate_count_alert(
        self,
        alert: AlertConfig,
        detection: Dict[str, Any],
        config: Dict[str, Any]
    ) -> bool:
        """Evaluate object count against threshold."""
        threshold_count = config.get("thresholdCount", 0)
        current_count = detection.get("currentCount", 0)

        if current_count >= threshold_count:
            self.logger.info(
                f"Count alert triggered: {alert.alert_name} - "
                f"Count: {current_count}, Threshold: {threshold_count}"
            )
            return True

        return False

    def _evaluate_fire_smoke_alert(
        self,
        alert: AlertConfig,
        detection: Dict[str, Any],
        config: Dict[str, Any]
    ) -> bool:
        """Evaluate fire/smoke detection."""
        min_confidence = config.get("minConfidence", 0.0)
        confidence = detection.get("confidence", 0.0)

        fire_detected = detection.get("fireDetected", False)
        smoke_detected = detection.get("smokeDetected", False)

        if (fire_detected or smoke_detected) and confidence >= min_confidence:
            self.logger.info(
                f"Fire/Smoke alert triggered: {alert.alert_name} - "
                f"Fire: {fire_detected}, Smoke: {smoke_detected}, Confidence: {confidence:.2f}"
            )
            return True

        return False

    def _evaluate_intrusion_alert(
        self,
        alert: AlertConfig,
        detection: Dict[str, Any],
        config: Dict[str, Any]
    ) -> bool:
        """Evaluate intrusion detection."""
        min_confidence = config.get("minConfidence", 0.0)
        confidence = detection.get("confidence", 0.0)

        if confidence >= min_confidence:
            self.logger.info(
                f"Intrusion alert triggered: {alert.alert_name} - "
                f"Confidence: {confidence:.2f}"
            )
            return True

        return False

    def _publish_trigger(self, alert: AlertConfig, detection: Dict[str, Any]):
        """
        Publish trigger message to backend.

        Args:
            alert: Alert configuration that was triggered
            detection: Detection event that triggered the alert
        """
        # Build unified internal output dict
        unified_output = self._build_unified_output(alert, detection)

        # Build trigger message for Go backend
        trigger_message = self._build_trigger_message(alert, detection, unified_output)

        # Publish via Redis (primary) or Kafka (fallback)
        success = False

        if self.redis_client:
            try:
                self.logger.debug(f"Publishing trigger to Redis stream: {self.trigger_topic}")
                self._publish_to_redis(self.trigger_topic, trigger_message)
                self.logger.info(f"Trigger published to Redis for alert: {alert.instant_alert_id}")
                success = True
            except Exception as e:
                self.logger.error(f"Redis publish failed: {e}", exc_info=True)

        if not success and self.kafka_client:
            try:
                self.logger.debug(f"Falling back to Kafka topic: {self.trigger_topic}")
                self._publish_to_kafka(self.trigger_topic, trigger_message)
                self.logger.info(f"Trigger published to Kafka for alert: {alert.instant_alert_id}")
            except Exception as e:
                self.logger.error(f"Kafka publish failed: {e}", exc_info=True)

    def _build_unified_output(
        self,
        alert: AlertConfig,
        detection: Dict[str, Any]
    ) -> Dict[str, Any]:
        """
        Build unified internal output dictionary for other modules.

        Returns dict with keys:
        - detectionType: 'listed' or 'normal'
        - objectClass: e.g., "person" or "ABC123"
        - zoneName: zone name or ""
        - objectCount: integer count
        - confidence: float
        - threshold_count: integer or ""
        """
        detection_type_raw = detection.get("detectionType", "").lower()

        # Determine if this is a 'listed' or 'normal' detection
        if detection_type_raw == "license_plate":
            # Listed detection (specific plate match)
            detection_type = "listed"
            object_class = detection.get("plateNumber", "")
        elif detection_type_raw == "object_count":
            # Normal detection (count-based)
            detection_type = "normal"
            object_class = detection.get("objectClass", "person")
        else:
            # Default to normal for other types
            detection_type = "normal"
            object_class = detection.get("objectClass", "unknown")

        # Extract zone name
        zone_name = detection.get("zoneName", "")

        # Extract counts
        object_count = detection.get("currentCount", detection.get("personCount", 1))
        threshold_count = alert.detection_config.get("thresholdCount", "")

        # Extract confidence
        confidence = detection.get("confidence", 0.0)

        unified_output = {
            "detectionType": detection_type,
            "objectClass": object_class,
            "zoneName": zone_name,
            "objectCount": object_count,
            "confidence": confidence,
            "threshold_count": threshold_count,
            # Additional metadata
            "frameUrl": detection.get("frameUrl", ""),
            "coordinates": detection.get("coordinates", {}),
            "cameraName": detection.get("cameraName", ""),
            "timestamp": detection.get("timestamp", datetime.now(timezone.utc).isoformat())
        }

        return unified_output

    def _build_trigger_message(
        self,
        alert: AlertConfig,
        detection: Dict[str, Any],
        unified_output: Dict[str, Any]
    ) -> Dict[str, Any]:
        """
        Build trigger message in exact format specified in documentation.

        Returns:
            Trigger message dict matching schema for alert_instant_triggered topic
        """
        # Build context_data based on detection type
        detection_type_raw = detection.get("detectionType", "").lower()

        context_data = {
            "detectionType": detection_type_raw,
            "confidence": unified_output["confidence"],
            "frameUrl": unified_output["frameUrl"],
            "coordinates": unified_output["coordinates"],
            "cameraName": unified_output["cameraName"]
        }

        # Add type-specific fields
        if detection_type_raw == "license_plate":
            context_data.update({
                "plateNumber": detection.get("plateNumber", ""),
                "vehicleType": detection.get("vehicleType", ""),
                "vehicleColor": detection.get("vehicleColor", "")
            })

        elif detection_type_raw == "object_count":
            context_data.update({
                "objectClass": unified_output["objectClass"],
                "currentCount": unified_output["objectCount"],
                "thresholdCount": unified_output.get("threshold_count", "")
            })

        elif detection_type_raw == "fire_smoke":
            context_data.update({
                "fireDetected": detection.get("fireDetected", False),
                "smokeDetected": detection.get("smokeDetected", False),
                "severity": alert.severity_level
            })

        elif detection_type_raw == "intrusion":
            context_data.update({
                "objectClass": detection.get("objectClass", "person"),
                "zoneName": unified_output["zoneName"],
                "personCount": detection.get("personCount", 1)
            })

        # Build final trigger message (exact schema from documentation)
        trigger_message = {
            "instant_alert_id": alert.instant_alert_id,
            "camera_id": alert.camera_id,
            "triggered_at": datetime.now(timezone.utc).isoformat(),
            "context_data": context_data
        }

        return trigger_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("instant_alert_id", "")
            )
        except Exception as e:
            self.logger.error(f"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("instant_alert_id", "")
            )
        except Exception as e:
            self.logger.error(f"Kafka publish error: {e}")
            raise

    def get_active_alerts_count(self) -> int:
        """Get count of active alerts."""
        with self._alerts_lock:
            return len(self._alerts)

    def get_alerts_for_camera(self, camera_id: str) -> List[Dict[str, Any]]:
        """Get all active alerts for a camera (for debugging/monitoring)."""
        with self._alerts_lock:
            return [
                {
                    "instant_alert_id": alert.instant_alert_id,
                    "alert_name": alert.alert_name,
                    "severity_level": alert.severity_level,
                    "detection_config": alert.detection_config
                }
                for alert in self._alerts.values()
                if alert.camera_id == camera_id and alert.is_active
            ]


# ============================================================================
# EXAMPLE USAGE AND TESTS
# ============================================================================

class MockStream:
    """Mock stream client for testing."""

    def __init__(self):
        self.messages = []
        self.published = []

    def add_messages(self, messages: List[Dict[str, Any]]):
        """Add messages to mock queue."""
        self.messages.extend(messages)

    def get_message(self, timeout=1.0):
        """Mock get_message."""
        if self.messages:
            msg = self.messages.pop(0)
            return {"value": json.dumps(msg)}
        return None

    def add_message(self, topic_or_channel, message, key=None):
        """Mock add_message."""
        self.published.append({
            "topic": topic_or_channel,
            "message": json.loads(message) if isinstance(message, str) else message,
            "key": key
        })


def test_lpr_listed_trigger():
    """Test LPR alert with listed plate detection."""
    print("\n" + "="*80)
    print("TEST 1: LPR Listed Plate Detection")
    print("="*80)

    # Setup mock clients
    mock_redis = MockStream()

    # Create alert config
    config_message = {
        "instant_alert_id": "673d8c177481e811e530ad50",
        "camera_id": "673d8c177481e811e530ad51",
        "app_deployment_id": "673d8c177481e811e530ad52",
        "application_id": "673d8c177481e811e530ad53",
        "alert_name": "Unauthorized Plate Detection",
        "detection_config": {
            "targetPlates": ["ABC123", "XYZ789"],
            "minConfidence": 0.85
        },
        "severity_level": "high",
        "is_active": True,
        "action": "create",
        "timestamp": "2025-11-03T10:30:00Z"
    }

    mock_redis.add_messages([config_message])

    # Initialize alert manager
    logger = logging.getLogger("test_lpr")
    logger.setLevel(logging.INFO)
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
    logger.addHandler(handler)

    alert_manager = ALERT_INSTANCE(
        redis_client=mock_redis,
        config_topic="alert_instant_config_request",
        trigger_topic="alert_instant_triggered",
        polling_interval=100,
        logger=logger
    )

    # Manually fetch configs (simulating one poll cycle)
    alert_manager._fetch_and_update_configs()

    print(f"\nActive alerts: {alert_manager.get_active_alerts_count()}")

    # Create detection event
    detection_event = {
        "camera_id": "673d8c177481e811e530ad51",
        "detectionType": "license_plate",
        "plateNumber": "ABC123",
        "confidence": 0.92,
        "frameUrl": "s3://bucket/frames/2025-11-03/frame_123456.jpg",
        "coordinates": {"x": 120, "y": 450, "width": 200, "height": 80},
        "cameraName": "Entry Gate Camera",
        "vehicleType": "car",
        "vehicleColor": "blue"
    }

    # Process detection
    alert_manager.process_detection_event(detection_event)

    # Check published triggers
    print(f"\nPublished triggers: {len(mock_redis.published)}")

    if mock_redis.published:
        trigger = mock_redis.published[0]
        print("\n--- Unified Internal Output ---")
        # Reconstruct unified output for display
        unified = {
            "detectionType": "listed",
            "objectClass": "ABC123",
            "zoneName": "",
            "objectCount": 1,
            "confidence": 0.92,
            "threshold_count": ""
        }
        print(json.dumps(unified, indent=2))

        print("\n--- Trigger Message Published to alert_instant_triggered ---")
        print(json.dumps(trigger["message"], indent=2))

    alert_manager.stop()
    print("\n" + "="*80)


def test_object_count_normal_trigger():
    """Test object count alert (normal detection)."""
    print("\n" + "="*80)
    print("TEST 2: Object Count Alert (People Counting)")
    print("="*80)

    # Setup mock clients
    mock_redis = MockStream()

    # Create alert config
    config_message = {
        "instant_alert_id": "673d8c177481e811e530ad60",
        "camera_id": "673d8c177481e811e530ad61",
        "app_deployment_id": "673d8c177481e811e530ad62",
        "application_id": "673d8c177481e811e530ad63",
        "alert_name": "Crowd Detection Alert",
        "detection_config": {
            "thresholdCount": 10,
            "minConfidence": 0.75
        },
        "severity_level": "medium",
        "is_active": True,
        "action": "create",
        "timestamp": "2025-11-03T11:00:00Z"
    }

    mock_redis.add_messages([config_message])

    # Initialize alert manager
    logger = logging.getLogger("test_count")
    logger.setLevel(logging.INFO)
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
    logger.addHandler(handler)

    alert_manager = ALERT_INSTANCE(
        redis_client=mock_redis,
        config_topic="alert_instant_config_request",
        trigger_topic="alert_instant_triggered",
        polling_interval=100,
        logger=logger
    )

    # Manually fetch configs
    alert_manager._fetch_and_update_configs()

    print(f"\nActive alerts: {alert_manager.get_active_alerts_count()}")

    # Create detection event
    detection_event = {
        "camera_id": "673d8c177481e811e530ad61",
        "detectionType": "object_count",
        "objectClass": "person",
        "currentCount": 15,
        "thresholdCount": 10,
        "confidence": 0.78,
        "frameUrl": "s3://bucket/frames/2025-11-03/frame_901234.jpg",
        "cameraName": "Main Hall Camera"
    }

    # Process detection
    alert_manager.process_detection_event(detection_event)

    # Check published triggers
    print(f"\nPublished triggers: {len(mock_redis.published)}")

    if mock_redis.published:
        trigger = mock_redis.published[0]
        print("\n--- Unified Internal Output ---")
        unified = {
            "detectionType": "normal",
            "objectClass": "person",
            "zoneName": "",
            "objectCount": 15,
            "confidence": 0.78,
            "threshold_count": 10
        }
        print(json.dumps(unified, indent=2))

        print("\n--- Trigger Message Published to alert_instant_triggered ---")
        print(json.dumps(trigger["message"], indent=2))

    alert_manager.stop()
    print("\n" + "="*80)


if __name__ == "__main__":
    print("\n" + "="*80)
    print("ALERT_INSTANCE UTILITY - PRODUCTION TEST HARNESS")
    print("THIS IS PRODUCTION. HIGH-QUALITY ACCURACY REQUIRED.")
    print("="*80)

    # Run tests
    test_lpr_listed_trigger()
    test_object_count_normal_trigger()

    print("\n✓ All tests completed successfully")
    print("\nIntegration Instructions:")
    print("1. Import ALERT_INSTANCE from this module")
    print("2. Initialize with real MatriceStream clients (Redis primary, Kafka fallback)")
    print("3. Call start() to begin polling for configs")
    print("4. Call process_detection_event() from your inference pipeline")
    print("5. Call stop() for graceful shutdown")
