"""CROWD-02: monitoring and explicitly enabled ROI Event settings."""
from dataclasses import asdict, dataclass
import math

from app.intrusion import IntrusionConfig

CROWD_MODEL = 'Awiros_person_and_head_detection'
CROWD_NAMES = ('person', 'head')  # Canonical IDs, NOT unverified raw tensor slots.
CROWD_COLORS = ('#00CC66', '#00B8D9')
FULL_FRAME = ((0., 0.), (1., 0.), (1., 1.), (0., 1.))
EVENT_PARAMETERS = ('reference_count', 'safe_ratio', 'caution_ratio', 'warning_ratio', 'danger_ratio')
FRAME_OBJECT_LIMIT = 300  # Existing FrameResult / native ingress contract.
SAMPLE_LIMIT = 4096  # Scalar history bound, not another detection/object cap.


def duration_ns(value):
    if (type(value) not in (int, float) or not math.isfinite(value)
            or not 0 < value <= 3600 or value * 1_000_000_000 < 1):
        raise ValueError('Invalid CROWD duration.')
    return round(value * 1_000_000_000)


def ratio(value):
    if type(value) not in (int, float) or not math.isfinite(value) or not 0 <= value <= 1:
        raise ValueError('Invalid CROWD confidence or IoU.')
    return value


@dataclass(frozen=True)
class CrowdConfig:
    enabled: bool = False
    person_confidence: float = .45
    head_confidence: float = .45
    roi: tuple = FULL_FRAME
    roi_name: str = 'Crowd ROI'
    roi_id: str = 'primary'
    debug_roi: bool = True
    window_seconds: float = 3.
    report_seconds: float = 3.
    minimum_samples: int = 2
    stale_seconds: float = 6.
    nms_enabled: bool = True
    nms_iou_threshold: float = .85
    event_enabled: bool = False
    reference_count: int = 50
    safe_ratio: float = .25
    caution_ratio: float = .5
    warning_ratio: float = .75
    danger_ratio: float = .9
    suppress_repeated_danger: bool = True

    def __post_init__(self):
        for key in ('enabled', 'debug_roi', 'nms_enabled', 'event_enabled', 'suppress_repeated_danger'):
            if type(getattr(self, key)) is not bool:
                raise ValueError('Invalid CROWD toggle.')
        for key in ('person_confidence', 'head_confidence', 'nms_iou_threshold'):
            ratio(getattr(self, key))
        validate_event_parameters({key: getattr(self, key) for key in EVENT_PARAMETERS})
        for key in ('window_seconds', 'report_seconds', 'stale_seconds'):
            duration_ns(getattr(self, key))
        if self.stale_seconds < self.report_seconds:
            raise ValueError('CROWD stale duration must cover the report interval.')
        if type(self.minimum_samples) is not int or not 2 <= self.minimum_samples <= SAMPLE_LIMIT:
            raise ValueError('Invalid CROWD minimum samples.')
        # Reuse the canonical polygon validator, including boundary/degeneracy rules.
        if not isinstance(self.roi, (tuple, list)):
            raise ValueError('Invalid CROWD ROI container.')
        roi = FULL_FRAME if not self.roi else self.roi
        geometry = IntrusionConfig(roi=roi, roi_name=self.roi_name, roi_id=self.roi_id,
                                   debug_roi=self.debug_roi)
        object.__setattr__(self, 'roi', geometry.roi)

    @classmethod
    def parse(cls, value):
        if type(value) is not dict or set(value) - set(cls.__dataclass_fields__):
            raise ValueError('Invalid CROWD settings.')
        return cls(**value)

    def settings_payload(self):
        """Private AI settings only. Do not put this dictionary on the Frame wire."""
        value = asdict(self)
        value['roi'] = [list(p) for p in self.roi]
        return value

    def patched(self, changes):
        if type(changes) is not dict:
            raise ValueError('Invalid CROWD settings patch.')
        return self.parse(dict(self.settings_payload(), **changes))

    def confidence_for(self, class_id):
        if type(class_id) is not int or class_id not in (0, 1):
            raise ValueError('Invalid canonical CROWD class.')
        return self.person_confidence if class_id == 0 else self.head_confidence


def validate_event_parameters(raw):
    if type(raw) is not dict or any(key not in raw for key in EVENT_PARAMETERS):
        raise ValueError('MISSING_CROWD_EVENT_PARAMETERS')
    count = raw['reference_count']
    if type(count) is not int or count <= 0:
        raise ValueError('INVALID_CROWD_REFERENCE_COUNT')
    for key in EVENT_PARAMETERS[1:]:
        try:
            ratio(raw[key])
        except (ValueError, OverflowError):
            raise ValueError('INVALID_CROWD_RATIO') from None
    if not raw['caution_ratio'] < raw['warning_ratio'] < raw['danger_ratio']:
        raise ValueError('INVALID_CROWD_RATIO_ORDER')
    return {key: raw[key] for key in EVENT_PARAMETERS}


@dataclass(frozen=True)
class CrowdDisplayConfig:
    """Existing ROI display keys only; aggregation settings stay AI-private.

    `confidence` is the lower display floor. Class-specific filtering has already
    happened. There are no synthetic dwell/rearm fields pretending to be timers.
    """
    config: CrowdConfig

    @property
    def scenario_name(self):
        return 'CROWD'

    @property
    def roi(self):
        return self.config.roi

    @property
    def roi_name(self):
        return self.config.roi_name

    @property
    def debug_roi(self):
        return self.config.debug_roi

    @property
    def confidence(self):
        return min(self.config.person_confidence, self.config.head_confidence)

    def payload(self):
        return dict(enabled=self.config.enabled, roi_id=self.config.roi_id,
                    roi_name=self.roi_name, roi=[list(p) for p in self.roi],
                    debug_roi=self.debug_roi, confidence=self.confidence)
