"""CHANNELPROC-01: spawn entry; SDK objects and Redis clients remain in this process."""

import ctypes
import json
import os
import queue
import signal
import time

STATUS_LIMIT = 64 * 1024


def parent_death_guard(parent_pid):
    libc = ctypes.CDLL(None, use_errno=True)
    if libc.prctl(1, signal.SIGKILL, 0, 0, 0) != 0:
        raise OSError(ctypes.get_errno(), 'Parent death guard failed.')
    if os.getppid() != parent_pid:
        raise RuntimeError('Parent disappeared during spawn.')


def send_latest(output, message):
    if len(json.dumps(message, allow_nan=False).encode()) > STATUS_LIMIT:
        raise ValueError('Child status exceeds IPC bound.')
    try:
        output.put_nowait(message)
    except queue.Full:
        try:
            output.get_nowait()
        except queue.Empty:
            return False
        try:
            output.put_nowait(message)
        except queue.Full:
            return False
    return True


def run_channel(spec, run_id, generation, settings, writer_token, commands, output, stop, terminal_ack, parent_pid):
    parent_death_guard(parent_pid)
    os.environ['CONTEXTRA_STREAM_EXECUTION_MODE'] = 'thread'
    output.cancel_join_thread()
    from app.stream_manager import StreamGroup
    from app.process_metadata import ChildPublisher
    publisher = None
    group = None
    requested = False
    last = None
    failed = False
    try:
        if settings is not None:
            publisher = ChildPublisher(settings, spec['source_id'], run_id, generation, writer_token)
            publisher.start()
        group = StreamGroup(spec, metadata_publisher=publisher)
        group.start(assigned_run_id=run_id, assigned_generation=generation)
        while True:
            if os.getppid() != parent_pid:
                os._exit(71)
            if stop.is_set() and not requested:
                group.stop()
                requested = True
            try:
                command = commands.get_nowait()
            except queue.Empty:
                command = None
            if command is not None:
                reply = dict(id=command.get('id'), run_id=run_id, run_generation=generation)
                try:
                    if command.get('op') != 'metrics' or group.result_path is None:
                        raise RuntimeError('Channel diagnostics unavailable.')
                    metrics = group.result_path.ingress.metrics
                    metrics.configure(command['enabled'], command['seconds'])
                    reply['value'] = metrics.snapshot()
                except Exception:
                    reply['error'] = 'DIAGNOSTICS_UNAVAILABLE'
                send_latest(output, dict(kind='reply', **reply))
            state = group.status(detail=False)
            if state.get('runtime_observation'):
                state.pop('runtime_observation')
                state['runtime_observation_supported'] = False
            performance = state.get('metadata_path', {}).get('performance')
            if performance is not None:
                performance['tracking_profiles'] = []
            state['frames_in_window'] = state.get('metadata_path', {}).get('collector_frames', 0)
            state['detail_capabilities'] = dict(full_collector=False, bounded_metrics=True,
                                               tracking_profiles=False)
            state['publisher_metrics'] = publisher.metrics() if publisher else {}
            state['child_pid'] = os.getpid()
            state['child_sampled_ns'] = time.monotonic_ns()
            last = state
            if state.get('last_error') and not requested:
                failed = True
                if not group._cleanup_started:
                    group.stop()
                requested = True
            done = requested and group._cleanup_done.is_set()
            send_latest(output, dict(kind='status', run_id=run_id, run_generation=generation, value=state))
            if done:
                failed = failed or bool(state.get('cleanup_error'))
                break
            time.sleep(.25)
        if publisher is not None and not publisher.close(2):
            failed = True
    except Exception as error:
        failed = True
        print('[CHANNEL-CHILD] ' + type(error).__name__, flush=True)
    finally:
        message = dict(kind='terminal', run_id=run_id, run_generation=generation,
                       value=last, failed=failed)
        deadline = time.monotonic() + 2
        while time.monotonic() < deadline and not terminal_ack.is_set():
            send_latest(output, message)
            terminal_ack.wait(.1)
        commands.close()
        output.close()
