"""
Liquid Leak Detection Use Case

Implements standardized agg_summary structure for industrial liquid leak detection.
Includes spatial merging, temporal validation, analytics, and alert generation.
"""

from typing import Any, Dict, List, Optional
import time

from ..core.base import (
    BaseProcessor,
    ProcessingContext,
    ProcessingResult,
    ConfigProtocol,
)
from ..core.config import BaseConfig
from ..utils import (
    filter_by_confidence,
    filter_by_categories,
    apply_category_mapping,
)


# ============================================================
# Configuration
# ============================================================

class LiquidLeakDetectionConfig(BaseConfig):
    """Configuration for Liquid Leak Detection Use Case."""

    def __init__(
        self,
        usecase: str = "liquid_leak_detection",
        category: str = "industrial",
        confidence_threshold: float = 0.25,
        target_categories: Optional[List[str]] = None,
        enable_analytics: bool = True,
        enable_spatial_merge: bool = True,
        iou_merge_threshold: float = 0.5,
        containment_threshold: float = 0.6,
        activation_frames: int = 3,
        deactivation_frames: int = 40,
        alert_cooldown_seconds: int = 30,
        index_to_category: Optional[Dict[int, str]] = None,
        **kwargs
    ):
        super().__init__(usecase=usecase, category=category, **kwargs)

        self.confidence_threshold = confidence_threshold
        self.target_categories = target_categories or ["liquid_leak"]
        self.enable_analytics = enable_analytics

        self.enable_spatial_merge = enable_spatial_merge
        self.iou_merge_threshold = iou_merge_threshold
        self.containment_threshold = containment_threshold

        self.activation_frames = activation_frames
        self.deactivation_frames = deactivation_frames
        self.alert_cooldown_seconds = alert_cooldown_seconds
        self.index_to_category = index_to_category


    def validate(self) -> List[str]:
        errors = super().validate()

        if not 0.0 <= self.confidence_threshold <= 1.0:
            errors.append("confidence_threshold must be between 0.0 and 1.0")

        if self.activation_frames < 1:
            errors.append("activation_frames must be >= 1")

        if self.deactivation_frames < 1:
            errors.append("deactivation_frames must be >= 1")

        return errors


# ============================================================
# Use Case
# ============================================================

class LiquidLeakDetectionUseCase(BaseProcessor):
    """Industrial liquid leak detection with temporal validation."""

    def __init__(self):
        super().__init__("liquid_leak_detection")
        self.category = "security"

        # Temporal state
        self._active_counter = 0
        self._inactive_counter = 0
        self._alert_active = False
        # self._last_alert_timestamp = None
        self._total_alerts_triggered = 0

        # Analytics state
        self._total_frames = 0
        self._total_detections = 0
        self._active_frames = 0

        # Clean incident lifecycle state (ADDED)
        self._current_incident_id = None
        # self._incident_start_time = None
        # self._incident_severity = None
        # self._incident_just_activated = False
        # self._incident_just_cleared = False

        # Persistent alert state
        self._alert_id = None
        self._alert_start_frame = None

    # ============================================================
    # Config Schema
    # ============================================================

    def get_config_schema(self) -> Dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "confidence_threshold": {"type": "number", "minimum": 0.0, "maximum": 1.0},
                "enable_spatial_merge": {"type": "boolean"},
                "iou_merge_threshold": {"type": "number"},
                "containment_threshold": {"type": "number"},
                "activation_frames": {"type": "integer", "minimum": 1},
                "deactivation_frames": {"type": "integer", "minimum": 1},
                "alert_cooldown_seconds": {"type": "integer", "minimum": 0},
                "enable_analytics": {"type": "boolean"},
            },
            "required": ["confidence_threshold"],
            "additionalProperties": False,
        }
    
    def reset(self):
        self._active_counter = 0
        self._inactive_counter = 0
        self._alert_active = False
        # self._last_alert_timestamp = None
        self._total_alerts_triggered = 0
        self._total_frames = 0
        self._total_detections = 0
        self._active_frames = 0

        self._current_incident_id = None
        # self._incident_start_time = None
        # self._incident_severity = None
        # self._incident_just_activated = False
        # self._incident_just_cleared = False

        self._alert_id = None
        self._alert_start_frame = None


    def create_default_config(self, **overrides) -> LiquidLeakDetectionConfig:
        defaults = {
            "category": self.category,
            "usecase": self.name,
            "confidence_threshold": 0.25,
            "enable_spatial_merge": True,
            "iou_merge_threshold": 0.5,
            "containment_threshold": 0.6,
            "activation_frames": 3,
            "deactivation_frames": 40,
            "alert_cooldown_seconds": 30,
            "enable_analytics": True,
        }
        defaults.update(overrides)
        return LiquidLeakDetectionConfig(**defaults)

    # ============================================================
    # Main Processing
    # ============================================================

    def process(
        self,
        data: Any,
        config: ConfigProtocol,
        context: Optional[ProcessingContext] = None,
        stream_info: Optional[Any] = None,
    ) -> ProcessingResult:

        try:
            errors = config.validate()
            if errors:
                return self.create_error_result(
                    f"Configuration validation failed: {errors}",
                    usecase=self.name,
                    category=self.category,
                    context=context,
                )

            if not isinstance(config, LiquidLeakDetectionConfig):
                return self.create_error_result(
                    "Invalid configuration type for liquid leak detection",
                    usecase=self.name,
                    category=self.category,
                    context=context,
                )

            if context is None:
                context = ProcessingContext()

            is_multi_frame = self.detect_frame_structure(data)

            if is_multi_frame:
                agg_summary = self._process_multi_frame(data, config, stream_info)
            else:
                agg_summary = self._process_single_frame(data, config, stream_info)

            context.mark_completed()

            return self.create_result(
                data={"agg_summary": agg_summary},
                usecase=self.name,
                category=self.category,
                context=context,
            )

        except Exception as e:
            if context:
                context.mark_completed()

            return self.create_error_result(
                str(e),
                type(e).__name__,
                usecase=self.name,
                category=self.category,
                context=context,
            )

    # ============================================================
    # Frame Processing
    # ============================================================

    def _process_multi_frame(self, data, config, stream_info):

        agg_summary = {}

        for frame_key, frame_data in data.items():
            frame_id = str(frame_key)

            incidents, tracking_stats, business_analytics, alerts, summary = \
                self._process_frame_detections(frame_data, config, frame_id, stream_info)

            agg_summary[frame_id] = {
                "incidents": incidents or [],
                "tracking_stats": tracking_stats or [],
                "business_analytics": business_analytics or [],
                "alerts": alerts or [],
                "human_text": summary or "",
            }

        return agg_summary


    def _process_single_frame(self, data, config, stream_info):
        incidents, tracking_stats, business_analytics, alerts, summary = \
            self._process_frame_detections(data, config, "current_frame", stream_info)

        return {
            "current_frame": {
                "incidents": incidents or [],
                "tracking_stats": tracking_stats or [],
                "business_analytics": business_analytics or [],
                "alerts": alerts or [],
                "human_text": summary or "",
            }
        }

    # ============================================================
    # Core Frame Logic
    # ============================================================

    def _process_frame_detections(self, frame_data, config, frame_id, stream_info):

        if isinstance(frame_data, list):
            detections = frame_data
        elif isinstance(frame_data, dict) and "predictions" in frame_data:
            detections = frame_data["predictions"]
        else:
            detections = []

        # print("DETECTION:",detections)

        # Confidence filtering
        detections = filter_by_confidence(detections, config.confidence_threshold)

        # Category mapping if exists
        if config.index_to_category:
            detections = apply_category_mapping(detections, config.index_to_category)

        # Filter target categories (mask ignored)
        detections = filter_by_categories(detections, config.target_categories)

        # Spatial merge
        if config.enable_spatial_merge:
            detections = self._merge_detections(detections, config)

        current_count = len(detections)

        # Update state
        self._total_frames += 1
        self._total_detections += current_count

        state_changed = self._update_temporal_state(current_count, config)

        if current_count > 0:
            self._active_frames += 1

        # incidents = self._generate_incidents(detections, config, frame_id, stream_info)
        incidents = []  # Incident generation is now handled purely by alert state transitions

        tracking_stats = self._generate_tracking_stats(detections, stream_info)
        business_analytics = (
            self._generate_business_analytics(detections, stream_info)
            if config.enable_analytics else []
        )
        alerts = self._generate_alerts(config, stream_info, state_changed)
        summary = self._generate_summary(current_count)
        
        # print("DEBUG:", current_count, self._alert_active)
        return incidents, tracking_stats, business_analytics, alerts, summary

    # ============================================================
    # Spatial Merge (IoU + Containment)
    # ============================================================

    def _merge_detections(self, detections, config):

        boxes = [d.get("bounding_box") for d in detections]
        confs = [d.get("confidence") for d in detections]

        merged = []
        used = set()

        for i in range(len(boxes)):
            if i in used:
                continue

            base = boxes[i]
            cluster = [base]
            cluster_confs = [confs[i]]
            used.add(i)

            for j in range(i + 1, len(boxes)):
                if j in used:
                    continue

                if self._should_merge(base, boxes[j], config):
                    cluster.append(boxes[j])
                    cluster_confs.append(confs[j])
                    used.add(j)

            merged_box = self._merge_cluster_boxes(cluster)
            merged_conf = max(cluster_confs)

            merged.append({
                "category": "liquid_leak",
                "confidence": merged_conf,
                "bounding_box": merged_box,
            })

        return merged

    def _should_merge(self, box1, box2, config):
        iou = self._compute_iou(box1, box2)
        containment = self._compute_containment(box1, box2)
        return (
            iou >= config.iou_merge_threshold
            or containment >= config.containment_threshold
        )

    def _compute_iou(self, b1, b2):
        x1 = max(b1["x_min"], b2["x_min"])
        y1 = max(b1["y_min"], b2["y_min"])
        x2 = min(b1["x_max"], b2["x_max"])
        y2 = min(b1["y_max"], b2["y_max"])

        inter = max(0, x2 - x1) * max(0, y2 - y1)
        area1 = (b1["x_max"] - b1["x_min"]) * (b1["y_max"] - b1["y_min"])
        area2 = (b2["x_max"] - b2["x_min"]) * (b2["y_max"] - b2["y_min"])

        union = area1 + area2 - inter
        return inter / union if union > 0 else 0

    def _compute_containment(self, b1, b2):
        x1 = max(b1["x_min"], b2["x_min"])
        y1 = max(b1["y_min"], b2["y_min"])
        x2 = min(b1["x_max"], b2["x_max"])
        y2 = min(b1["y_max"], b2["y_max"])

        inter = max(0, x2 - x1) * max(0, y2 - y1)
        area1 = (b1["x_max"] - b1["x_min"]) * (b1["y_max"] - b1["y_min"])
        area2 = (b2["x_max"] - b2["x_min"]) * (b2["y_max"] - b2["y_min"])

        min_area = min(area1, area2)
        return inter / min_area if min_area > 0 else 0

    def _merge_cluster_boxes(self, cluster):
        xmins = [b["x_min"] for b in cluster]
        ymins = [b["y_min"] for b in cluster]
        xmaxs = [b["x_max"] for b in cluster]
        ymaxs = [b["y_max"] for b in cluster]

        return {
            "x_min": min(xmins),
            "y_min": min(ymins),
            "x_max": max(xmaxs),
            "y_max": max(ymaxs),
        }

    # ============================================================
    # Temporal Logic
    # ============================================================

    def _update_temporal_state(self, current_count, config):

        state_changed = False

        if current_count > 0:
            self._active_counter += 1
            self._inactive_counter = 0

            # Activation condition
            if (
                not self._alert_active
                and self._active_counter >= config.activation_frames
            ):
                self._alert_active = True
                self._total_alerts_triggered += 1
                state_changed = True

                # Persistent alert initialization
                self._alert_id = f"liquid_leak_{self._total_frames}"
                self._alert_start_frame = self._total_frames

        else:
            self._inactive_counter += 1
            self._active_counter = 0

            # Deactivation condition
            if (
                self._alert_active
                and self._inactive_counter >= config.deactivation_frames
            ):
                self._alert_active = False
                state_changed = True

                # Clear persistent alert
                self._alert_id = None
                self._alert_start_frame = None

        return state_changed



    # ============================================================
    # Standardized Generators
    # ============================================================

    # def _generate_incidents(self, detections, config, frame_id, stream_info):
    #     incidents = []

    #     # Only create incident on activation
    #     if not self._incident_just_activated:
    #         return incidents

    #     camera_info = self.get_camera_info_from_stream(stream_info)

    #     incident = self.create_incident(
    #         self._current_incident_id,
    #         "liquid_leak_detected",
    #         self._incident_severity,
    #         "Liquid leak confirmed and activated",
    #         camera_info,
    #         start_time=self._incident_start_time
    #     )

    #     incidents.append(incident)
    #     return incidents


    def _generate_tracking_stats(self, detections, stream_info):
        camera_info = self.get_camera_info_from_stream(stream_info)

        total_counts = [
            self.create_count_object("liquid_leak", self._total_detections)
        ]
        current_counts = [
            self.create_count_object("liquid_leak", len(detections))
        ]

        tracking_detections = [
            self.create_detection_object("liquid_leak", d["bounding_box"])
            for d in detections
        ]

        human_text = f"Current leaks: {len(detections)}, Total detections: {self._total_detections}"

        stat = self.create_tracking_stats(
            total_counts,
            current_counts,
            tracking_detections,
            human_text,
            camera_info,
        )

        return [stat]

    def _generate_business_analytics(self, detections, stream_info):
        camera_info = self.get_camera_info_from_stream(stream_info)

        analytics_stats = {
            "total_frames": self._total_frames,
            "total_alerts_triggered": self._total_alerts_triggered,
            "active_frames": self._active_frames,
            "leak_presence_ratio": self._active_frames / max(1, self._total_frames),
            "current_detections": len(detections),
        }

        text = f"Leak presence ratio: {analytics_stats['leak_presence_ratio']:.2f}"

        analytics = self.create_business_analytics(
            "liquid_leak_analytics",
            analytics_stats,
            text,
            camera_info,
        )

        return [analytics]


    def _generate_alerts(self, config, stream_info, state_changed):

        if not self._alert_active:
            return []

        camera_info = self.get_camera_info_from_stream(stream_info)

        duration_frames = self._total_frames - self._alert_start_frame

        alert = {
            "alert_id": self._alert_id,
            "alert_type": "liquid_leak_detected",
            "status": "active",
            "start_frame": self._alert_start_frame,
            "current_frame": self._total_frames,
            "duration_frames": duration_frames,
            "camera_info": camera_info,
        }

        return [alert]



    def _generate_summary(self, current_count):
        if current_count == 0:
            if self._alert_active:
                return "Leak stabilizing - monitoring"
            return "No leak detected"

        if not self._alert_active:
            return f"Leak detected ({self._active_counter} validation frames)"

        return f"Leak confirmed and alert active ({current_count} detections)"