from __future__ import annotations

import gc
import inspect
import os
import time
from pathlib import Path

import dill as pickle
import pytest
from inference.core.workflows.core_steps.trackers._base import OUTPUT_KEY
from inference.core.workflows.core_steps.trackers.bytetrack.v1 import \
    ByteTrackManifest
from inference.core.workflows.execution_engine.entities.base import \
    OutputDefinition
from inference.core.workflows.execution_engine.entities.types import \
    OBJECT_DETECTION_PREDICTION_KIND
from pydantic import ValidationError


def codeflash_wrap(codeflash_wrapped, codeflash_test_module_name, codeflash_test_class_name, codeflash_test_name, codeflash_function_name, codeflash_line_id, codeflash_loop_index, *args, **kwargs):
    test_id = f'{codeflash_test_module_name}:{codeflash_test_class_name}:{codeflash_test_name}:{codeflash_line_id}:{codeflash_loop_index}'
    if not hasattr(codeflash_wrap, 'index'):
        codeflash_wrap.index = {}
    if test_id in codeflash_wrap.index:
        codeflash_wrap.index[test_id] += 1
    else:
        codeflash_wrap.index[test_id] = 0
    codeflash_test_index = codeflash_wrap.index[test_id]
    invocation_id = f'{codeflash_line_id}_{codeflash_test_index}'
    test_stdout_tag = f"{codeflash_test_module_name}:{(codeflash_test_class_name + '.' if codeflash_test_class_name else '')}{codeflash_test_name}:{codeflash_function_name}:{codeflash_loop_index}:{invocation_id}"
    print(f'!$######{test_stdout_tag}######$!')
    exception = None
    gc.disable()
    try:
        counter = time.perf_counter_ns()
        return_value = codeflash_wrapped(*args, **kwargs)
        codeflash_duration = time.perf_counter_ns() - counter
    except Exception as e:
        codeflash_duration = time.perf_counter_ns() - counter
        exception = e
    gc.enable()
    print(f'!######{test_stdout_tag}######!')
    iteration = os.environ['CODEFLASH_TEST_ITERATION']
    with Path('{codeflash_run_tmp_dir_client_side}', f'test_return_values_{iteration}.bin').open('ab') as f:
        pickled_values = pickle.dumps((args, kwargs, exception)) if exception else pickle.dumps((args, kwargs, return_value))
        _test_name = f"{codeflash_test_module_name}:{(codeflash_test_class_name + '.' if codeflash_test_class_name else '')}{codeflash_test_name}:{codeflash_function_name}:{codeflash_line_id}".encode('ascii')
        f.write(len(_test_name).to_bytes(4, byteorder='big'))
        f.write(_test_name)
        f.write(codeflash_duration.to_bytes(8, byteorder='big'))
        f.write(len(pickled_values).to_bytes(4, byteorder='big'))
        f.write(pickled_values)
        f.write(codeflash_loop_index.to_bytes(8, byteorder='big'))
        f.write(len(invocation_id).to_bytes(4, byteorder='big'))
        f.write(invocation_id.encode('ascii'))
    if exception:
        raise exception
    return return_value

def _snapshot_outputs(outputs):
    """
    Turn a list of OutputDefinition into a tuple of (name, tuple(kind))
    which is easy and deterministic to compare in assertions.
    """
    return tuple(((out.name, tuple(out.kind)) for out in outputs))

def test_describe_outputs_basic_structure_and_types():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    _call__bound__arguments = inspect.signature(ByteTrackManifest.describe_outputs).bind()
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(ByteTrackManifest.describe_outputs, 'tests.inference.unit_tests.test_describe_outputs__unit_test_0', None, 'test_describe_outputs_basic_structure_and_types', 'describe_outputs', '0', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    outputs = codeflash_output
    for idx, out in enumerate(outputs):
        pass
    expected_names = ('tracked_detections', 'new_instances', 'already_seen_instances')
    actual_names = tuple((out.name for out in outputs))
    for out in outputs:
        pass

def test_instance_get_actual_outputs_matches_classmethod_and_is_fresh_copy():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    manifest = ByteTrackManifest(type='roboflow_core/trackers_bytetrack@v1', name='bytetrack-test')
    actual_from_instance = manifest.get_actual_outputs()
    _call__bound__arguments = inspect.signature(ByteTrackManifest.describe_outputs).bind()
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(ByteTrackManifest.describe_outputs, 'tests.inference.unit_tests.test_describe_outputs__unit_test_0', None, 'test_instance_get_actual_outputs_matches_classmethod_and_is_fresh_copy', 'describe_outputs', '2', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    actual_from_class = codeflash_output
    for a, b in zip(actual_from_instance, actual_from_class):
        pass

def test_describe_outputs_is_side_effect_free_between_calls():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    _call__bound__arguments = inspect.signature(ByteTrackManifest.describe_outputs).bind()
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(ByteTrackManifest.describe_outputs, 'tests.inference.unit_tests.test_describe_outputs__unit_test_0', None, 'test_describe_outputs_is_side_effect_free_between_calls', 'describe_outputs', '0', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    first_call = codeflash_output
    popped = first_call.pop()
    _call__bound__arguments = inspect.signature(ByteTrackManifest.describe_outputs).bind()
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(ByteTrackManifest.describe_outputs, 'tests.inference.unit_tests.test_describe_outputs__unit_test_0', None, 'test_describe_outputs_is_side_effect_free_between_calls', 'describe_outputs', '3', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    second_call = codeflash_output

def test_bad_manifest_type_raises_validation_error():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    with pytest.raises(ValidationError):
        ByteTrackManifest(type='invalid/type@v999', name='should-fail')

def test_describe_outputs_return_consistency_under_high_call_volume():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    _call__bound__arguments = inspect.signature(ByteTrackManifest.describe_outputs).bind()
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(ByteTrackManifest.describe_outputs, 'tests.inference.unit_tests.test_describe_outputs__unit_test_0', None, 'test_describe_outputs_return_consistency_under_high_call_volume', 'describe_outputs', '0', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    expected_snapshot = codeflash_return_value
    for _ in range(1000):
        _call__bound__arguments = inspect.signature(ByteTrackManifest.describe_outputs).bind()
        _call__bound__arguments.apply_defaults()
        codeflash_return_value = codeflash_wrap(ByteTrackManifest.describe_outputs, 'tests.inference.unit_tests.test_describe_outputs__unit_test_0', None, 'test_describe_outputs_return_consistency_under_high_call_volume', 'describe_outputs', '1_0', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
        s = codeflash_return_value

def test_mutating_returned_output_list_does_not_change_future_outputs():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    _call__bound__arguments = inspect.signature(ByteTrackManifest.describe_outputs).bind()
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(ByteTrackManifest.describe_outputs, 'tests.inference.unit_tests.test_describe_outputs__unit_test_0', None, 'test_mutating_returned_output_list_does_not_change_future_outputs', 'describe_outputs', '0', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    outputs1 = codeflash_output
    original_first = outputs1[0]
    outputs1.pop(0)
    replacement = OutputDefinition(name='replacement_output', kind=[OBJECT_DETECTION_PREDICTION_KIND])
    outputs1.insert(0, replacement)
    _call__bound__arguments = inspect.signature(ByteTrackManifest.describe_outputs).bind()
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(ByteTrackManifest.describe_outputs, 'tests.inference.unit_tests.test_describe_outputs__unit_test_0', None, 'test_mutating_returned_output_list_does_not_change_future_outputs', 'describe_outputs', '6', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    outputs2 = codeflash_output