"""CPU SDK metadata fixture: production ingress/copy/retriever, not GPU inference."""
import ctypes
import gc
import glob
import sys
import time
import json
import faulthandler
faulthandler.enable()
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst
from pyservicemaker import Buffer
from app.frame_ingress import FrameIngress
from app.byte_tracking import ByteTracker
from app.det_result_path import DetResultPath
from app.multi_pipeline import _DecodedFrameObserver, _make_retriever, ResultCollector
from app.stream_lifecycle import RoleRunHandle
from app.metadata_publisher import MetadataPublisher, MetadataSettings

assert not glob.glob('/dev/nvidia*')
Gst.init(None)
fixture = ctypes.CDLL(sys.argv[1])
fixture.attach_fixture_at.argtypes = [ctypes.c_size_t, ctypes.c_bool, ctypes.c_bool, ctypes.c_uint]
fixture.copy_fixture_frames.argtypes = [ctypes.c_size_t, ctypes.c_size_t]
model = sys.argv[2] if len(sys.argv) > 2 else 'RFDETRMedium'
class_id = int(sys.argv[3]) if len(sys.argv) > 3 else 0
assert model in ('RFDETRMedium', 'RFDETRFireSmoke')
if model == 'RFDETRFireSmoke':
    assert class_id in (0, 1)
    fixture.set_fixture_class.argtypes = [ctypes.c_size_t, ctypes.c_int]
ingress = FrameIngress('test-server', 'test-camera', 'test-stream', 'test-run', 1)
collector = ResultCollector(['det'])
errors = []
class Sink:
    def __init__(self):
        self.frames = []
    def write(self, key, status, payload):
        if payload is not None:
            self.frames.append(json.loads(payload))
    def close(self):
        pass
class VisualBoundary:
    def reserve_image(self, frame, dimensions):
        return False  # CPU Metadata fixture에는 GPU pixel이 없다.
    def submit_result(self, result):
        self.latest = result
    def note_failure(self, code):
        raise AssertionError(code)
    def status(self):
        return {}
    def close(self, timeout):
        return True
sink = Sink()
publisher = MetadataPublisher(MetadataSettings('fixture-no-network'), sink)
publisher.start()
print('SDK_PROBE publisher_started', flush=True)
path = DetResultPath(ingress, ByteTracker(), model, .5, collector, lambda: True,
                     lambda: errors.append(True), display_threshold=.1, publisher=publisher,
                     visual=VisualBoundary())
path.start()
print('SDK_PROBE result_started', flush=True)
handle = RoleRunHandle('det', 1)
observer = _DecodedFrameObserver(handle, ingress=ingress)
receiver = _make_retriever('det', 'unused', 'unused', collector, 0, {'n': 0},
                           component_id=7, result_path=path)()
try:
    for seq, populated in enumerate((True, False)):
        raw = Gst.Buffer.new()
        print('SDK_PROBE fixture_attach', seq, flush=True)
        assert fixture.attach_fixture_at(hash(raw), populated, False, seq) == 0
        if model == 'RFDETRFireSmoke':
            assert fixture.set_fixture_class(hash(raw), class_id) == 0
        wrapped = Buffer(raw)
        print('SDK_PROBE ingress_stamp', seq, flush=True)
        assert observer.handle_buffer(wrapped)
        detached = Gst.Buffer.new()
        assert fixture.copy_fixture_frames(hash(raw), hash(detached)) == 0
        print('SDK_PROBE copied', seq, flush=True)
        del wrapped, raw
        gc.collect()
        copied = Buffer(detached)
        # 실제 SDK JSON에서 다른 run/generation의 identity 소비를 거부한다.
        for frame_meta in copied.batch_meta.frame_items:
            independent = ingress.copy(frame_meta)
            other = FrameIngress('test-server', 'test-camera', 'test-stream', 'new-run', 2)
            try:
                other.copy(frame_meta)
            except ValueError:
                pass
            else:
                raise AssertionError('foreign generation accepted')
        assert receiver.consume(copied) == 1
        del copied, detached
        gc.collect()
        end = time.monotonic() + 3
        while path.status().get('processed', 0) <= seq and time.monotonic() < end:
            time.sleep(.01)
        assert not errors and path.status()['processed'] == seq + 1
        snapshot = path.snapshot()
        assert snapshot['frameSeq'] == seq
        assert independent.frame_seq == seq
        assert independent.frame_time == ingress.frame_time
        if ingress.frame_time is not None:
            assert snapshot['frameTime']['timeSource'] == 'SYNTHETIC_TEST_ANCHOR'
            assert snapshot['frameTime']['startWallTimeMs'] == ingress.frame_time.anchor_wall_time_ms
            assert snapshot['frameTime']['rtpTimestamp'] is None
            assert snapshot['frameTime']['detectedAtWallMs'] is None
        assert snapshot['frameWidth'] == 1280
        assert len(snapshot['objects']) == int(populated)
        assert snapshot['frameTime']['rtpTimestamp'] is None
        end = time.monotonic() + 3
        while len(sink.frames) <= seq and time.monotonic() < end:
            time.sleep(.01)
        assert sink.frames[seq] == path.latest_result().snapshot()
        if populated:
            assert snapshot['objects'][0]['classId'] == class_id
            assert snapshot['objects'][0]['bbox'] == [.1, .1, .3, .3]
            assert snapshot['objects'][0]['trackId'] is not None
finally:
    path.close(3)
    assert publisher.close(1)
assert not path._thread.is_alive()
assert not glob.glob('/dev/nvidia*')
print('SDK_IDENTITY_PASS: ingress/copy/release/Native->ByteTrack->snapshot->fake transport; GPU0')
