"""Main evaluation harness orchestrating parallel task execution."""

import asyncio
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, TextIO

from rich.console import Console
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn

from .benchmarks import Benchmark, create_benchmark
from .cache import ResultCache
from .config import HarnessConfig
from .docker_env import DockerEnvironmentManager, TaskEnvironment
from .evaluation import EvaluationResult
from .harnesses import AgentHarness, AgentResult, create_harness
from .incremental_save import save_task_result_incremental
from .log_formatter import InstanceLogWriter
from .pricing import calculate_cost

console = Console()


class SimpleNamespace:
    """Simple object to hold attributes from a dictionary."""

    def __init__(self, **kwargs: Any) -> None:
        """Initialize with keyword arguments as attributes."""
        self.__dict__.update(kwargs)


def dict_to_namespace(data: Any) -> Any:
    """Recursively convert dict to SimpleNamespace for attribute access.

    Args:
        data: Data to convert (dict, list, or primitive).

    Returns:
        Converted data with dicts as SimpleNamespace objects.
    """
    if isinstance(data, dict):
        return SimpleNamespace(**{k: dict_to_namespace(v) for k, v in data.items()})
    elif isinstance(data, list):
        return [dict_to_namespace(item) for item in data]
    else:
        return data


@dataclass
class TaskResult:
    """Result for a single task."""

    instance_id: str
    mcp: dict[str, Any] | None = None
    baseline: dict[str, Any] | None = None


@dataclass
class EvaluationResults:
    """Complete evaluation results."""

    metadata: dict[str, Any]
    summary: dict[str, Any]
    tasks: list[TaskResult]


def agent_result_to_dict(
    result: AgentResult, eval_result: EvaluationResult | None, model_id: str
) -> dict[str, Any]:
    """Convert agent and evaluation results to dictionary.

    Args:
        result: Agent result with token usage.
        eval_result: Evaluation result (if patch was generated).
        model_id: Model ID for cost calculation.

    Returns:
        Dictionary with agent results including cost information.
    """
    data: dict[str, Any] = {
        "patch_generated": bool(result.patch),
        "tokens": {
            "input": result.tokens_input,
            "output": result.tokens_output,
        },
        "iterations": result.iterations,
        "tool_calls": result.tool_calls,
    }

    # Calculate cost
    cost = calculate_cost(
        model_id=model_id,
        input_tokens=result.tokens_input,
        output_tokens=result.tokens_output,
    )
    if cost is not None:
        data["cost"] = cost

    if result.tool_usage:
        data["tool_usage"] = result.tool_usage

    if result.error:
        data["error"] = result.error

    if result.messages:
        data["messages"] = result.messages

    if eval_result:
        data["resolved"] = eval_result.resolved
        data["patch_applied"] = eval_result.patch_applied

        if getattr(eval_result, "fail_to_pass", None):
            data["fail_to_pass"] = {
                "passed": eval_result.fail_to_pass.passed,
                "total": eval_result.fail_to_pass.total,
            }

        if getattr(eval_result, "pass_to_pass", None):
            data["pass_to_pass"] = {
                "passed": eval_result.pass_to_pass.passed,
                "total": eval_result.pass_to_pass.total,
            }

        if getattr(eval_result, "error", None):
            data["eval_error"] = eval_result.error
    else:
        data["resolved"] = False
        data["patch_applied"] = False

    return data


def _create_mcp_agent(
    config: HarnessConfig,
    benchmark: Benchmark,
    verbosity: int = 1,
    log_file: TextIO | InstanceLogWriter | None = None,
) -> AgentHarness:
    """Create the agent harness based on config.

    Args:
        config: Harness configuration.
        benchmark: Benchmark instance for getting default prompt.
        verbosity: Verbosity level (0=silent, 1=summary, 2=detailed).
        log_file: Optional file handle for writing raw JSON logs.

    Returns:
        Configured AgentHarness.
    """
    # Use custom prompt if provided, otherwise use benchmark's default prompt
    prompt = config.agent_prompt if config.agent_prompt else benchmark.get_prompt_template()

    return create_harness(
        config.agent_harness,
        model=config.model,
        mcp_server=config.mcp_server,
        prompt=prompt,
        max_iterations=config.max_iterations,
        verbosity=verbosity,
        log_file=log_file,
    )


def _create_baseline_agent(
    config: HarnessConfig,
    benchmark: Benchmark,
    verbosity: int = 1,
    log_file: TextIO | InstanceLogWriter | None = None,
) -> AgentHarness:
    """Create a baseline agent (same as MCP agent, but without MCP server).

    Args:
        config: Harness configuration.
        benchmark: Benchmark instance for getting default prompt.
        verbosity: Verbosity level (0=silent, 1=summary, 2=detailed).
        log_file: Optional file handle for writing raw JSON logs.

    Returns:
        Configured AgentHarness without MCP server.
    """
    # Use custom prompt if provided, otherwise use benchmark's default prompt
    prompt = config.agent_prompt if config.agent_prompt else benchmark.get_prompt_template()

    return create_harness(
        config.agent_harness,
        model=config.model,
        mcp_server=None,
        prompt=prompt,
        max_iterations=config.max_iterations,
        verbosity=verbosity,
        log_file=log_file,
    )


async def run_single_task(
    task: dict[str, Any],
    config: HarnessConfig,
    docker_manager: DockerEnvironmentManager,
    benchmark: Benchmark,
    run_mcp: bool,
    run_baseline: bool,
    verbose: bool,
    verbosity: int = 1,
    log_file: TextIO | None = None,
    log_dir: Path | None = None,
    cache: ResultCache | None = None,
) -> TaskResult:
    """Run evaluation for a single task.

    Args:
        task: Task dictionary from benchmark.
        config: Harness configuration.
        docker_manager: Docker environment manager.
        benchmark: Benchmark instance for evaluation.
        run_mcp: Whether to run MCP evaluation.
        run_baseline: Whether to run baseline evaluation.
        verbose: Enable verbose output.
        verbosity: Verbosity level (0=silent, 1=summary, 2=detailed).
        log_file: Optional file handle for writing raw JSON logs.
        log_dir: Optional directory for per-instance JSON log files.
        cache: Optional result cache.

    Returns:
        TaskResult with results for both runs.
    """
    instance_id = task["instance_id"]
    result = TaskResult(instance_id=instance_id)

    if run_mcp:
        mcp_log_writer: InstanceLogWriter | None = None
        if log_dir:
            mcp_log_writer = InstanceLogWriter(log_dir, instance_id, "mcp")
        try:
            result.mcp = await _run_mcp_evaluation(
                task,
                config,
                docker_manager,
                benchmark,
                verbose,
                verbosity,
                mcp_log_writer if mcp_log_writer else log_file,
                cache,
            )
        finally:
            if mcp_log_writer:
                mcp_log_writer.close()

    if run_baseline:
        baseline_log_writer: InstanceLogWriter | None = None
        if log_dir:
            baseline_log_writer = InstanceLogWriter(log_dir, instance_id, "baseline")
        try:
            result.baseline = await _run_baseline_evaluation(
                task,
                config,
                docker_manager,
                benchmark,
                verbose,
                verbosity,
                baseline_log_writer if baseline_log_writer else log_file,
                cache,
            )
        finally:
            if baseline_log_writer:
                baseline_log_writer.close()

    return result


async def _run_mcp_evaluation(
    task: dict[str, Any],
    config: HarnessConfig,
    docker_manager: DockerEnvironmentManager,
    benchmark: Benchmark,
    verbose: bool,
    verbosity: int = 1,
    log_file: TextIO | InstanceLogWriter | None = None,
    cache: ResultCache | None = None,
) -> dict[str, Any]:
    """Run MCP agent evaluation with optional caching.

    Args:
        task: Task dictionary from benchmark.
        config: Harness configuration.
        docker_manager: Docker environment manager.
        benchmark: Benchmark instance.
        verbose: Enable verbose output.
        verbosity: Verbosity level.
        log_file: Optional log file writer.
        cache: Optional result cache.

    Returns:
        Dictionary with evaluation results.
    """
    # Check cache first if enabled
    if cache and cache.enabled:
        prompt = config.agent_prompt if config.agent_prompt else benchmark.get_prompt_template()
        cached_result = cache.get(task, config, prompt, is_mcp=True)
        if cached_result is not None:
            # Add cache hit marker to result
            cached_result["cache_hit"] = True
            return cached_result

    env: TaskEnvironment | None = None
    try:
        env = await benchmark.create_environment(task, docker_manager)

        agent = _create_mcp_agent(config, benchmark, verbosity, log_file)

        instance_id = task.get(
            "instance_id", f"{task.get('project', 'unknown')}_{task.get('bug_id', 'unknown')}"
        )
        agent_result = await asyncio.wait_for(
            agent.solve(
                task,
                env.host_workdir,
                timeout=config.timeout_seconds,
                verbose=verbose,
                task_id=f"{instance_id}:mcp",
                env=env,  # Pass Docker environment for in-container execution
            ),
            timeout=config.timeout_seconds + 60,
        )

        if agent_result.patch:
            eval_result_dict = await benchmark.evaluate(env, task, agent_result.patch)
            # Convert benchmark result format to EvaluationResult-like object
            eval_result = dict_to_namespace(eval_result_dict)
        else:
            eval_result = None

        result = agent_result_to_dict(agent_result, eval_result, config.model)

        # Store in cache if enabled
        if cache and cache.enabled:
            prompt = config.agent_prompt if config.agent_prompt else benchmark.get_prompt_template()
            cache.put(task, config, prompt, is_mcp=True, result=result)

        return result

    except asyncio.TimeoutError:
        cost = calculate_cost(config.model, 0, 0)
        return {
            "resolved": False,
            "patch_applied": False,
            "error": "Timeout",
            "tokens": {"input": 0, "output": 0},
            "iterations": 0,
            "tool_calls": 0,
            "cost": cost if cost is not None else 0.0,
        }
    except Exception as e:
        cost = calculate_cost(config.model, 0, 0)
        return {
            "resolved": False,
            "patch_applied": False,
            "error": str(e),
            "tokens": {"input": 0, "output": 0},
            "iterations": 0,
            "tool_calls": 0,
            "cost": cost if cost is not None else 0.0,
        }
    finally:
        if env:
            await env.cleanup()


async def _run_baseline_evaluation(
    task: dict[str, Any],
    config: HarnessConfig,
    docker_manager: DockerEnvironmentManager,
    benchmark: Benchmark,
    verbose: bool,
    verbosity: int = 1,
    log_file: TextIO | InstanceLogWriter | None = None,
    cache: ResultCache | None = None,
) -> dict[str, Any]:
    """Run baseline agent evaluation (same harness as MCP, but without MCP server).

    Args:
        task: Task dictionary from benchmark.
        config: Harness configuration.
        docker_manager: Docker environment manager.
        benchmark: Benchmark instance.
        verbose: Enable verbose output.
        verbosity: Verbosity level.
        log_file: Optional log file writer.
        cache: Optional result cache.

    Returns:
        Dictionary with evaluation results.
    """
    # Check cache first if enabled
    if cache and cache.enabled:
        prompt = config.agent_prompt if config.agent_prompt else benchmark.get_prompt_template()
        cached_result = cache.get(task, config, prompt, is_mcp=False)
        if cached_result is not None:
            # Add cache hit marker to result
            cached_result["cache_hit"] = True
            return cached_result

    env: TaskEnvironment | None = None
    try:
        env = await benchmark.create_environment(task, docker_manager)

        agent = _create_baseline_agent(config, benchmark, verbosity, log_file)

        instance_id = task.get(
            "instance_id", f"{task.get('project', 'unknown')}_{task.get('bug_id', 'unknown')}"
        )
        agent_result = await asyncio.wait_for(
            agent.solve(
                task,
                env.host_workdir,
                timeout=config.timeout_seconds,
                verbose=verbose,
                task_id=f"{instance_id}:baseline",
                env=env,  # Pass Docker environment for in-container execution
            ),
            timeout=config.timeout_seconds + 60,
        )

        if agent_result.patch:
            eval_result_dict = await benchmark.evaluate(env, task, agent_result.patch)
            # Convert benchmark result format to EvaluationResult-like object
            eval_result = dict_to_namespace(eval_result_dict)
        else:
            eval_result = None

        result = agent_result_to_dict(agent_result, eval_result, config.model)

        # Store in cache if enabled
        if cache and cache.enabled:
            prompt = config.agent_prompt if config.agent_prompt else benchmark.get_prompt_template()
            cache.put(task, config, prompt, is_mcp=False, result=result)

        return result

    except asyncio.TimeoutError:
        cost = calculate_cost(config.model, 0, 0)
        return {
            "resolved": False,
            "patch_applied": False,
            "error": "Timeout",
            "tokens": {"input": 0, "output": 0},
            "iterations": 0,
            "tool_calls": 0,
            "cost": cost if cost is not None else 0.0,
        }
    except Exception as e:
        cost = calculate_cost(config.model, 0, 0)
        return {
            "resolved": False,
            "patch_applied": False,
            "error": str(e),
            "tokens": {"input": 0, "output": 0},
            "iterations": 0,
            "tool_calls": 0,
            "cost": cost if cost is not None else 0.0,
        }
    finally:
        if env:
            await env.cleanup()


async def run_evaluation(
    config: HarnessConfig,
    run_mcp: bool = True,
    run_baseline: bool = True,
    verbose: bool = False,
    verbosity: int = 1,
    log_file: TextIO | None = None,
    log_dir: Path | None = None,
    task_ids: list[str] | None = None,
    state_tracker: Any | None = None,
    from_task: str | None = None,
    incremental_save_path: Path | None = None,
) -> EvaluationResults:
    """Run the full evaluation.

    Args:
        config: Harness configuration.
        run_mcp: Whether to run MCP evaluation.
        run_baseline: Whether to run baseline evaluation.
        verbose: Enable verbose output.
        verbosity: Verbosity level (0=silent, 1=summary, 2=detailed).
        log_file: Optional file handle for writing raw JSON logs.
        log_dir: Optional directory for per-instance JSON log files.
        task_ids: Specific task IDs to run (None for all).
        state_tracker: Optional state tracker for incremental evaluation.
        from_task: Optional task ID to resume from.
        incremental_save_path: Optional path to save results incrementally for crash recovery.

    Returns:
        EvaluationResults with all results.
    """
    # Create benchmark instance
    benchmark_kwargs: dict[str, Any] = {}
    if config.dataset:
        # Only pass dataset if it's compatible with the benchmark
        # CyberGym should use its own default dataset, not SWE-bench
        if config.benchmark == "cybergym" and "cybergym" not in config.dataset.lower():
            pass  # Don't pass SWE-bench dataset to CyberGym
        else:
            benchmark_kwargs["dataset"] = config.dataset
    if config.benchmark == "cybergym":
        benchmark_kwargs["level"] = config.cybergym_level

    benchmark = create_benchmark(config.benchmark, **benchmark_kwargs)

    # Determine which dataset we're actually using (benchmark's dataset, not config)
    dataset_name = getattr(benchmark, "dataset", config.dataset or "unknown")
    console.print(f"[dim]Loading dataset: {dataset_name}[/dim]")

    # Load tasks from benchmark
    tasks = benchmark.load_tasks(
        sample_size=config.sample_size,
        task_ids=task_ids,
    )

    # Apply state tracker filtering and resume logic
    tasks_to_run = tasks
    skipped_count = 0
    resumed_from = False

    if state_tracker:
        from .state_tracker import compute_task_hash

        # Load existing results from state
        state_tracker.load_state()

        # Handle from_task: find starting position and run from there
        if from_task:
            found = False
            for i, task in enumerate(tasks):
                if task["instance_id"] == from_task:
                    tasks_to_run = tasks[i:]
                    skipped_count = i
                    resumed_from = True
                    found = True
                    break
            if not found:
                console.print(
                    f"[yellow]Warning: Task {from_task} not found, running all tasks[/yellow]"
                )
        else:
            # Filter out already completed tasks (if config unchanged)
            filtered_tasks = []
            cached_results: list[TaskResult] = []

            for task in tasks:
                instance_id = task["instance_id"]
                task_hash = compute_task_hash(task)

                if state_tracker.is_task_completed(instance_id, task_hash):
                    # Task is completed and unchanged, use cached result
                    cached_state = state_tracker.get_task_result(instance_id)
                    if cached_state:
                        cached_results.append(
                            TaskResult(
                                instance_id=instance_id,
                                mcp=cached_state.mcp_result,
                                baseline=cached_state.baseline_result,
                            )
                        )
                        skipped_count += 1
                else:
                    filtered_tasks.append(task)

            tasks_to_run = filtered_tasks

        if skipped_count > 0:
            if resumed_from:
                console.print(
                    f"[cyan]Resuming from task {from_task}, skipping {skipped_count} tasks[/cyan]"
                )
            else:
                console.print(
                    f"[cyan]Using cached results for {skipped_count} completed tasks[/cyan]"
                )

    console.print(f"[dim]Evaluating {len(tasks_to_run)} tasks[/dim]")
    console.print(f"[dim]Provider: {config.provider}, Harness: {config.agent_harness}[/dim]")

    # Initialize cache if enabled
    cache: ResultCache | None = None
    if config.cache_enabled:
        cache = ResultCache(cache_dir=config.cache_dir, enabled=True)
        console.print(f"[dim]Cache enabled: {cache.cache_dir}[/dim]")

    # Prepare metadata for incremental saves
    metadata_for_save = None
    if incremental_save_path:
        metadata_for_save = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "config": {
                "provider": config.provider,
                "agent_harness": config.agent_harness,
                "model": config.model,
                "dataset": dataset_name,
                "sample_size": len(tasks),
                "timeout_seconds": config.timeout_seconds,
                "max_concurrent": config.max_concurrent,
            },
            "mcp_server": {
                "command": config.mcp_server.command,
                "args": config.mcp_server.args,
            },
        }

    docker_manager = DockerEnvironmentManager(use_prebuilt=config.use_prebuilt_images)

    results: list[TaskResult] = []
    # Add cached results if using state tracker
    if state_tracker and "cached_results" in locals():
        results.extend(cached_results)
    semaphore = asyncio.Semaphore(config.max_concurrent)
    budget_exceeded = False
    current_cost = 0.0

    async def run_with_semaphore(task: dict[str, Any]) -> TaskResult | None:
        nonlocal current_cost, budget_exceeded

        # Check budget before running task
        if config.budget and current_cost >= config.budget:
            budget_exceeded = True
            return None

        async with semaphore:
            result = await run_single_task(
                task,
                config,
                docker_manager,
                benchmark,
                run_mcp,
                run_baseline,
                verbose,
                verbosity,
                log_file,
                log_dir,
                cache,
            )

            # Update current cost
            if result.mcp and result.mcp.get("cost"):
                current_cost += result.mcp.get("cost", 0.0)
            if result.baseline and result.baseline.get("cost"):
                current_cost += result.baseline.get("cost", 0.0)

            # Save state after task completion
            if state_tracker:
                from .state_tracker import compute_task_hash

                task_hash = compute_task_hash(task)
                error = None
                if result.mcp and result.mcp.get("error"):
                    error = result.mcp.get("error")
                elif result.baseline and result.baseline.get("error"):
                    error = result.baseline.get("error")

                state_tracker.mark_task_completed(
                    instance_id=result.instance_id,
                    task_hash=task_hash,
                    mcp_result=result.mcp,
                    baseline_result=result.baseline,
                    error=error,
                )
                state_tracker.save_state()

            return result

    progress_console = Console(force_terminal=True)

    async_tasks = []
    for task in tasks_to_run:
        async_tasks.append(run_with_semaphore(task))

    try:
        if verbose:
            for coro in asyncio.as_completed(async_tasks):
                result = await coro
                if result is not None:
                    results.append(result)

                    # Save result incrementally for crash recovery
                    if incremental_save_path:
                        save_task_result_incremental(
                            result, incremental_save_path, metadata_for_save
                        )
                        # Only include metadata on first save
                        metadata_for_save = None

                if budget_exceeded:
                    console.print(
                        f"\n[yellow]Budget limit of ${config.budget:.2f} reached. "
                        f"Stopping evaluation (spent ${current_cost:.4f}).[/yellow]"
                    )
                    break
        else:
            with Progress(
                SpinnerColumn(),
                TextColumn("[progress.description]{task.description}"),
                BarColumn(),
                TaskProgressColumn(),
                console=progress_console,
            ) as progress:
                main_task = progress.add_task(
                    "Evaluating tasks...", total=len(tasks_to_run), completed=0
                )

                for coro in asyncio.as_completed(async_tasks):
                    result = await coro
                    if result is not None:
                        results.append(result)
                        progress.update(main_task, advance=1)

                        # Save result incrementally for crash recovery
                        if incremental_save_path:
                            save_task_result_incremental(
                                result, incremental_save_path, metadata_for_save
                            )
                            # Only include metadata on first save
                            metadata_for_save = None

                    if budget_exceeded:
                        progress.stop()
                        console.print(
                            f"\n[yellow]Budget limit of ${config.budget:.2f} reached. "
                            f"Stopping evaluation (spent ${current_cost:.4f}).[/yellow]"
                        )
                        break
    finally:
        await docker_manager.cleanup_all()

    mcp_resolved = 0
    baseline_resolved = 0
    mcp_total = 0
    baseline_total = 0
    mcp_cost = 0.0
    baseline_cost = 0.0

    for r in results:
        if r.mcp:
            mcp_total += 1
            if r.mcp.get("resolved"):
                mcp_resolved += 1
            if r.mcp.get("cost"):
                mcp_cost += r.mcp.get("cost", 0.0)
        if r.baseline:
            baseline_total += 1
            if r.baseline.get("resolved"):
                baseline_resolved += 1
            if r.baseline.get("cost"):
                baseline_cost += r.baseline.get("cost", 0.0)

    mcp_rate = mcp_resolved / mcp_total if mcp_total > 0 else 0
    baseline_rate = baseline_resolved / baseline_total if baseline_total > 0 else 0

    if baseline_rate > 0:
        improvement = ((mcp_rate - baseline_rate) / baseline_rate) * 100
        improvement_str = f"{improvement:+.1f}%"
    else:
        improvement_str = "N/A"

    # Calculate cost-effectiveness metrics
    from .pricing import calculate_cost_effectiveness
    from .reporting import calculate_tool_coverage

    cost_effectiveness = calculate_cost_effectiveness(
        mcp_cost=mcp_cost,
        baseline_cost=baseline_cost,
        mcp_resolved=mcp_resolved,
        baseline_resolved=baseline_resolved,
    )

    # Create temporary results object for coverage calculation
    temp_results = EvaluationResults(
        metadata={},
        summary={},
        tasks=results,
    )
    tool_coverage = calculate_tool_coverage(temp_results)

    # Build metadata with incremental evaluation stats
    metadata = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "config": {
            "model": config.model,
            "provider": config.provider,
            "agent_harness": config.agent_harness,
            "benchmark": config.benchmark,
            "dataset": dataset_name,
            "sample_size": config.sample_size,
            "timeout_seconds": config.timeout_seconds,
            "max_iterations": config.max_iterations,
            "cybergym_level": config.cybergym_level if config.benchmark == "cybergym" else None,
            "budget": config.budget,
            "budget_exceeded": budget_exceeded,
        },
        "mcp_server": {
            "command": config.mcp_server.command,
            "args": config.mcp_server.args,
            "args_note": "{workdir} is replaced with task repository path at runtime",
        },
    }

    # Add incremental evaluation stats if state tracker was used
    if state_tracker:
        metadata["incremental"] = {
            "enabled": True,
            "total_tasks": len(tasks),
            "cached_tasks": skipped_count,
            "evaluated_tasks": len(tasks_to_run),
            "resumed_from": from_task if from_task and resumed_from else None,
        }
    else:
        metadata["incremental"] = {
            "enabled": False,
        }

    return EvaluationResults(
        metadata=metadata,
        summary={
            "mcp": {
                "resolved": mcp_resolved,
                "total": mcp_total,
                "rate": mcp_rate,
                "total_cost": mcp_cost,
                "cost_per_task": mcp_cost / mcp_total if mcp_total > 0 else 0.0,
                "cost_per_resolved": cost_effectiveness["mcp_cost_per_resolved"],
            },
            "baseline": {
                "resolved": baseline_resolved,
                "total": baseline_total,
                "rate": baseline_rate,
                "total_cost": baseline_cost,
                "cost_per_task": baseline_cost / baseline_total if baseline_total > 0 else 0.0,
                "cost_per_resolved": cost_effectiveness["baseline_cost_per_resolved"],
            },
            "improvement": improvement_str,
            "cost_comparison": {
                "total_difference": mcp_cost - baseline_cost,
                "cost_per_additional_resolution": cost_effectiveness[
                    "cost_per_additional_resolution"
                ],
            },
            "tool_coverage": tool_coverage,
        },
        tasks=results,
    )
