"""MROI-01: provider dependency projection, not rule-state projection."""
from copy import deepcopy
from dataclasses import dataclass
import json
from uuid import NAMESPACE_URL, uuid5

from app.multi_roi.adapter import normalize
from app.multi_roi.errors import RoiError


@dataclass(frozen=True)
class StartResult:
    group: object
    summary: dict

    @property
    def observed_state(self):
        return self.group.observed_state


def project(spec, snapshot):
    spec = deepcopy(spec)
    source = json.loads(snapshot.source_json)
    spec.update(uri=source['rtspUrl'], camera_id=snapshot.camera_id, scenarios='off')
    old_anchor = spec.get('frame_time_anchor') or {}
    spec['frame_time_anchor'] = dict(startWallTimeMs=source['startWallTimeMs'],
        synthetic=bool(old_anchor.get('synthetic', False))
        if old_anchor.get('startWallTimeMs') == source['startWallTimeMs'] else False)
    spec['tracking'] = dict(spec.get('tracking') or {}, enabled=True)
    for key in ('intrusion', 'loitering', 'fall', 'fight', 'crowd', 'fire', 'pose'):
        spec[key] = {'enabled': False}
    spec.pop('event_cooldowns', None)
    spec.pop('crowd_start_rejection', None)
    spec.pop('secure_control_projection', None)
    for code in ('INTRUSION', 'LOITERING', 'FALL', 'FIGHT', 'FIRE', 'CROWD'):
        rules = [r for r in snapshot.rules if r.enabled and r.scenario == code]
        if not rules:
            continue
        cfg = rules[0].settings
        # The representative config selects providers/capture only. Rules keep their own
        # settings. A shared upstream floor must not discard a weaker rule's candidates.
        target = cfg.get('scenario', cfg) if code == 'FIRE' else cfg
        sources = [r.settings.get('scenario', r.settings) if code == 'FIRE' else r.settings for r in rules]
        for name in ('confidence', 'person_score', 'joint_score', 'person_confidence',
                     'head_confidence', 'flame_confidence', 'smoke_confidence'):
            if name in target:
                target[name] = min(s[name] for s in sources)
        spec[code.lower()] = cfg
    for incoming, key in (('recServerId', 'rec_server_id'), ('recCameraId', 'rec_camera_id')):
        spec.pop(key, None)
        if incoming in source:
            spec[key] = source[incoming]
    # Multi ROI's display policy is rule union; legacy UI category filters are not a rule.
    spec['visualization'] = dict(spec.get('visualization') or {}, categories=None,
                                 debug_all_candidates=False)
    spec['multi_roi'] = snapshot.payload()
    return spec


def start(service, value):
    from app.secure_control import ControlError
    manager = service.manager
    with manager._commands:
        if service.roi_policy.value['mode'] != 'multi_roi':
            raise RoiError('CONTRACT_MODE_MISMATCH')
        snapshot = normalize(value, service.roi_policy.value)
        try:
            sid = manager.camera_stream_id(str(snapshot.camera_id))
            group = manager.get(sid)
        except KeyError:
            group = None
            sid = 'camera-'+uuid5(NAMESPACE_URL, str(snapshot.camera_id)).hex
        if group is None:
            if service.template is None:
                raise ControlError('CAMERA_NOT_PROVISIONED')
            spec = deepcopy(service.template)
            spec.update(stream_id=sid, source_id=str(snapshot.camera_id), server_id=service.server_id,
                        camera_id=snapshot.camera_id, source_type='rtsp', autostart=False)
        else:
            if group.spec.get('server_id') != service.server_id:
                raise ControlError('SERVER_ID_CONFLICT')
            if group.observed_state == 'stopping' and group._pending_restart is None:
                raise ControlError('STOP_CLEANUP_PENDING')
            spec = deepcopy(manager._saved_spec(group))
            if spec.get('source_type', 'rtsp') != 'rtsp' or spec.get('camera_id') != snapshot.camera_id:
                raise ControlError('SOURCE_TYPE_CONFLICT')
        candidate = project(spec, snapshot)
        previous = spec.get('multi_roi') or {}
        change = dict(addedRules=sorted({r.rule_id for r in snapshot.rules} -
                                       {r['ruleId'] for r in previous.get('rules', [])}),
                      removedRules=sorted({r['ruleId'] for r in previous.get('rules', [])} -
                                         {r.rule_id for r in snapshot.rules}))
        pending = group is not None and getattr(group, '_pending_restart', None) == candidate
        reused = group is not None and group.spec == candidate and group.observed_state in ('running', 'starting')
        applied = manager.apply_and_start(candidate)
        return StartResult(applied, dict(snapshot.summary(),
                           action='PENDING_REUSED' if pending else 'RUN_REUSED' if reused else 'NEW_RUN_REQUESTED', **change))
