"""Unit tests for pipeline._candidate_eval standalone functions.

Covers evaluate_candidate_isolated, run_tests_and_benchmark,
rank_candidates, build_benchmark_details, and log_evaluation_results.
"""

from __future__ import annotations

import ast
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch

import pytest

from codeflash_core import Candidate, EvaluationContext
from codeflash_python._model import FunctionToOptimize
from codeflash_python.pipeline._candidate_eval import (
    build_benchmark_details,
    evaluate_candidate_isolated,
    log_evaluation_results,
    rank_candidates,
    run_tests_and_benchmark,
)
from codeflash_python.pipeline._optimizer import FunctionInput
from codeflash_python.testing.models import (
    FunctionTestInvocation,
    InvocationId,
    TestConfig,
    TestFile,
    TestFiles,
    TestResults,
)
from codeflash_python.verification.models import OriginalCodeBaseline


def _make_invocation(
    *,
    did_pass: bool = True,
    runtime: int = 1_000_000,
    loop_index: int = 0,
    test_name: str = "test_fn",
    file_name: str = "test_mod.py",
) -> FunctionTestInvocation:
    """Build a minimal passing invocation."""
    from codeflash_python.test_discovery.models import TestType

    return FunctionTestInvocation(
        loop_index=loop_index,
        id=InvocationId(
            test_module_path=file_name,
            test_class_name=None,
            test_function_name=test_name,
            function_getting_tested="target_fn",
            iteration_id="0",
        ),
        file_name=Path(file_name),
        did_pass=did_pass,
        runtime=runtime,
        test_framework="pytest",
        test_type=TestType.EXISTING_UNIT_TEST,
        return_value=42,
        timed_out=False,
    )


def _make_test_results(
    *, runtime: int = 1_000_000, count: int = 1
) -> TestResults:
    """Build TestResults with *count* passing invocations."""
    tr = TestResults()
    for i in range(count):
        tr.add(
            _make_invocation(
                runtime=runtime,
                loop_index=i,
                test_name=f"test_fn_{i}",
            ),
        )
    return tr


@pytest.fixture(name="project")
def _project(tmp_path: Path) -> tuple[Path, Path]:
    """A minimal src-layout project with one module."""
    root = tmp_path / "project"
    root.mkdir()
    (root / "pyproject.toml").write_text("[project]\nname='demo'\n")
    src = root / "src"
    pkg = src / "mypkg"
    pkg.mkdir(parents=True)
    (pkg / "__init__.py").write_text("")
    mod = pkg / "core.py"
    mod.write_text("def target_fn():\n    return 42\n")
    return root, mod


@pytest.fixture(name="fn_input")
def _fn_input(project: tuple[Path, Path]) -> FunctionInput:
    """A FunctionInput pointing at the project module."""
    root, mod = project
    source = mod.read_text("utf-8")
    func = FunctionToOptimize(
        function_name="target_fn",
        file_path=mod,
    )
    return FunctionInput(
        function=func,
        module_path=mod,
        source_code=source,
        normalized_code=source,
        module_ast=ast.parse(source),
        validated_code={},
    )


@pytest.fixture(name="baseline")
def _baseline() -> OriginalCodeBaseline:
    """A baseline with runtime of 1ms."""
    behavior = _make_test_results(runtime=1_000_000)
    bench = _make_test_results(runtime=1_000_000)
    lp = TestResults()
    return OriginalCodeBaseline(
        behavior_test_results=behavior,
        benchmarking_test_results=bench,
        runtime=1_000_000,
        line_profile_results=lp,
    )


@pytest.fixture(name="ctx")
def _ctx(project: tuple[Path, Path]) -> MagicMock:
    """A minimal OptimizationContext with mocked dependencies."""
    root, _ = project
    test_cfg = TestConfig(
        tests_project_rootdir=root,
        pytest_cmd="pytest",
        tests_root=str(root / "tests"),
    )
    ctx = MagicMock()
    ctx.project_root = root
    ctx.test_cfg = test_cfg
    return ctx


@pytest.fixture(name="test_files")
def _test_files(project: tuple[Path, Path]) -> TestFiles:
    """A TestFiles with one dummy test file."""
    root, _ = project
    test_dir = root / "tests"
    test_dir.mkdir(exist_ok=True)
    test_path = test_dir / "test_core.py"
    test_path.write_text("def test_target(): pass\n")
    return TestFiles(
        test_files=[TestFile(original_file_path=test_path)],
    )


def _mock_async_behavioral_ok(
    bench_runtime: int = 500_000,
) -> tuple[Any, Any, Any]:
    """Return async-compatible mock callables for behavioral + benchmark success."""
    xml_sentinel = Path("/fake/results.xml")

    async def _run_behavioral(
        **kwargs: Any,
    ) -> tuple[Path, Any, None, None]:
        return (xml_sentinel, MagicMock(), None, None)

    async def _run_benchmarking(
        **kwargs: Any,
    ) -> tuple[Path, Any]:
        return (xml_sentinel, MagicMock())

    def _parse(
        *,
        test_xml_path: Any,
        test_files: Any,
        test_config: Any,
        optimization_iteration: int,
        run_result: Any,
    ) -> TestResults:
        return _make_test_results(runtime=bench_runtime)

    return _run_behavioral, _run_benchmarking, _parse


def _patch_eval_pipeline(
    run_beh: Any,
    run_bench: Any,
    parse: Any,
    *,
    is_correct: bool = True,
    diffs: list[Any] | None = None,
) -> Any:
    """Context manager that patches the async eval pipeline."""
    from contextlib import ExitStack

    stack = ExitStack()
    stack.enter_context(
        patch(
            "codeflash_python.testing._test_runner.async_run_behavioral_tests",
            side_effect=run_beh,
        )
    )
    stack.enter_context(
        patch(
            "codeflash_python.testing._test_runner"
            ".async_run_benchmarking_tests",
            side_effect=run_bench,
        )
    )
    stack.enter_context(
        patch(
            "codeflash_python.pipeline._candidate_eval.parse_test_results",
            side_effect=parse,
        )
    )
    stack.enter_context(
        patch(
            "codeflash_python.pipeline._candidate_eval.compare_test_results",
            return_value=(
                is_correct,
                diffs if diffs is not None else [],
            ),
        )
    )
    stack.enter_context(
        patch(
            "codeflash_python.verification._baseline.add_async_perf_decorator",
            return_value={},
        )
    )
    stack.enter_context(
        patch(
            "codeflash_python.verification._baseline.revert_async_decorator",
        )
    )
    return stack


class TestEvaluateCandidateIsolated:
    """Tests for evaluate_candidate_isolated."""

    async def test_replacement_failure_records_failed(
        self,
        fn_input: FunctionInput,
        baseline: OriginalCodeBaseline,
        ctx: MagicMock,
        test_files: TestFiles,
    ) -> None:
        """When replace_functions_in_file raises, candidate is recorded as failed."""
        candidate = Candidate(
            code="def target_fn():\n    return 42\n",
            explanation="opt",
            candidate_id="c1",
        )
        eval_ctx = EvaluationContext()
        failed_code: dict[str, str] = {}

        with patch(
            "codeflash_python.pipeline._candidate_eval"
            ".replace_functions_in_file",
            side_effect=RuntimeError("replacement boom"),
        ):
            result = await evaluate_candidate_isolated(
                candidate=candidate,
                fn_input=fn_input,
                baseline=baseline,
                eval_ctx=eval_ctx,
                test_files=test_files,
                test_env={},
                ctx=ctx,
                failed_candidate_code=failed_code,
                failed_candidate_diffs={},
                candidate_bench_results={},
            )

        assert result is None
        assert eval_ctx.is_correct["c1"] is False

    async def test_successful_eval_returns_speedup(
        self,
        fn_input: FunctionInput,
        baseline: OriginalCodeBaseline,
        ctx: MagicMock,
        test_files: TestFiles,
    ) -> None:
        """A correct, faster candidate returns a positive speedup."""
        candidate = Candidate(
            code="def target_fn():\n    return 42\n",
            explanation="faster",
            candidate_id="c2",
        )
        eval_ctx = EvaluationContext()
        failed_code: dict[str, str] = {}
        bench_results: dict[str, TestResults] = {}

        run_beh, run_bench, parse = _mock_async_behavioral_ok(
            bench_runtime=500_000,
        )

        with _patch_eval_pipeline(run_beh, run_bench, parse):
            result = await evaluate_candidate_isolated(
                candidate=candidate,
                fn_input=fn_input,
                baseline=baseline,
                eval_ctx=eval_ctx,
                test_files=test_files,
                test_env={},
                ctx=ctx,
                failed_candidate_code=failed_code,
                failed_candidate_diffs={},
                candidate_bench_results=bench_results,
            )

        assert result is not None
        assert result > 0
        assert eval_ctx.is_correct["c2"] is True
        assert "c2" in eval_ctx.optimizations_post

    async def test_original_source_unchanged(
        self,
        project: tuple[Path, Path],
        fn_input: FunctionInput,
        baseline: OriginalCodeBaseline,
        ctx: MagicMock,
        test_files: TestFiles,
    ) -> None:
        """The original module file is never modified (overlay isolation)."""
        _, mod = project
        original_content = mod.read_text("utf-8")

        candidate = Candidate(
            code="def target_fn():\n    return 99\n",
            explanation="changed",
            candidate_id="c3",
        )
        eval_ctx = EvaluationContext()

        run_beh, run_bench, parse = _mock_async_behavioral_ok()

        with _patch_eval_pipeline(run_beh, run_bench, parse):
            await evaluate_candidate_isolated(
                candidate=candidate,
                fn_input=fn_input,
                baseline=baseline,
                eval_ctx=eval_ctx,
                test_files=test_files,
                test_env={},
                ctx=ctx,
                failed_candidate_code={},
                failed_candidate_diffs={},
                candidate_bench_results={},
            )

        assert original_content == mod.read_text("utf-8")

    async def test_failed_eval_stores_candidate_code(
        self,
        fn_input: FunctionInput,
        baseline: OriginalCodeBaseline,
        ctx: MagicMock,
        test_files: TestFiles,
    ) -> None:
        """When behavioral tests fail, the code is stored in failed_candidate_code."""
        candidate = Candidate(
            code="def target_fn():\n    return 99\n",
            explanation="bad",
            candidate_id="c5",
        )
        eval_ctx = EvaluationContext()
        failed_code: dict[str, str] = {}

        run_beh, run_bench, parse = _mock_async_behavioral_ok()

        with _patch_eval_pipeline(run_beh, run_bench, parse, is_correct=False):
            result = await evaluate_candidate_isolated(
                candidate=candidate,
                fn_input=fn_input,
                baseline=baseline,
                eval_ctx=eval_ctx,
                test_files=test_files,
                test_env={},
                ctx=ctx,
                failed_candidate_code=failed_code,
                failed_candidate_diffs={},
                candidate_bench_results={},
            )

        assert result is None
        assert "c5" in failed_code
        assert failed_code["c5"] == candidate.code


class TestRunTestsAndBenchmark:
    """Tests for run_tests_and_benchmark."""

    async def test_behavioral_failure_returns_none(
        self,
        fn_input: FunctionInput,
        baseline: OriginalCodeBaseline,
        ctx: MagicMock,
        test_files: TestFiles,
    ) -> None:
        """Returns None and records failed when behavioral tests fail."""
        eval_ctx = EvaluationContext()
        failed_diffs: dict[str, list[Any]] = {}
        bench_results: dict[str, TestResults] = {}

        run_beh, run_bench, parse = _mock_async_behavioral_ok()

        with _patch_eval_pipeline(run_beh, run_bench, parse, is_correct=False):
            result = await run_tests_and_benchmark(
                cid="c1",
                fn_input=fn_input,
                baseline=baseline,
                eval_ctx=eval_ctx,
                test_files=test_files,
                test_env={},
                ctx=ctx,
                failed_candidate_diffs=failed_diffs,
                candidate_bench_results=bench_results,
            )

        assert result is None
        assert eval_ctx.is_correct["c1"] is False

    async def test_zero_runtime_returns_none_for_sync(
        self,
        fn_input: FunctionInput,
        baseline: OriginalCodeBaseline,
        ctx: MagicMock,
        test_files: TestFiles,
    ) -> None:
        """Returns None when optimized runtime is zero for sync function."""
        eval_ctx = EvaluationContext()
        failed_diffs: dict[str, list[Any]] = {}
        bench_results: dict[str, TestResults] = {}

        run_beh, run_bench, parse = _mock_async_behavioral_ok(
            bench_runtime=0,
        )

        with _patch_eval_pipeline(run_beh, run_bench, parse):
            result = await run_tests_and_benchmark(
                cid="c2",
                fn_input=fn_input,
                baseline=baseline,
                eval_ctx=eval_ctx,
                test_files=test_files,
                test_env={},
                ctx=ctx,
                failed_candidate_diffs=failed_diffs,
                candidate_bench_results=bench_results,
            )

        assert result is None
        assert eval_ctx.is_correct["c2"] is False

    async def test_successful_path_computes_speedup(
        self,
        fn_input: FunctionInput,
        baseline: OriginalCodeBaseline,
        ctx: MagicMock,
        test_files: TestFiles,
    ) -> None:
        """A correct and faster candidate returns a positive speedup."""
        eval_ctx = EvaluationContext()
        failed_diffs: dict[str, list[Any]] = {}
        bench_results: dict[str, TestResults] = {}

        run_beh, run_bench, parse = _mock_async_behavioral_ok(
            bench_runtime=500_000,
        )

        with _patch_eval_pipeline(run_beh, run_bench, parse):
            result = await run_tests_and_benchmark(
                cid="c3",
                fn_input=fn_input,
                baseline=baseline,
                eval_ctx=eval_ctx,
                test_files=test_files,
                test_env={},
                ctx=ctx,
                failed_candidate_diffs=failed_diffs,
                candidate_bench_results=bench_results,
            )

        assert result is not None
        assert result > 0
        assert eval_ctx.is_correct["c3"] is True
        assert "c3" in bench_results

    async def test_behavioral_failure_stores_diffs(
        self,
        fn_input: FunctionInput,
        baseline: OriginalCodeBaseline,
        ctx: MagicMock,
        test_files: TestFiles,
    ) -> None:
        """When behavioral tests fail with diffs, diffs are stored."""
        from codeflash_python.verification.models import (
            BehaviorDiff,
            BehaviorDiffScope,
        )

        eval_ctx = EvaluationContext()
        failed_diffs: dict[str, list[Any]] = {}
        bench_results: dict[str, TestResults] = {}

        diff_obj = BehaviorDiff(
            scope=BehaviorDiffScope.RETURN_VALUE,
            original_pass=True,
            candidate_pass=True,
            original_value="42",
            candidate_value="99",
        )

        run_beh, run_bench, parse = _mock_async_behavioral_ok()

        with _patch_eval_pipeline(
            run_beh,
            run_bench,
            parse,
            is_correct=False,
            diffs=[diff_obj],
        ):
            result = await run_tests_and_benchmark(
                cid="c4",
                fn_input=fn_input,
                baseline=baseline,
                eval_ctx=eval_ctx,
                test_files=test_files,
                test_env={},
                ctx=ctx,
                failed_candidate_diffs=failed_diffs,
                candidate_bench_results=bench_results,
            )

        assert result is None
        assert "c4" in failed_diffs
        assert 1 == len(failed_diffs["c4"])


class TestRankCandidates:
    """Tests for rank_candidates."""

    def test_empty_list_returns_none(self) -> None:
        """An empty candidate list returns None."""
        ai_client = MagicMock()
        eval_ctx = EvaluationContext()

        result = rank_candidates(
            ai_client=ai_client,
            function_trace_id="trace-1",
            eval_ctx=eval_ctx,
            valid=[],
            diff_lengths=[],
            original_source="def f(): pass",
            original_runtime_ns=1_000_000,
        )

        assert result is None

    def test_single_candidate_returns_zero(self) -> None:
        """A single candidate always returns index 0."""
        ai_client = MagicMock()
        eval_ctx = EvaluationContext()
        candidate = Candidate(
            code="def f(): return 1",
            explanation="opt",
            candidate_id="c1",
        )

        result = rank_candidates(
            ai_client=ai_client,
            function_trace_id="trace-1",
            eval_ctx=eval_ctx,
            valid=[candidate],
            diff_lengths=[10],
            original_source="def f(): pass",
            original_runtime_ns=1_000_000,
        )

        assert 0 == result

    def test_multiple_candidates_ai_ranking_first(self) -> None:
        """With multiple candidates, AI ranking is used when available."""
        ai_client = MagicMock()
        ai_client.generate_ranking.return_value = [1, 0]

        eval_ctx = EvaluationContext()
        eval_ctx.record_success("c0", runtime=500_000, speedup=1.0)
        eval_ctx.record_success("c1", runtime=250_000, speedup=3.0)

        candidates = [
            Candidate(
                code="def f(): return 1",
                explanation="opt0",
                candidate_id="c0",
            ),
            Candidate(
                code="def f(): return 2",
                explanation="opt1",
                candidate_id="c1",
            ),
        ]

        result = rank_candidates(
            ai_client=ai_client,
            function_trace_id="trace-1",
            eval_ctx=eval_ctx,
            valid=candidates,
            diff_lengths=[10, 20],
            original_source="def f(): pass",
            original_runtime_ns=1_000_000,
        )

        assert 1 == result
        ai_client.generate_ranking.assert_called_once()

    def test_fallback_to_select_best_when_ai_returns_none(
        self,
    ) -> None:
        """Falls back to select_best when AI ranking returns None."""
        ai_client = MagicMock()
        ai_client.generate_ranking.return_value = None

        eval_ctx = EvaluationContext()
        eval_ctx.record_success("c0", runtime=500_000, speedup=1.0)
        eval_ctx.record_success("c1", runtime=250_000, speedup=3.0)

        candidates = [
            Candidate(
                code="def f(): return 1",
                explanation="opt0",
                candidate_id="c0",
            ),
            Candidate(
                code="def f(): return 2",
                explanation="opt1",
                candidate_id="c1",
            ),
        ]

        with patch(
            "codeflash_python.pipeline._candidate_eval.select_best",
            return_value="c1",
        ):
            result = rank_candidates(
                ai_client=ai_client,
                function_trace_id="trace-1",
                eval_ctx=eval_ctx,
                valid=candidates,
                diff_lengths=[10, 20],
                original_source="def f(): pass",
                original_runtime_ns=1_000_000,
            )

        assert 1 == result

    def test_fallback_returns_none_when_select_best_fails(
        self,
    ) -> None:
        """Returns None when both AI ranking and select_best fail."""
        ai_client = MagicMock()
        ai_client.generate_ranking.return_value = None

        eval_ctx = EvaluationContext()
        eval_ctx.record_success("c0", runtime=500_000, speedup=1.0)
        eval_ctx.record_success("c1", runtime=250_000, speedup=3.0)

        candidates = [
            Candidate(
                code="def f(): return 1",
                explanation="opt0",
                candidate_id="c0",
            ),
            Candidate(
                code="def f(): return 2",
                explanation="opt1",
                candidate_id="c1",
            ),
        ]

        with patch(
            "codeflash_python.pipeline._candidate_eval.select_best",
            return_value=None,
        ):
            result = rank_candidates(
                ai_client=ai_client,
                function_trace_id="trace-1",
                eval_ctx=eval_ctx,
                valid=candidates,
                diff_lengths=[10, 20],
                original_source="def f(): pass",
                original_runtime_ns=1_000_000,
            )

        assert result is None


class TestBuildBenchmarkDetails:
    """Tests for build_benchmark_details."""

    def test_returns_none_when_timings_empty(
        self,
        baseline: OriginalCodeBaseline,
    ) -> None:
        """Returns None when function_benchmark_timings is empty."""
        winner = Candidate(code="x", explanation="e", candidate_id="w1")

        result = build_benchmark_details(
            winner=winner,
            baseline=baseline,
            function_benchmark_timings={},
            total_benchmark_timings={},
            candidate_bench_results={},
            replay_tests_dir=None,
            project_root=Path("/fake"),
        )

        assert result is None

    def test_returns_none_when_winner_not_in_bench_results(
        self,
        baseline: OriginalCodeBaseline,
    ) -> None:
        """Returns None when the winner has no bench results."""
        from codeflash_python.benchmarking.models import (
            BenchmarkKey,
        )

        bk = BenchmarkKey(
            module_path="mod.path",
            function_name="func",
        )
        winner = Candidate(
            code="x",
            explanation="e",
            candidate_id="missing",
        )

        result = build_benchmark_details(
            winner=winner,
            baseline=baseline,
            function_benchmark_timings={bk: 500_000},
            total_benchmark_timings={bk: 1_000_000},
            candidate_bench_results={},
            replay_tests_dir=None,
            project_root=Path("/fake"),
        )

        assert result is None

    def test_fallback_uniform_gain_without_replay_dir(
        self,
        baseline: OriginalCodeBaseline,
    ) -> None:
        """Without a replay dir, uses uniform overall gain."""
        from codeflash_python.benchmarking.models import (
            BenchmarkKey,
        )

        bk = BenchmarkKey(
            module_path="mod.path",
            function_name="func",
        )
        winner = Candidate(
            code="x",
            explanation="e",
            candidate_id="w1",
        )
        winner_bench = _make_test_results(runtime=500_000)

        mock_info = MagicMock()
        mock_detail = MagicMock()
        mock_detail.benchmark_name = "mod.path"
        mock_detail.test_function = "func"
        mock_detail.original_timing = "1.00 milliseconds"
        mock_detail.expected_new_timing = "500 microseconds"
        mock_detail.speedup_percent = 100.0
        mock_info.benchmark_details = [mock_detail]

        with patch(
            "codeflash_python.benchmarking._benchmarking"
            ".process_benchmark_data",
            return_value=mock_info,
        ):
            result = build_benchmark_details(
                winner=winner,
                baseline=baseline,
                function_benchmark_timings={bk: 500_000},
                total_benchmark_timings={bk: 1_000_000},
                candidate_bench_results={"w1": winner_bench},
                replay_tests_dir=None,
                project_root=Path("/fake"),
            )

        assert result is not None
        assert 1 == len(result)
        assert "mod.path" == result[0]["benchmark_name"]

    def test_returns_none_when_process_benchmark_data_returns_none(
        self,
        baseline: OriginalCodeBaseline,
    ) -> None:
        """Returns None when process_benchmark_data returns None."""
        from codeflash_python.benchmarking.models import (
            BenchmarkKey,
        )

        bk = BenchmarkKey(
            module_path="mod.path",
            function_name="func",
        )
        winner = Candidate(
            code="x",
            explanation="e",
            candidate_id="w1",
        )
        winner_bench = _make_test_results(runtime=500_000)

        with patch(
            "codeflash_python.benchmarking._benchmarking"
            ".process_benchmark_data",
            return_value=None,
        ):
            result = build_benchmark_details(
                winner=winner,
                baseline=baseline,
                function_benchmark_timings={bk: 500_000},
                total_benchmark_timings={bk: 1_000_000},
                candidate_bench_results={"w1": winner_bench},
                replay_tests_dir=None,
                project_root=Path("/fake"),
            )

        assert result is None

    def test_returns_none_fallback_when_original_runtime_zero(
        self,
    ) -> None:
        """Returns None when original total runtime is zero (no replay dir)."""
        from codeflash_python.benchmarking.models import (
            BenchmarkKey,
        )

        bk = BenchmarkKey(
            module_path="mod.path",
            function_name="func",
        )
        winner = Candidate(
            code="x",
            explanation="e",
            candidate_id="w1",
        )
        empty_baseline = OriginalCodeBaseline(
            behavior_test_results=TestResults(),
            benchmarking_test_results=TestResults(),
            runtime=0,
            line_profile_results=TestResults(),
        )
        winner_bench = _make_test_results(runtime=500_000)

        result = build_benchmark_details(
            winner=winner,
            baseline=empty_baseline,
            function_benchmark_timings={bk: 500_000},
            total_benchmark_timings={bk: 1_000_000},
            candidate_bench_results={"w1": winner_bench},
            replay_tests_dir=None,
            project_root=Path("/fake"),
        )

        assert result is None

    def test_builds_details_from_replay_tests_dir(
        self,
        baseline: OriginalCodeBaseline,
        project: tuple[Path, Path],
    ) -> None:
        """When replay_tests_dir is provided, uses per-benchmark grouping."""
        from codeflash_python.benchmarking.models import (
            BenchmarkKey,
        )

        root, _ = project
        bk = BenchmarkKey(
            module_path="mod.path",
            function_name="func",
        )
        winner = Candidate(
            code="x",
            explanation="e",
            candidate_id="w1",
        )
        winner_bench = _make_test_results(runtime=500_000)

        mock_grouped = {bk: _make_test_results(runtime=500_000)}

        mock_info = MagicMock()
        mock_detail = MagicMock()
        mock_detail.benchmark_name = "mod.path"
        mock_detail.test_function = "func"
        mock_detail.original_timing = "1.00 ms"
        mock_detail.expected_new_timing = "500 us"
        mock_detail.speedup_percent = 50.0
        mock_info.benchmark_details = [mock_detail]

        replay_dir = root / "replay_tests"
        replay_dir.mkdir()

        with (
            patch(
                "codeflash_python.testing.models.TestResults"
                ".group_by_benchmarks",
                return_value=mock_grouped,
            ),
            patch(
                "codeflash_python.benchmarking._benchmarking"
                ".process_benchmark_data",
                return_value=mock_info,
            ),
        ):
            result = build_benchmark_details(
                winner=winner,
                baseline=baseline,
                function_benchmark_timings={bk: 500_000},
                total_benchmark_timings={bk: 1_000_000},
                candidate_bench_results={"w1": winner_bench},
                replay_tests_dir=replay_dir,
                project_root=root,
            )

        assert result is not None
        assert 1 == len(result)
        assert "mod.path" == result[0]["benchmark_name"]
        assert "func" == result[0]["test_function"]


class TestLogEvaluationResults:
    """Tests for log_evaluation_results."""

    def test_calls_log_results_with_correct_keys(
        self,
        baseline: OriginalCodeBaseline,
    ) -> None:
        """Calls ai_client.log_results with the expected payload keys."""
        ai_client = MagicMock()
        winner = Candidate(
            code="x",
            explanation="e",
            candidate_id="w1",
        )
        eval_ctx = EvaluationContext()
        eval_ctx.record_success("w1", runtime=500_000, speedup=1.0)

        log_evaluation_results(
            ai_client=ai_client,
            function_trace_id="trace-42",
            winner=winner,
            eval_ctx=eval_ctx,
            baseline=baseline,
        )

        ai_client.log_results.assert_called_once()
        payload = ai_client.log_results.call_args[0][0]
        assert "trace-42" == payload["trace_id"]
        assert "w1" == payload["metadata"]["best_optimization_id"]
        assert 1_000_000 == payload["original_runtime"]
        assert "codeflash_version" in payload
        assert "speedup_ratio" in payload
        assert "optimized_runtime" in payload
        assert "is_correct" in payload
        assert "optimized_line_profiler_results" in payload
        assert "optimizations_post" in payload
