"""Bounded provider snapshots; canonical Frame identities are never rewritten."""
from copy import deepcopy
import json
import time
import queue

from app.provider_plan import ProviderPlan, plan


def provider_spec(spec, provider, required):
    from app.stream_settings import revision
    result = deepcopy(spec)
    for name in ('intrusion', 'loitering', 'fall', 'fight'):
        if name.upper() not in provider.scenarios:
            result[name] = dict(result.get(name) or {}, enabled=False)
    if provider.key != 'pose':
        result['pose'] = dict(result.get('pose') or {}, enabled=False, show_joints=False, show_edges=False)
    if provider.key != 'fire':
        fire = dict(result.get('fire') or {})
        fire.update(enabled=False, scenario=dict(fire.get('scenario') or {}, enabled=False))
        result['fire'] = fire
    if provider.key != 'crowd':
        result['crowd'] = dict(result.get('crowd') or {}, enabled=False)
    if not provider.person_owner:
        result['tracking'] = dict(result.get('tracking') or {}, enabled=False)
    result['_provider_runtime'] = dict(provider=provider, required=tuple(required), revision=revision(spec))
    return result


def runtime_plan(spec):
    context = spec.get('_provider_runtime')
    if context is None:
        return plan(spec)
    provider = context['provider']
    return ProviderPlan((provider,), provider.key if provider.person_owner else None, provider.scenarios)


class ProviderPublisher:
    def __init__(self, target, provider, required):
        self.target, self.provider, self.required = target, provider, list(required)

    def submit(self, snapshot):
        return self.target.submit(dict(snapshot, providerKey=self.provider.key,
            providerModel=self.provider.model, requiredProviders=self.required))

    def __getattr__(self, name):
        return getattr(self.target, name)


class SnapshotRelay:
    """One bounded IPC slot per provider; channel owner is the only Redis writer."""
    def __init__(self, output, identity, max_bytes):
        self.output, self.identity, self.max_bytes = output, identity, max_bytes
        self.counters = dict(relayed=0, replaced=0, dropped=0)
        output.cancel_join_thread()

    def start(self):
        pass

    def channel(self, camera_id, run_id, generation, state):
        return (camera_id, run_id, generation) == self.identity

    def wait_channel(self, camera_id, run_id, generation, timeout):
        return (camera_id, run_id, generation) == self.identity

    def submit(self, snapshot):
        if (str(snapshot['cameraId']), snapshot['runId'], snapshot['runGeneration']) != self.identity:
            return False
        from app.pipeline_diagnostics import diagnostics
        if diagnostics.collecting():
            snapshot = dict(snapshot, _ipc_ready_ns=time.monotonic_ns())
        payload = json.dumps(snapshot, ensure_ascii=False, allow_nan=False, separators=(',', ':'))
        if len(payload.encode()) > self.max_bytes:
            self.counters['dropped'] += 1
            return False
        try:
            self.output.put_nowait(payload)
        except queue.Full:
            try:
                self.output.get_nowait()
                self.counters['replaced'] += 1
                self.output.put_nowait(payload)
            except (queue.Empty, queue.Full):
                self.counters['dropped'] += 1
                return False
        self.counters['relayed'] += 1
        return True

    def metrics(self):
        return dict(self.counters)

    def close(self, timeout):
        return True
