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.sinks.email_notification.v2 import \
    format_email_message


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 test_basic_single_replacement():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    message = 'Hello {{$parameters.name}}!'
    params = {'name': 'Alice'}
    _call__bound__arguments = inspect.signature(format_email_message).bind(message, params, {})
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(format_email_message, 'tests.inference.unit_tests.test_format_email_message__unit_test_0', None, 'test_basic_single_replacement', 'format_email_message', '2', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    result = codeflash_output

def test_multiple_occurrences_and_whitespace():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    message = 'User: {{$parameters.name }}, again: {{$parameters.name}}.'
    params = {'name': 'Bob'}
    _call__bound__arguments = inspect.signature(format_email_message).bind(message, params, {})
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(format_email_message, 'tests.inference.unit_tests.test_format_email_message__unit_test_0', None, 'test_multiple_occurrences_and_whitespace', 'format_email_message', '2', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    result = codeflash_output

def test_unmatched_placeholder_left_untouched():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    message = 'Hello {{$parameters.exists}} and {{$parameters.missing}}!'
    params = {'exists': 'Present'}
    _call__bound__arguments = inspect.signature(format_email_message).bind(message, params, {})
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(format_email_message, 'tests.inference.unit_tests.test_format_email_message__unit_test_0', None, 'test_unmatched_placeholder_left_untouched', 'format_email_message', '2', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    result = codeflash_output

def test_various_parameter_types_converted_to_strings():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    message = 'int: {{$parameters.i}}, float: {{$parameters.f}}, bool: {{$parameters.b}}, none: {{$parameters.n}}, list: {{$parameters.l}}, dict: {{$parameters.d}}'
    params = {'i': 42, 'f': 3.14, 'b': False, 'n': None, 'l': [1, 2, 3], 'd': {'x': 'y'}}
    _call__bound__arguments = inspect.signature(format_email_message).bind(message, params, {})
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(format_email_message, 'tests.inference.unit_tests.test_format_email_message__unit_test_0', None, 'test_various_parameter_types_converted_to_strings', 'format_email_message', '2', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    result = codeflash_output

def test_placeholder_name_characters_and_nonmatching_names():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    message = 'Good: {{$parameters.first_name}}, Bad: {{$parameters.first-name}}'
    params = {'first_name': 'Charlie', 'first-name': 'ShouldNotMatch'}
    _call__bound__arguments = inspect.signature(format_email_message).bind(message, params, {})
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(format_email_message, 'tests.inference.unit_tests.test_format_email_message__unit_test_0', None, 'test_placeholder_name_characters_and_nonmatching_names', 'format_email_message', '2', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    result = codeflash_output

def test_empty_operations_list_behaves_like_identity():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    message = 'Value: {{$parameters.x}}'
    params = {'x': 7}
    ops = {'x': []}
    _call__bound__arguments = inspect.signature(format_email_message).bind(message, params, ops)
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(format_email_message, 'tests.inference.unit_tests.test_format_email_message__unit_test_0', None, 'test_empty_operations_list_behaves_like_identity', 'format_email_message', '3', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    result = codeflash_output

def test_placeholder_with_extra_internal_spaces():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    message = 'Start {{$   $parameters.spaced    }} End'
    params = {'spaced': 'OK'}
    _call__bound__arguments = inspect.signature(format_email_message).bind(message, params, {})
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(format_email_message, 'tests.inference.unit_tests.test_format_email_message__unit_test_0', None, 'test_placeholder_with_extra_internal_spaces', 'format_email_message', '2', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    result = codeflash_output

def test_large_scale_many_unique_parameters():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    n = 1000
    placeholders = [f'{{$parameters.p{i}}}' for i in range(n)]
    message = ' '.join(placeholders)
    params = {f'p{i}': f'v{i}' for i in range(n)}
    _call__bound__arguments = inspect.signature(format_email_message).bind(message, params, {})
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(format_email_message, 'tests.inference.unit_tests.test_format_email_message__unit_test_0', None, 'test_large_scale_many_unique_parameters', 'format_email_message', '4', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    result = codeflash_output
    expected = ' '.join((f'v{i}' for i in range(n)))

def test_large_scale_repeated_placeholder_many_replacements():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    n = 1000
    placeholder = '{{$parameters.repeat}}'
    message = ','.join([placeholder for _ in range(n)])
    params = {'repeat': 'X'}
    _call__bound__arguments = inspect.signature(format_email_message).bind(message, params, {})
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(format_email_message, 'tests.inference.unit_tests.test_format_email_message__unit_test_0', None, 'test_large_scale_repeated_placeholder_many_replacements', 'format_email_message', '4', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    result = codeflash_output

def test_overlapping_parameter_names_do_not_interfere():
    codeflash_loop_index = int(os.environ['CODEFLASH_LOOP_INDEX'])
    message = 'A: {{$parameters.a}}, AB: {{$parameters.ab}}'
    params = {'a': 'alpha', 'ab': 'alphabeta'}
    _call__bound__arguments = inspect.signature(format_email_message).bind(message, params, {})
    _call__bound__arguments.apply_defaults()
    codeflash_return_value = codeflash_wrap(format_email_message, 'tests.inference.unit_tests.test_format_email_message__unit_test_0', None, 'test_overlapping_parameter_names_do_not_interfere', 'format_email_message', '2', codeflash_loop_index, *_call__bound__arguments.args, **_call__bound__arguments.kwargs)
    codeflash_output = codeflash_return_value
    result = codeflash_output