from __future__ import annotations

import ast
import os
import tempfile
from pathlib import Path

import pytest

from codeflash_python._model import (
    FunctionParent,
    FunctionToOptimize,
    TestingMode,
)
from codeflash_python.test_discovery.models import CodePosition
from codeflash_python.testing._instrument_core import FunctionImportedAsVisitor
from codeflash_python.testing._instrumentation import (
    inject_profiling_into_existing_test,
)

project_root = Path(__file__).parent.resolve()


@pytest.fixture
def tmp_dir():
    """Create a temporary directory for test results."""
    with tempfile.TemporaryDirectory() as tmpdirname:
        yield Path(tmpdirname)


def _assert_sync_instrumentation_present(source: str) -> None:
    """Assert that the new sync instrumentation markers are present."""
    assert "_codeflash_call_site.set(" in source
    assert "from codeflash_async_wrapper import _codeflash_call_site" in source


def _assert_old_instrumentation_absent(source: str) -> None:
    """Assert that old sync instrumentation artifacts are absent."""
    assert "codeflash_wrap" not in source
    assert "codeflash_con" not in source
    assert "codeflash_cur" not in source
    assert "import sqlite3" not in source


def test_perfinjector_bubble_sort(tmp_dir) -> None:
    """Instrument a unittest bubble sort test with profiling."""
    code = """import unittest

from code_to_optimize.bubble_sort import sorter


class TestPigLatin(unittest.TestCase):
    def test_sort(self):
        input = [5, 4, 3, 2, 1, 0]
        output = sorter(input)
        self.assertEqual(output, [0, 1, 2, 3, 4, 5])

        input = [5.0, 4.0, 3.0, 2.0, 1.0, 0.0]
        output = sorter(input)
        self.assertEqual(output, [0.0, 1.0, 2.0, 3.0, 4.0, 5.0])

        input = list(reversed(range(5000)))
        self.assertEqual(sorter(input), list(range(5000)))
"""

    with (tmp_dir / "test_sort.py").open("w") as f:
        f.write(code)
        f.flush()
        func = FunctionToOptimize(
            function_name="sorter", parents=(), file_path=Path(f.name)
        )
        original_cwd = Path.cwd()
        run_cwd = project_root
        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            Path(f.name),
            [CodePosition(9, 17), CodePosition(13, 17), CodePosition(17, 17)],
            func,
            Path(f.name).parent,
        )
        os.chdir(original_cwd)
    assert success
    assert new_test is not None
    _assert_sync_instrumentation_present(new_test)
    _assert_old_instrumentation_absent(new_test)
    assert "sorter" in new_test
    assert "class TestPigLatin" in new_test
    assert "def test_sort" in new_test
    count = new_test.count("_codeflash_call_site.set(")
    assert count == 3


def test_perfinjector_only_replay_test(tmp_dir) -> None:
    """Instrument a replay test with profiling."""
    code = """import dill as pickle
import pytest
from codeflash.tracing.replay_test import get_next_arg_and_return
from codeflash.validation.equivalence import compare_results
from packagename.ml.yolo.image_reshaping_utils import prepare_image_for_yolo as packagename_ml_yolo_image_reshaping_utils_prepare_image_for_yolo
def test_prepare_image_for_yolo():
    for arg_val_pkl, return_val_pkl in get_next_arg_and_return('/home/saurabh/packagename/traces/first.trace', 3):
        args = pickle.loads(arg_val_pkl)
        return_val_1= pickle.loads(return_val_pkl)
        ret = packagename_ml_yolo_image_reshaping_utils_prepare_image_for_yolo(**args)
        assert compare_results(return_val_1, ret)
"""

    with (tmp_dir / "test_return_values.py").open("w") as f:
        f.write(code)
        f.flush()
        func = FunctionToOptimize(
            function_name="prepare_image_for_yolo",
            parents=(),
            file_path=Path("module.py"),
        )
        original_cwd = Path.cwd()
        run_cwd = project_root
        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            Path(f.name), [CodePosition(10, 14)], func, Path(f.name).parent
        )
        os.chdir(original_cwd)
    assert success
    assert new_test is not None
    _assert_sync_instrumentation_present(new_test)
    _assert_old_instrumentation_absent(new_test)
    assert (
        "packagename_ml_yolo_image_reshaping_utils_prepare_image_for_yolo"
        in new_test
    )
    assert "def test_prepare_image_for_yolo" in new_test
    assert "_codeflash_call_site.set(" in new_test


def test_perfinjector_bubble_sort_results() -> None:
    """Instrument bubble sort and verify output structure."""
    code = """from code_to_optimize.bubble_sort import sorter
import datetime


def test_sort():
    input = [5, 4, 3, 2, 1, 0]
    print(datetime.datetime.now().isoformat())
    output = sorter(input)
    assert output == [0, 1, 2, 3, 4, 5]

    input = [5.0, 4.0, 3.0, 2.0, 1.0, 0.0]
    output = sorter(input)
    assert output == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]"""

    test_path = (
        project_root
        / "code_to_optimize/tests/pytest/test_perfinjector_bubble_sort_results_temp.py"
    ).resolve()
    try:
        with test_path.open("w") as f:
            f.write(code)
        code_path = (
            project_root / "code_to_optimize/bubble_sort.py"
        ).resolve()
        project_root_path = project_root
        original_cwd = Path.cwd()
        run_cwd = project_root
        func = FunctionToOptimize(
            function_name="sorter", parents=(), file_path=code_path
        )
        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(8, 14), CodePosition(12, 14)],
            func,
            project_root_path,
            mode=TestingMode.BEHAVIOR,
        )
        os.chdir(original_cwd)
        assert success
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "def test_sort" in new_test
        assert "sorter" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 2

        os.chdir(run_cwd)
        success_perf, new_perf_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(8, 14), CodePosition(12, 14)],
            func,
            project_root_path,
            mode=TestingMode.PERFORMANCE,
        )
        os.chdir(original_cwd)
        assert success_perf
        assert new_perf_test is not None
        _assert_sync_instrumentation_present(new_perf_test)
        _assert_old_instrumentation_absent(new_perf_test)
    finally:
        test_path.unlink(missing_ok=True)


def test_perfinjector_bubble_sort_parametrized_results() -> None:
    """Instrument parametrized bubble sort and verify output structure."""
    code = """from code_to_optimize.bubble_sort import sorter
import pytest


@pytest.mark.parametrize(
    "input, expected_output",
    [
        ([5, 4, 3, 2, 1, 0], [0, 1, 2, 3, 4, 5]),
        ([5.0, 4.0, 3.0, 2.0, 1.0, 0.0], [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]),
        (list(reversed(range(50))), list(range(50))),
    ],
)
def test_sort_parametrized(input, expected_output):
    output = sorter(input)
    assert output == expected_output
"""
    code_path = (project_root / "code_to_optimize/bubble_sort.py").resolve()
    test_path = (
        project_root
        / "code_to_optimize/tests/pytest/test_perfinjector_bubble_sort_parametrized_results_temp.py"
    ).resolve()
    try:
        with test_path.open("w") as f:
            f.write(code)

        project_root_path = project_root
        original_cwd = Path.cwd()
        run_cwd = project_root

        func = FunctionToOptimize(
            function_name="sorter", parents=(), file_path=code_path
        )
        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(14, 13)],
            func,
            project_root_path,
            mode=TestingMode.BEHAVIOR,
        )
        assert success
        success_perf, new_test_perf = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(14, 13)],
            func,
            project_root_path,
            mode=TestingMode.PERFORMANCE,
        )
        os.chdir(original_cwd)

        assert success
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "def test_sort_parametrized" in new_test
        assert "sorter" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 1

        assert success_perf
        assert new_test_perf is not None
        _assert_sync_instrumentation_present(new_test_perf)
        _assert_old_instrumentation_absent(new_test_perf)
    finally:
        test_path.unlink(missing_ok=True)


def test_perfinjector_bubble_sort_parametrized_loop_results() -> None:
    """Instrument parametrized loop bubble sort and verify output structure."""
    code = """from code_to_optimize.bubble_sort import sorter
import pytest


@pytest.mark.parametrize(
    "input, expected_output",
    [
        ([5, 4, 3, 2, 1, 0], [0, 1, 2, 3, 4, 5]),
        ([5.0, 4.0, 3.0, 2.0, 1.0, 0.0], [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]),
        (list(reversed(range(50))), list(range(50))),
    ],
)
def test_sort_parametrized_loop(input, expected_output):
    for i in range(2):
        output = sorter(input)
        assert output == expected_output
"""
    code_path = (project_root / "code_to_optimize/bubble_sort.py").resolve()
    test_path = (
        project_root
        / "code_to_optimize/tests/pytest/test_perfinjector_bubble_sort_parametrized_loop_results_temp.py"
    ).resolve()
    try:
        with test_path.open("w") as f:
            f.write(code)

        project_root_path = project_root
        original_cwd = Path.cwd()
        run_cwd = project_root

        func = FunctionToOptimize(
            function_name="sorter", parents=(), file_path=code_path
        )
        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(15, 17)],
            func,
            project_root_path,
            mode=TestingMode.BEHAVIOR,
        )
        assert success
        success_perf, new_test_perf = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(15, 17)],
            func,
            project_root_path,
            mode=TestingMode.PERFORMANCE,
        )
        os.chdir(original_cwd)

        assert success
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "def test_sort_parametrized_loop" in new_test
        assert "sorter" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 1

        assert success_perf
        assert new_test_perf is not None
        _assert_sync_instrumentation_present(new_test_perf)
        _assert_old_instrumentation_absent(new_test_perf)
    finally:
        test_path.unlink(missing_ok=True)


def test_perfinjector_bubble_sort_loop_results() -> None:
    """Instrument loop bubble sort and verify output structure."""
    code = """from code_to_optimize.bubble_sort import sorter


def test_sort():
    inputs = [[5, 4, 3, 2, 1, 0], [5.0, 4.0, 3.0, 2.0, 1.0, 0.0], list(reversed(range(50)))]
    expected_outputs = [[0, 1, 2, 3, 4, 5], [0.0, 1.0, 2.0, 3.0, 4.0, 5.0], list(range(50))]

    for i in range(3):
        input = inputs[i]
        expected_output = expected_outputs[i]
        output = sorter(input)
        assert output == expected_output"""

    code_path = (project_root / "code_to_optimize/bubble_sort.py").resolve()
    test_path = (
        project_root
        / "code_to_optimize/tests/pytest/test_perfinjector_bubble_sort_loop_results_temp.py"
    ).resolve()
    try:
        with test_path.open("w") as f:
            f.write(code)

        project_root_path = project_root
        original_cwd = Path.cwd()
        run_cwd = project_root

        func = FunctionToOptimize(
            function_name="sorter", parents=(), file_path=code_path
        )
        os.chdir(str(run_cwd))
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(11, 17)],
            func,
            project_root_path,
            mode=TestingMode.BEHAVIOR,
        )
        assert success
        success_perf, new_test_perf = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(11, 17)],
            func,
            project_root_path,
            mode=TestingMode.PERFORMANCE,
        )
        os.chdir(original_cwd)

        assert success
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "def test_sort" in new_test
        assert "sorter" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 1

        assert success_perf
        assert new_test_perf is not None
        _assert_sync_instrumentation_present(new_test_perf)
        _assert_old_instrumentation_absent(new_test_perf)
    finally:
        test_path.unlink(missing_ok=True)


def test_perfinjector_bubble_sort_unittest_results() -> None:
    """Instrument unittest bubble sort and verify output structure."""
    code = """import unittest

from code_to_optimize.bubble_sort import sorter


class TestPigLatin(unittest.TestCase):
    def test_sort(self):
        input = [5, 4, 3, 2, 1, 0]
        output = sorter(input)
        self.assertEqual(output, [0, 1, 2, 3, 4, 5])

        input = [5.0, 4.0, 3.0, 2.0, 1.0, 0.0]
        output = sorter(input)
        self.assertEqual(output, [0.0, 1.0, 2.0, 3.0, 4.0, 5.0])

        input = list(reversed(range(50)))
        output = sorter(input)
        self.assertEqual(output, list(range(50)))
"""

    code_path = (project_root / "code_to_optimize/bubble_sort.py").resolve()
    test_path = (
        project_root
        / "code_to_optimize/tests/unittest/test_perfinjector_bubble_sort_unittest_results_temp.py"
    ).resolve()
    try:
        with test_path.open("w") as f:
            f.write(code)

        project_root_path = project_root
        run_cwd = project_root
        original_cwd = Path.cwd()

        func = FunctionToOptimize(
            function_name="sorter", parents=(), file_path=code_path
        )
        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(9, 17), CodePosition(13, 17), CodePosition(17, 17)],
            func,
            project_root_path,
            mode=TestingMode.BEHAVIOR,
        )
        assert success
        success_perf, new_test_perf = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(9, 17), CodePosition(13, 17), CodePosition(17, 17)],
            func,
            project_root_path,
            mode=TestingMode.PERFORMANCE,
        )
        os.chdir(original_cwd)

        assert success
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "class TestPigLatin" in new_test
        assert "def test_sort" in new_test
        assert "sorter" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 3

        assert success_perf
        assert new_test_perf is not None
        _assert_sync_instrumentation_present(new_test_perf)
        _assert_old_instrumentation_absent(new_test_perf)
        assert new_test_perf.count("_codeflash_call_site.set(") == 3
    finally:
        test_path.unlink(missing_ok=True)


def test_perfinjector_bubble_sort_unittest_parametrized_results() -> None:
    """Instrument unittest parametrized bubble sort and verify output structure."""
    code = """import unittest
from parameterized import parameterized

from code_to_optimize.bubble_sort import sorter


class TestPigLatin(unittest.TestCase):
    @parameterized.expand(
        [
            ([5, 4, 3, 2, 1, 0], [0, 1, 2, 3, 4, 5]),
            ([5.0, 4.0, 3.0, 2.0, 1.0, 0.0], [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]),
            (list(reversed(range(50))), list(range(50))),
        ]
    )
    def test_sort(self, input, expected_output):
        output = sorter(input)
        self.assertEqual(output, expected_output)
"""

    code_path = (project_root / "code_to_optimize/bubble_sort.py").resolve()
    test_path = (
        project_root
        / "code_to_optimize/tests/unittest/test_perfinjector_bubble_sort_unittest_parametrized_results_temp.py"
    ).resolve()
    try:
        with test_path.open("w") as f:
            f.write(code)
        project_root_path = project_root
        run_cwd = project_root
        original_cwd = Path.cwd()

        func = FunctionToOptimize(
            function_name="sorter", parents=(), file_path=code_path
        )
        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(16, 17)],
            func,
            project_root_path,
            mode=TestingMode.BEHAVIOR,
        )
        assert success
        success_perf, new_test_perf = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(16, 17)],
            func,
            project_root_path,
            mode=TestingMode.PERFORMANCE,
        )
        os.chdir(original_cwd)

        assert success
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "class TestPigLatin" in new_test
        assert "def test_sort" in new_test
        assert "sorter" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 1

        assert success_perf
        assert new_test_perf is not None
        _assert_sync_instrumentation_present(new_test_perf)
        _assert_old_instrumentation_absent(new_test_perf)
    finally:
        test_path.unlink(missing_ok=True)


def test_perfinjector_bubble_sort_unittest_loop_results() -> None:
    """Instrument unittest loop bubble sort and verify output structure."""
    code = """import unittest

from code_to_optimize.bubble_sort import sorter


class TestPigLatin(unittest.TestCase):
    def test_sort(self):
        inputs = [[5, 4, 3, 2, 1, 0], [5.0, 4.0, 3.0, 2.0, 1.0, 0.0], list(reversed(range(50)))]
        expected_outputs = [[0, 1, 2, 3, 4, 5], [0.0, 1.0, 2.0, 3.0, 4.0, 5.0], list(range(50))]

        for i in range(3):
            input = inputs[i]
            expected_output = expected_outputs[i]
            output = sorter(input)
            self.assertEqual(output, expected_output)"""

    code_path = (project_root / "code_to_optimize/bubble_sort.py").resolve()
    test_path = (
        project_root
        / "code_to_optimize/tests/unittest/test_perfinjector_bubble_sort_unittest_loop_results_temp.py"
    ).resolve()
    try:
        with test_path.open("w") as f:
            f.write(code)

        project_root_path = project_root
        run_cwd = project_root
        original_cwd = Path.cwd()

        func = FunctionToOptimize(
            function_name="sorter", parents=(), file_path=code_path
        )
        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(14, 21)],
            func,
            project_root_path,
            mode=TestingMode.BEHAVIOR,
        )
        assert success
        success_perf, new_test_perf = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(14, 21)],
            func,
            project_root_path,
            mode=TestingMode.PERFORMANCE,
        )
        os.chdir(original_cwd)

        assert success
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "class TestPigLatin" in new_test
        assert "def test_sort" in new_test
        assert "sorter" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 1

        assert success_perf
        assert new_test_perf is not None
        _assert_sync_instrumentation_present(new_test_perf)
        _assert_old_instrumentation_absent(new_test_perf)
    finally:
        test_path.unlink(missing_ok=True)


def test_perfinjector_bubble_sort_unittest_parametrized_loop_results() -> None:
    """Instrument unittest parametrized loop bubble sort and verify output structure."""
    code = """import unittest
from parameterized import parameterized

from code_to_optimize.bubble_sort import sorter


class TestPigLatin(unittest.TestCase):
    @parameterized.expand(
        [
            ([5, 4, 3, 2, 1, 0], [0, 1, 2, 3, 4, 5]),
            ([5.0, 4.0, 3.0, 2.0, 1.0, 0.0], [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]),
            (list(reversed(range(50))), list(range(50))),
        ]
    )
    def test_sort(self, input, expected_output):
        for i in range(2):
            output = sorter(input)
            self.assertEqual(output, expected_output)
"""

    code_path = (project_root / "code_to_optimize/bubble_sort.py").resolve()
    test_path = (
        project_root
        / "code_to_optimize/tests/unittest/test_perfinjector_bubble_sort_unittest_parametrized_loop_results_temp.py"
    ).resolve()
    try:
        with test_path.open("w") as f:
            f.write(code)
        project_root_path = project_root
        run_cwd = project_root
        original_cwd = Path.cwd()

        func = FunctionToOptimize(
            function_name="sorter", file_path=code_path, parents=()
        )
        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(17, 21)],
            func,
            project_root_path,
            mode=TestingMode.BEHAVIOR,
        )
        success_perf, new_test_perf = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(17, 21)],
            func,
            project_root_path,
            mode=TestingMode.PERFORMANCE,
        )
        os.chdir(original_cwd)

        assert success
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "class TestPigLatin" in new_test
        assert "def test_sort" in new_test
        assert "sorter" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 1

        assert success_perf
        assert new_test_perf is not None
        _assert_sync_instrumentation_present(new_test_perf)
        _assert_old_instrumentation_absent(new_test_perf)
    finally:
        test_path.unlink(missing_ok=True)


def test_class_method_imported_as() -> None:
    """Detect import aliases for functions and class methods."""
    code = """import functionA
import moduleB as module_B
from module import functionB as function_B
import class_name_B
from nuitka.nodes.ImportNodes import ExpressionBuiltinImport as nuitka_nodes_ImportNodes_ExpressionBuiltinImport
"""
    f = FunctionToOptimize(
        function_name="functionA", file_path=Path("module.py"), parents=()
    )
    tree = ast.parse(code)
    visitor = FunctionImportedAsVisitor(f)
    visitor.visit(tree)
    assert visitor.imported_as.function_name == "functionA"

    f = FunctionToOptimize(
        function_name="functionB", file_path=Path("module.py"), parents=()
    )
    visitor = FunctionImportedAsVisitor(f)
    visitor.visit(tree)
    assert visitor.imported_as.function_name == "function_B"

    f = FunctionToOptimize(
        function_name="method_name",
        file_path=Path("module.py"),
        parents=(FunctionParent("ExpressionBuiltinImport", "ClassDef"),),
    )
    visitor = FunctionImportedAsVisitor(f)
    visitor.visit(tree)
    assert (
        visitor.imported_as.qualified_name
        == "nuitka_nodes_ImportNodes_ExpressionBuiltinImport.method_name"
    )

    f = FunctionToOptimize(
        function_name="class_name_B", file_path=Path("module.py"), parents=()
    )
    visitor = FunctionImportedAsVisitor(f)
    visitor.visit(tree)
    assert visitor.imported_as.qualified_name == "class_name_B"


def test_class_function_instrumentation() -> None:
    """Instrument a class method call in a test function."""
    code = """from module import class_name as class_name_A

def test_class_name_A_function_name():
    ret = class_name_A.function_name(**args)
"""

    test_path = (
        project_root
        / "code_to_optimize/tests/pytest/test_class_function_instrumentation_temp.py"
    )
    try:
        with open(test_path, "w") as f:
            f.write(code)

        project_root_path = project_root / "code_to_optimize/"
        run_cwd = project_root
        original_cwd = Path.cwd()
        func = FunctionToOptimize(
            function_name="function_name",
            file_path=project_root_path / "module.py",
            parents=(FunctionParent("class_name", "ClassDef"),),
        )
        os.chdir(str(run_cwd))
        success, new_test = inject_profiling_into_existing_test(
            test_path, [CodePosition(4, 23)], func, project_root_path
        )
        os.chdir(original_cwd)
    finally:
        test_path.unlink(missing_ok=True)
    assert success
    assert new_test is not None
    _assert_sync_instrumentation_present(new_test)
    _assert_old_instrumentation_absent(new_test)
    assert "class_name_A.function_name" in new_test
    assert "def test_class_name_A_function_name" in new_test
    assert new_test.count("_codeflash_call_site.set(") == 1


def test_wrong_function_instrumentation() -> None:
    """Instrument multiple calls to find_common_tags correctly."""
    code = """from codeflash.result.common_tags import find_common_tags


def test_common_tags_1():
    articles_1 = [1, 2, 3]

    assert find_common_tags(articles_1) == set(1, 2)

    articles_2 = [1, 2]

    assert find_common_tags(articles_2) == set(1)
"""

    test_path = (
        project_root
        / "code_to_optimize/tests/pytest/test_wrong_function_instrumentation_temp.py"
    )
    try:
        with test_path.open("w") as f:
            f.write(code)

        project_root_path = project_root / "code_to_optimize/"
        run_cwd = project_root
        original_cwd = Path.cwd()
        func = FunctionToOptimize(
            function_name="find_common_tags",
            file_path=project_root_path / "module.py",
            parents=(),
        )

        os.chdir(str(run_cwd))
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(7, 11), CodePosition(11, 11)],
            func,
            project_root_path,
        )
        os.chdir(original_cwd)
        assert success
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "find_common_tags" in new_test
        assert "def test_common_tags_1" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 2
    finally:
        test_path.unlink(missing_ok=True)


def test_conditional_instrumentation() -> None:
    """Instrument a function call inside an if block."""
    code = """from code_to_optimize.bubble_sort import sorter


def test_sort():
    input = [5, 4, 3, 2, 1, 0]
    if len(input) > 0:
        assert sorter(input) == [0, 1, 2, 3, 4, 5]"""

    test_path = (
        project_root
        / "code_to_optimize/tests/pytest/test_conditional_instrumentation_temp.py"
    )
    try:
        with open(test_path, "w") as f:
            f.write(code)

        project_root_path = project_root / "code_to_optimize/"
        run_cwd = project_root
        original_cwd = Path.cwd()
        func = FunctionToOptimize(
            function_name="sorter",
            file_path=project_root_path / "module.py",
            parents=(),
        )

        os.chdir(str(run_cwd))
        success, new_test = inject_profiling_into_existing_test(
            test_path, [CodePosition(7, 15)], func, project_root_path
        )
        os.chdir(original_cwd)
        assert success
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "sorter" in new_test
        assert "def test_sort" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 1
    finally:
        test_path.unlink(missing_ok=True)


def test_static_method_instrumentation():
    """Instrument a static method call (BubbleSorter.sorter)."""
    code = """from code_to_optimize.bubble_sort import BubbleSorter


def test_sort():
    input = [5, 4, 3, 2, 1, 0]
    output = BubbleSorter.sorter(input)
    assert output == [0, 1, 2, 3, 4, 5]

    input = [5.0, 4.0, 3.0, 2.0, 1.0, 0.0]
    output = BubbleSorter.sorter(input)
    assert output == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]"""

    function_to_optimize = FunctionToOptimize(
        function_name="sorter",
        file_path=Path(
            "/Users/renaud/repos/codeflash/cli/code_to_optimize/bubble_sort.py"
        ),
        parents=(FunctionParent("BubbleSorter", "ClassDef"),),
        starting_line=None,
        ending_line=None,
    )

    test_path = (
        project_root
        / "code_to_optimize/tests/pytest/test_perfinjector_bubble_sort_results_temp.py"
    )
    try:
        with test_path.open("w") as f:
            f.write(code)
        project_root_path = project_root / "code_to_optimize/"
        run_cwd = project_root
        original_cwd = Path.cwd()

        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(6, 26), CodePosition(10, 26)],
            function_to_optimize,
            project_root_path,
        )
        os.chdir(original_cwd)
        assert success
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "BubbleSorter.sorter" in new_test
        assert "def test_sort" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 2
    finally:
        test_path.unlink(missing_ok=True)


def test_class_method_instrumentation(tmp_path: Path) -> None:
    """Instrument a class method call (Optimizer.get_code_optimization_context)."""
    code = """from codeflash.optimization.optimizer import Optimizer
def test_code_replacement10() -> None:
    get_code_output = '''random code'''
    file_path = Path(__file__).resolve()
    opt = Optimizer(
        Namespace(
            project_root=str(file_path.parent.resolve()),
            disable_telemetry=True,
            tests_root="tests",
            test_framework="pytest",
            pytest_cmd="pytest",
            experiment_id=None,
        ),
    )
    func_top_optimize = FunctionToOptimize(
        function_name="main_method",
        file_path=str(file_path),
        parents=[FunctionParent("MainClass", "ClassDef")],
    )
    with open(file_path) as f:
        original_code = f.read()
        code_context = opt.get_code_optimization_context(
            function_to_optimize=func_top_optimize,
            project_root=str(file_path.parent),
            original_source_code=original_code,
        ).unwrap()
        assert code_context.testgen_context_code == get_code_output
        code_context = opt.get_code_optimization_context(
            function_to_optimize=func_top_optimize,
            project_root=str(file_path.parent),
            original_source_code=original_code,
        )
        assert code_context.testgen_context_code == get_code_output
    """

    test_file_path = tmp_path / "test_class_method_instrumentation.py"
    test_file_path.write_text(code, encoding="utf-8")

    func = FunctionToOptimize(
        function_name="get_code_optimization_context",
        parents=(FunctionParent("Optimizer", "ClassDef"),),
        file_path=test_file_path,
    )
    original_cwd = Path.cwd()
    run_cwd = project_root
    os.chdir(run_cwd)
    success, new_test = inject_profiling_into_existing_test(
        test_file_path,
        [CodePosition(22, 28), CodePosition(28, 28)],
        func,
        test_file_path.parent,
    )
    os.chdir(original_cwd)
    assert success
    assert new_test is not None
    _assert_sync_instrumentation_present(new_test)
    _assert_old_instrumentation_absent(new_test)
    assert "opt.get_code_optimization_context" in new_test
    assert "def test_code_replacement10" in new_test
    assert new_test.count("_codeflash_call_site.set(") >= 1


def test_time_correction_instrumentation() -> None:
    """Instrument parametrized sleep test for performance timing."""
    code = """from code_to_optimize.sleeptime import accurate_sleepfunc
import pytest
@pytest.mark.parametrize("n, expected_total_sleep_time", [
    (0.01, 0.010),
    (0.02, 0.020),
])
def test_sleepfunc_sequence_short(n, expected_total_sleep_time):
    output = accurate_sleepfunc(n)
    assert output == expected_total_sleep_time

"""

    code_path = (project_root / "code_to_optimize/sleeptime.py").resolve()
    test_path = (
        project_root
        / "code_to_optimize/tests/pytest/test_time_correction_instrumentation_temp.py"
    ).resolve()
    try:
        with test_path.open("w") as f:
            f.write(code)

        project_root_path = project_root
        original_cwd = Path.cwd()
        run_cwd = project_root
        func = FunctionToOptimize(
            function_name="accurate_sleepfunc", parents=(), file_path=code_path
        )
        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(8, 13)],
            func,
            project_root_path,
            mode=TestingMode.PERFORMANCE,
        )
        os.chdir(original_cwd)

        assert success, "Test instrumentation failed"
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "accurate_sleepfunc" in new_test
        assert "def test_sleepfunc_sequence_short" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 1
    finally:
        test_path.unlink(missing_ok=True)


def test_time_correction_instrumentation_unittest() -> None:
    """Instrument parametrized unittest sleep test for performance timing."""
    code = """import unittest
from parameterized import parameterized

from code_to_optimize.sleeptime import accurate_sleepfunc


class TestPigLatin(unittest.TestCase):
    @parameterized.expand([
        (0.01, 0.010),
        (0.02, 0.020),
    ])
    def test_sleepfunc_sequence_short(self, n, expected_total_sleep_time):
        output = accurate_sleepfunc(n)
"""

    code_path = (project_root / "code_to_optimize/sleeptime.py").resolve()
    test_path = (
        project_root
        / "code_to_optimize/tests/unittest/test_time_correction_instrumentation_unittest_temp.py"
    ).resolve()
    try:
        with test_path.open("w") as f:
            f.write(code)

        project_root_path = project_root
        original_cwd = Path.cwd()
        run_cwd = project_root
        func = FunctionToOptimize(
            function_name="accurate_sleepfunc", parents=(), file_path=code_path
        )
        os.chdir(run_cwd)
        success, new_test = inject_profiling_into_existing_test(
            test_path,
            [CodePosition(13, 17)],
            func,
            project_root_path,
            mode=TestingMode.PERFORMANCE,
        )
        os.chdir(original_cwd)

        assert success, "Test instrumentation failed"
        assert new_test is not None
        _assert_sync_instrumentation_present(new_test)
        _assert_old_instrumentation_absent(new_test)
        assert "accurate_sleepfunc" in new_test
        assert "class TestPigLatin" in new_test
        assert "def test_sleepfunc_sequence_short" in new_test
        assert new_test.count("_codeflash_call_site.set(") == 1
    finally:
        test_path.unlink(missing_ok=True)
