# -*- coding: utf-8 -*-
"""Location: ./mcp-servers/python/mcp_eval_server/mcp_eval_server/tools/agent_tools.py
Copyright 2025
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti

MCP tools for agent evaluation.
"""

# Standard
import re
import secrets
import statistics
from typing import Any, Dict, List, Optional

# Local
from .judge_tools import JudgeTools


class AgentTools:
    """Tools for agent performance evaluation."""

    def __init__(self, judge_tools: Optional[JudgeTools] = None):
        """Initialize agent tools.

        Args:
            judge_tools: Judge tools instance for LLM evaluations
        """
        self.judge_tools = judge_tools or JudgeTools()

    async def evaluate_tool_use(
        self,
        agent_trace: Dict[str, Any],
        expected_tools: List[str],
        tool_sequence_matters: bool = False,
        allow_extra_tools: bool = True,
        judge_model: str = "gpt-4",
    ) -> Dict[str, Any]:
        """Assess agent's tool selection and usage.

        Args:
            agent_trace: Complete execution trace with tool calls
            expected_tools: Tools that should be used
            tool_sequence_matters: Whether order is important
            allow_extra_tools: Permit additional tool calls
            judge_model: Judge model for evaluation

        Returns:
            Tool usage evaluation result
        """
        # Extract tool calls from trace
        tool_calls = self._extract_tool_calls(agent_trace)

        # Analyze tool selection
        used_tools = [call["tool_name"] for call in tool_calls]
        expected_set = set(expected_tools)
        used_set = set(used_tools)

        # Calculate accuracy metrics
        correct_tools = expected_set & used_set
        missing_tools = expected_set - used_set
        extra_tools = used_set - expected_set

        tool_accuracy = len(correct_tools) / len(expected_set) if expected_set else 1.0

        # Sequence analysis
        sequence_score = 1.0
        if tool_sequence_matters and len(expected_tools) > 1:
            sequence_score = self._evaluate_tool_sequence(used_tools, expected_tools)

        # Efficiency analysis
        efficiency_score = self._evaluate_tool_efficiency(tool_calls, expected_tools)

        # Parameter accuracy
        parameter_accuracy = await self._evaluate_parameters(tool_calls, judge_model)

        # Error handling analysis
        error_handling = self._analyze_error_handling(agent_trace)

        # Overall score
        weights = {"accuracy": 0.3, "sequence": 0.2 if tool_sequence_matters else 0.0, "efficiency": 0.25, "parameters": 0.25, "error_handling": 0.1 if not tool_sequence_matters else 0.1}

        # Normalize weights
        total_weight = sum(weights.values())
        for key in weights:
            weights[key] /= total_weight

        overall_score = (
            tool_accuracy * weights["accuracy"]
            + sequence_score * weights["sequence"]
            + efficiency_score * weights["efficiency"]
            + parameter_accuracy * weights["parameters"]
            + error_handling["score"] * weights["error_handling"]
        )

        return {
            "tool_accuracy": tool_accuracy,
            "sequence_score": sequence_score,
            "efficiency_score": efficiency_score,
            "parameter_accuracy": parameter_accuracy,
            "error_handling": error_handling,
            "overall_score": overall_score,
            "analysis": {
                "correct_tools": list(correct_tools),
                "missing_tools": list(missing_tools),
                "extra_tools": list(extra_tools),
                "tool_calls": tool_calls,
                "expected_tools": expected_tools,
                "sequence_matters": tool_sequence_matters,
                "allow_extra": allow_extra_tools,
            },
            "recommendations": self._generate_tool_recommendations(missing_tools, extra_tools, efficiency_score, parameter_accuracy),
        }

    def _extract_tool_calls(self, agent_trace: Dict[str, Any]) -> List[Dict[str, Any]]:
        """Extract tool calls from agent execution trace.

        Args:
            agent_trace: Complete execution trace with tool calls

        Returns:
            List of formatted tool call dictionaries
        """
        tool_calls = []

        # Handle different trace formats
        if "tool_calls" in agent_trace:
            return agent_trace["tool_calls"]

        if "steps" in agent_trace:
            for step in agent_trace["steps"]:
                if "tool_call" in step:
                    tool_calls.append(step["tool_call"])
                elif "action" in step and step["action"].get("type") == "tool_call":
                    tool_calls.append(step["action"])

        if "actions" in agent_trace:
            for action in agent_trace["actions"]:
                if action.get("type") == "tool_call":
                    tool_calls.append(action)

        # Ensure consistent format
        formatted_calls = []
        for call in tool_calls:
            formatted_call = {
                "tool_name": call.get("tool_name", call.get("name", "unknown")),
                "parameters": call.get("parameters", call.get("args", {})),
                "result": call.get("result", call.get("output")),
                "success": call.get("success", call.get("result") is not None),
                "timestamp": call.get("timestamp"),
                "duration": call.get("duration"),
            }
            formatted_calls.append(formatted_call)

        return formatted_calls

    def _evaluate_tool_sequence(self, used_tools: List[str], expected_tools: List[str]) -> float:
        """Evaluate correctness of tool usage sequence.

        Args:
            used_tools: List of tools used by the agent
            expected_tools: Expected sequence of tools

        Returns:
            Float score between 0.0 and 1.0 for sequence accuracy
        """
        if not expected_tools or len(expected_tools) <= 1:
            return 1.0

        # Find longest common subsequence
        def lcs_length(seq1, seq2):
            m, n = len(seq1), len(seq2)
            dp = [[0] * (n + 1) for _ in range(m + 1)]

            for i in range(1, m + 1):
                for j in range(1, n + 1):
                    if seq1[i - 1] == seq2[j - 1]:
                        dp[i][j] = dp[i - 1][j - 1] + 1
                    else:
                        dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

            return dp[m][n]

        lcs_len = lcs_length(used_tools, expected_tools)
        sequence_score = lcs_len / len(expected_tools)

        return sequence_score

    def _evaluate_tool_efficiency(self, tool_calls: List[Dict[str, Any]], expected_tools: List[str]) -> float:
        """Evaluate efficiency of tool usage.

        Args:
            tool_calls: List of tool call dictionaries
            expected_tools: Expected tools that should be used

        Returns:
            Float score between 0.0 and 1.0 for tool usage efficiency
        """
        if not tool_calls:
            return 0.0

        # Basic efficiency: minimize unnecessary calls
        total_calls = len(tool_calls)
        expected_calls = len(expected_tools)

        if expected_calls == 0:
            return 1.0 if total_calls == 0 else 0.5

        # Penalty for excessive calls
        efficiency = expected_calls / total_calls if total_calls > 0 else 0.0

        # Bonus for successful calls
        successful_calls = sum(1 for call in tool_calls if call.get("success", False))
        success_rate = successful_calls / total_calls

        # Combined efficiency score
        efficiency_score = (efficiency * 0.7) + (success_rate * 0.3)

        return min(1.0, efficiency_score)

    async def _evaluate_parameters(self, tool_calls: List[Dict[str, Any]], judge_model: str) -> float:  # pylint: disable=unused-argument
        """Evaluate correctness of tool parameters.

        Args:
            tool_calls: List of tool call dictionaries with parameters
            judge_model: Judge model for evaluation (currently unused)

        Returns:
            Float score between 0.0 and 1.0 for parameter correctness
        """
        if not tool_calls:
            return 1.0

        total_score = 0.0
        evaluated_calls = 0

        for call in tool_calls:
            parameters = call.get("parameters", {})

            # Basic parameter validation
            param_score = 1.0

            # Check for required parameters (simplified heuristic)
            if not parameters:
                param_score = 0.5
            else:
                # Check for common parameter issues
                for _key, value in parameters.items():
                    if value is None or value == "":
                        param_score -= 0.2
                    elif isinstance(value, str) and len(value) < 2:
                        param_score -= 0.1

            param_score = max(0.0, param_score)
            total_score += param_score
            evaluated_calls += 1

        return total_score / evaluated_calls if evaluated_calls > 0 else 1.0

    def _analyze_error_handling(self, agent_trace: Dict[str, Any]) -> Dict[str, Any]:
        """Analyze agent's error handling capabilities.

        Args:
            agent_trace: Complete execution trace containing error information.

        Returns:
            Dict[str, Any]: Error handling analysis including score, recovery rate, and details.
        """
        error_events = []
        recovery_attempts = []

        # Extract errors from trace
        if "errors" in agent_trace:
            error_events = agent_trace["errors"]

        if "steps" in agent_trace:
            for step in agent_trace["steps"]:
                if step.get("status") == "error" or "error" in step:
                    error_events.append(step)
                if "retry" in step or "recovery" in step:
                    recovery_attempts.append(step)

        total_errors = len(error_events)
        recovered_errors = len(recovery_attempts)

        if total_errors == 0:
            error_score = 1.0
            handling_quality = "No errors encountered"
        else:
            recovery_rate = recovered_errors / total_errors
            error_score = recovery_rate * 0.8 + 0.2  # Base score for encountering errors

            if recovery_rate > 0.8:
                handling_quality = "Excellent error recovery"
            elif recovery_rate > 0.5:
                handling_quality = "Good error recovery"
            elif recovery_rate > 0.2:
                handling_quality = "Basic error recovery"
            else:
                handling_quality = "Poor error recovery"

        return {
            "score": error_score,
            "total_errors": total_errors,
            "recovered_errors": recovered_errors,
            "recovery_rate": recovered_errors / total_errors if total_errors > 0 else 1.0,
            "quality": handling_quality,
            "error_details": error_events[:5],  # Limit to first 5 errors
        }

    async def measure_task_completion(
        self,
        task_description: str,  # pylint: disable=unused-argument
        success_criteria: List[Dict[str, Any]],
        agent_trace: Dict[str, Any],
        final_state: Optional[Dict[str, Any]] = None,
        judge_model: str = "gpt-4",
    ) -> Dict[str, Any]:
        """Evaluate end-to-end task success.

        Args:
            task_description: What the agent should accomplish
            success_criteria: Measurable outcomes
            agent_trace: Execution history
            final_state: System state after execution
            judge_model: Judge model for evaluation

        Returns:
            Task completion evaluation result
        """
        # Evaluate each success criterion
        criteria_results = []
        total_weight = sum(criterion.get("weight", 1.0) for criterion in success_criteria)

        for criterion in success_criteria:
            criterion_result = await self._evaluate_success_criterion(criterion, agent_trace, final_state, judge_model)
            criteria_results.append(criterion_result)

        # Calculate overall completion rate
        weighted_score = sum(result["score"] * result.get("weight", 1.0) for result in criteria_results)
        completion_rate = weighted_score / total_weight if total_weight > 0 else 0.0

        # Identify which criteria were met
        met_criteria = [result for result in criteria_results if result["met"]]
        failed_criteria = [result for result in criteria_results if not result["met"]]

        # Analyze partial credit
        partial_credit = sum(result["score"] for result in failed_criteria if result["score"] > 0)

        # Generate failure analysis
        failure_analysis = self._analyze_task_failures(failed_criteria, agent_trace)

        return {
            "completion_rate": completion_rate,
            "criteria_met": len(met_criteria),
            "total_criteria": len(success_criteria),
            "criteria_results": criteria_results,
            "partial_credit": partial_credit,
            "failure_analysis": failure_analysis,
            "recommendations": self._generate_completion_recommendations(failed_criteria, completion_rate),
        }

    async def _evaluate_success_criterion(self, criterion: Dict[str, Any], agent_trace: Dict[str, Any], final_state: Optional[Dict[str, Any]], judge_model: str) -> Dict[str, Any]:
        """Evaluate a single success criterion.

        Args:
            criterion: Success criterion definition with type, name, weight, and threshold.
            agent_trace: Complete execution trace.
            final_state: Optional final system state after execution.
            judge_model: Judge model for evaluation.

        Returns:
            Dict[str, Any]: Criterion evaluation result with score, met status, and details.
        """

        criterion_type = criterion.get("type", "output_check")
        criterion_name = criterion.get("name", "unnamed")
        weight = criterion.get("weight", 1.0)

        if criterion_type == "output_check":
            score, met = await self._check_output_criterion(criterion, agent_trace, judge_model)
        elif criterion_type == "state_check":
            score, met = self._check_state_criterion(criterion, final_state)
        elif criterion_type == "process_check":
            score, met = self._check_process_criterion(criterion, agent_trace)
        else:
            score, met = 0.0, False

        return {"name": criterion_name, "type": criterion_type, "score": score, "met": met, "weight": weight, "details": criterion.get("details", ""), "threshold": criterion.get("threshold", 0.8)}

    async def _check_output_criterion(self, criterion: Dict[str, Any], agent_trace: Dict[str, Any], judge_model: str) -> tuple[float, bool]:
        """Check output-based success criterion.

        Args:
            criterion: Output criterion definition with expected output and threshold.
            agent_trace: Complete execution trace containing final output.
            judge_model: Judge model for output comparison.

        Returns:
            tuple[float, bool]: Score and whether criterion was met.
        """

        expected_output = criterion.get("expected_output", "")
        threshold = criterion.get("threshold", 0.8)

        # Get agent's final output
        final_output = agent_trace.get("final_output", "")
        if not final_output and "steps" in agent_trace:
            # Try to extract from last step
            last_step = agent_trace["steps"][-1] if agent_trace["steps"] else {}
            final_output = last_step.get("output", "")

        if not final_output:
            return 0.0, False

        # Use LLM judge to compare outputs
        result = await self.judge_tools.evaluate_with_reference(response=final_output, reference=expected_output, judge_model=judge_model, evaluation_type="completeness")

        score = result["similarity_score"]
        met = score >= threshold

        return score, met

    def _check_state_criterion(self, criterion: Dict[str, Any], final_state: Optional[Dict[str, Any]]) -> tuple[float, bool]:
        """Check state-based success criterion.

        Args:
            criterion: State criterion definition with expected state and threshold.
            final_state: Optional final system state after execution.

        Returns:
            tuple[float, bool]: Score and whether criterion was met.
        """

        if not final_state:
            return 0.0, False

        expected_state = criterion.get("expected_state", {})
        threshold = criterion.get("threshold", 0.8)

        # Simple state matching
        matches = 0
        total = len(expected_state)

        for key, expected_value in expected_state.items():
            actual_value = final_state.get(key)
            if actual_value == expected_value:
                matches += 1
            elif isinstance(expected_value, (int, float)) and isinstance(actual_value, (int, float)):
                # Numeric tolerance
                if abs(actual_value - expected_value) / max(abs(expected_value), 1) < 0.1:
                    matches += 0.8

        score = matches / total if total > 0 else 1.0
        met = score >= threshold

        return score, met

    def _check_process_criterion(self, criterion: Dict[str, Any], agent_trace: Dict[str, Any]) -> tuple[float, bool]:
        """Check process-based success criterion.

        Args:
            criterion: Process criterion definition with required actions and threshold.
            agent_trace: Complete execution trace containing actions.

        Returns:
            tuple[float, bool]: Score and whether criterion was met.
        """

        required_actions = criterion.get("required_actions", [])
        threshold = criterion.get("threshold", 0.8)

        # Extract actions from trace
        actions = []
        if "actions" in agent_trace:
            actions = agent_trace["actions"]
        elif "steps" in agent_trace:
            actions = [step.get("action", {}) for step in agent_trace["steps"]]

        # Check for required actions
        found_actions = 0
        for required_action in required_actions:
            for action in actions:
                if self._action_matches(action, required_action):
                    found_actions += 1
                    break

        score = found_actions / len(required_actions) if required_actions else 1.0
        met = score >= threshold

        return score, met

    def _action_matches(self, action: Dict[str, Any], required: Dict[str, Any]) -> bool:
        """Check if action matches required action pattern.

        Args:
            action: Actual action performed by the agent.
            required: Required action pattern to match against.

        Returns:
            bool: True if action matches the required pattern, False otherwise.
        """

        action_type = action.get("type", action.get("action_type", ""))
        required_type = required.get("type", required.get("action_type", ""))

        if action_type != required_type:
            return False

        # Check parameters if specified
        if "parameters" in required:
            action_params = action.get("parameters", {})
            for key, value in required["parameters"].items():
                if action_params.get(key) != value:
                    return False

        return True

    async def analyze_reasoning(
        self,
        reasoning_trace: List[Dict[str, Any]],
        decision_points: List[Dict[str, Any]],
        context: Dict[str, Any],
        optimal_path: Optional[List[str]] = None,
        judge_model: str = "gpt-4",
    ) -> Dict[str, Any]:
        """Evaluate agent's decision-making process.

        Args:
            reasoning_trace: Agent's thought process
            decision_points: Key choices made
            context: Available information
            optimal_path: Best possible approach
            judge_model: Judge model for evaluation

        Returns:
            Reasoning analysis result
        """
        # Analyze reasoning quality
        reasoning_quality = await self._evaluate_reasoning_quality(reasoning_trace, judge_model)

        # Analyze decision accuracy
        decision_accuracy = self._evaluate_decision_accuracy(decision_points, optimal_path)

        # Evaluate efficiency
        efficiency = self._evaluate_reasoning_efficiency(reasoning_trace, optimal_path)

        # Detect hallucinations
        hallucination_analysis = await self._detect_hallucinations(reasoning_trace, context, judge_model)

        return {
            "reasoning_quality": reasoning_quality,
            "decision_accuracy": decision_accuracy,
            "efficiency": efficiency,
            "hallucination_detection": hallucination_analysis,
            "overall_reasoning_score": (reasoning_quality * 0.4 + decision_accuracy * 0.3 + efficiency * 0.2 + (1.0 - hallucination_analysis["hallucination_rate"]) * 0.1),
            "recommendations": self._generate_reasoning_recommendations(reasoning_quality, decision_accuracy, efficiency, hallucination_analysis),
        }

    async def _evaluate_reasoning_quality(self, reasoning_trace: List[Dict[str, Any]], judge_model: str) -> float:
        """Evaluate quality of reasoning using LLM judge.

        Args:
            reasoning_trace: List of reasoning steps with thoughts and rationale.
            judge_model: Judge model for evaluation.

        Returns:
            float: Reasoning quality score between 0.0 and 1.0.
        """

        if not reasoning_trace:
            return 0.0

        # Combine reasoning steps into coherent text
        reasoning_text = "\n".join([step.get("thought", step.get("reasoning", str(step))) for step in reasoning_trace])

        criteria = [{"name": "logical_coherence", "description": "Logical flow and consistency of reasoning", "scale": "1-5", "weight": 1.0}]

        rubric = {
            "criteria": criteria,
            "scale_description": {
                "1": "Illogical, incoherent reasoning",
                "2": "Some logical flaws, weak connections",
                "3": "Generally logical with minor issues",
                "4": "Good logical flow with clear connections",
                "5": "Excellent logical reasoning, very coherent",
            },
        }

        result = await self.judge_tools.evaluate_response(response=reasoning_text, criteria=criteria, rubric=rubric, judge_model=judge_model)

        return result["overall_score"] / 5.0  # Normalize to 0-1

    def _evaluate_decision_accuracy(self, decision_points: List[Dict[str, Any]], optimal_path: Optional[List[str]] = None) -> float:
        """Evaluate accuracy of decisions made.

        Args:
            decision_points: List of key decision points made by the agent.
            optimal_path: Optional list of optimal decisions for comparison.

        Returns:
            float: Decision accuracy score between 0.0 and 1.0.
        """

        if not decision_points:
            return 1.0

        if not optimal_path:
            # Without optimal path, use heuristics
            correct_decisions = 0
            for decision in decision_points:
                # Simple heuristic: decisions that led to progress are good
                if decision.get("outcome", "success") == "success":
                    correct_decisions += 1

            return correct_decisions / len(decision_points)

        # Compare against optimal path
        decision_choices = [d.get("choice", d.get("action", "")) for d in decision_points]
        matching_decisions = sum(1 for choice, optimal in zip(decision_choices, optimal_path) if choice == optimal)

        return matching_decisions / len(decision_points)

    def _evaluate_reasoning_efficiency(self, reasoning_trace: List[Dict[str, Any]], optimal_path: Optional[List[str]] = None) -> float:
        """Evaluate efficiency of reasoning process.

        Args:
            reasoning_trace: List of reasoning steps taken by the agent.
            optimal_path: Optional list of optimal reasoning steps for comparison.

        Returns:
            float: Reasoning efficiency score between 0.0 and 1.0.
        """

        if not reasoning_trace:
            return 1.0

        # Basic efficiency: fewer steps is better (up to a point)
        step_count = len(reasoning_trace)

        if optimal_path:
            optimal_steps = len(optimal_path)
            if optimal_steps > 0:
                efficiency = min(1.0, optimal_steps / step_count)
            else:
                efficiency = 1.0 / step_count if step_count > 0 else 1.0
        else:
            # Heuristic: 3-7 steps is reasonable for most tasks
            if 3 <= step_count <= 7:
                efficiency = 1.0
            elif step_count < 3:
                efficiency = 0.7  # Too few steps might miss important considerations
            else:
                efficiency = max(0.3, 7 / step_count)  # Too many steps is inefficient

        return efficiency

    async def _detect_hallucinations(self, reasoning_trace: List[Dict[str, Any]], context: Dict[str, Any], judge_model: str) -> Dict[str, Any]:  # pylint: disable=unused-argument
        """Detect hallucinations in reasoning.

        Args:
            reasoning_trace: List of reasoning steps to analyze for hallucinations.
            context: Available context information to check claims against.
            judge_model: Judge model to use for hallucination detection.

        Returns:
            Dict[str, Any]: Hallucination analysis including rate, claims, and severity.
        """

        hallucinations = []
        total_claims = 0

        for step in reasoning_trace:
            reasoning_text = step.get("thought", step.get("reasoning", ""))

            # Simple pattern matching for factual claims
            # Standard
            claims = re.findall(r"(.*(?:is|are|was|were|will be|has|have).+?[.!?])", reasoning_text)
            total_claims += len(claims)

            # Check claims against context (simplified)
            for claim in claims:
                if not self._claim_supported_by_context(claim, context):
                    hallucinations.append({"step": reasoning_trace.index(step), "claim": claim.strip(), "type": "unsupported_fact"})

        hallucination_rate = len(hallucinations) / max(1, total_claims)

        return {
            "hallucination_rate": hallucination_rate,
            "total_claims": total_claims,
            "hallucinations": hallucinations[:5],  # Limit to first 5
            "severity": "high" if hallucination_rate > 0.3 else "medium" if hallucination_rate > 0.1 else "low",
        }

    def _claim_supported_by_context(self, claim: str, context: Dict[str, Any]) -> bool:
        """Check if claim is supported by available context (simplified).

        Args:
            claim: Text claim to verify against context.
            context: Available context information.

        Returns:
            bool: True if claim is supported by context, False otherwise.
        """

        claim_lower = claim.lower()
        context_text = " ".join(str(v) for v in context.values()).lower()

        # Simple keyword overlap check
        claim_words = set(claim_lower.split())
        context_words = set(context_text.split())

        overlap = len(claim_words & context_words)
        support_ratio = overlap / len(claim_words) if claim_words else 0

        return support_ratio > 0.3  # At least 30% word overlap

    def _generate_tool_recommendations(self, missing_tools: set, extra_tools: set, efficiency_score: float, parameter_accuracy: float) -> List[str]:
        """Generate recommendations for tool usage improvement.

        Args:
            missing_tools: Set of tools that should have been used but weren't.
            extra_tools: Set of tools that were used but weren't necessary.
            efficiency_score: Tool usage efficiency score.
            parameter_accuracy: Parameter correctness score.

        Returns:
            List[str]: List of recommendation messages.
        """
        recommendations = []

        if missing_tools:
            recommendations.append(f"Consider using these missing tools: {', '.join(missing_tools)}")

        if extra_tools:
            recommendations.append(f"Avoid unnecessary tools: {', '.join(extra_tools)}")

        if efficiency_score < 0.7:
            recommendations.append("Reduce redundant tool calls for better efficiency")

        if parameter_accuracy < 0.7:
            recommendations.append("Improve parameter validation and formatting")

        return recommendations

    def _generate_completion_recommendations(self, failed_criteria: List[Dict[str, Any]], completion_rate: float) -> List[str]:
        """Generate recommendations for task completion improvement.

        Args:
            failed_criteria: List of criteria that were not met.
            completion_rate: Overall task completion rate.

        Returns:
            List[str]: List of recommendation messages.
        """
        recommendations = []

        if completion_rate < 0.5:
            recommendations.append("Task completion is low - review core functionality")

        for criterion in failed_criteria:
            if criterion["type"] == "output_check":
                recommendations.append(f"Improve output quality for: {criterion['name']}")
            elif criterion["type"] == "state_check":
                recommendations.append(f"Ensure proper state management for: {criterion['name']}")
            elif criterion["type"] == "process_check":
                recommendations.append(f"Follow required process for: {criterion['name']}")

        return recommendations

    def _generate_reasoning_recommendations(self, reasoning_quality: float, decision_accuracy: float, efficiency: float, hallucination_analysis: Dict[str, Any]) -> List[str]:
        """Generate recommendations for reasoning improvement.

        Args:
            reasoning_quality: Quality score of the reasoning process.
            decision_accuracy: Accuracy score of decisions made.
            efficiency: Efficiency score of the reasoning process.
            hallucination_analysis: Analysis of hallucinations in reasoning.

        Returns:
            List[str]: List of recommendation messages.
        """
        recommendations = []

        if reasoning_quality < 0.6:
            recommendations.append("Improve logical coherence in reasoning")

        if decision_accuracy < 0.7:
            recommendations.append("Enhance decision-making accuracy")

        if efficiency < 0.6:
            recommendations.append("Optimize reasoning process for better efficiency")

        if hallucination_analysis["hallucination_rate"] > 0.2:
            recommendations.append("Reduce hallucinations by grounding in available context")

        return recommendations

    async def benchmark_performance(
        self,
        benchmark_suite: str,
        agent_config: Dict[str, Any],
        baseline_comparison: Optional[Dict[str, Any]] = None,
        metrics_focus: List[str] = None,
    ) -> Dict[str, Any]:
        """Run comprehensive agent benchmarks.

        Args:
            benchmark_suite: Which tests to run
            agent_config: Agent setup
            baseline_comparison: Compare to other agents
            metrics_focus: Priority metrics

        Returns:
            Benchmark results
        """
        if metrics_focus is None:
            metrics_focus = ["accuracy", "efficiency", "reliability"]

        # Define benchmark tasks based on suite
        benchmark_tasks = self._get_benchmark_tasks(benchmark_suite)

        performance_scores = {}
        detailed_results = []

        for task in benchmark_tasks:
            task_result = await self._run_benchmark_task(task, agent_config)
            detailed_results.append(task_result)

            # Aggregate metrics
            for metric in metrics_focus:
                if metric not in performance_scores:
                    performance_scores[metric] = []
                performance_scores[metric].append(task_result.get(metric, 0.0))

        # Calculate aggregate scores
        aggregate_scores = {}
        for metric, scores in performance_scores.items():
            aggregate_scores[metric] = statistics.mean(scores) if scores else 0.0

        # Compare to baseline if provided
        comparison_results = None
        if baseline_comparison:
            comparison_results = self._compare_to_baseline(aggregate_scores, baseline_comparison)

        # Generate recommendations
        recommendations = self._generate_benchmark_recommendations(aggregate_scores, detailed_results)

        return {
            "performance_scores": aggregate_scores,
            "detailed_results": detailed_results,
            "benchmark_ranking": comparison_results.get("ranking") if comparison_results else None,
            "strengths_weaknesses": self._analyze_strengths_weaknesses(aggregate_scores),
            "improvement_suggestions": recommendations,
            "benchmark_suite": benchmark_suite,
            "total_tasks": len(benchmark_tasks),
            "comparison_baseline": baseline_comparison is not None,
        }

    def _get_benchmark_tasks(self, suite: str) -> List[Dict[str, Any]]:
        """Get benchmark tasks for specified suite.

        Args:
            suite: Name of the benchmark suite (basic, intermediate, advanced).

        Returns:
            List[Dict[str, Any]]: List of benchmark task definitions.
        """

        task_suites = {
            "basic": [
                {"name": "simple_tool_usage", "description": "Basic tool selection and usage", "expected_tools": ["search", "calculate"], "complexity": "low"},
                {"name": "data_retrieval", "description": "Information gathering and synthesis", "expected_tools": ["search", "read"], "complexity": "low"},
            ],
            "intermediate": [
                {"name": "multi_step_planning", "description": "Complex task breakdown and execution", "expected_tools": ["plan", "execute", "verify"], "complexity": "medium"},
                {"name": "error_recovery", "description": "Handling failures and retries", "expected_tools": ["try", "catch", "retry"], "complexity": "medium"},
            ],
            "advanced": [
                {"name": "dynamic_adaptation", "description": "Adapting to changing requirements", "expected_tools": ["analyze", "adapt", "execute"], "complexity": "high"},
                {"name": "creative_problem_solving", "description": "Novel solution generation", "expected_tools": ["brainstorm", "evaluate", "implement"], "complexity": "high"},
            ],
        }

        return task_suites.get(suite, task_suites["basic"])

    async def _run_benchmark_task(self, task: Dict[str, Any], agent_config: Dict[str, Any]) -> Dict[str, Any]:  # pylint: disable=unused-argument
        """Run a single benchmark task.

        Args:
            task: Task definition with expected tools and complexity.
            agent_config: Agent configuration for the benchmark run.

        Returns:
            Dict[str, Any]: Task execution results with accuracy, efficiency, and reliability scores.
        """

        # Simulate task execution (in real implementation, would run actual agent)
        # Generate mock results based on task complexity
        complexity_multiplier = {"low": 0.9, "medium": 0.7, "high": 0.5}
        base_score = complexity_multiplier.get(task.get("complexity", "medium"), 0.7)

        # Add some randomness
        noise = secrets.SystemRandom().uniform(-0.2, 0.2)

        accuracy = max(0.0, min(1.0, base_score + noise))
        efficiency = max(0.0, min(1.0, base_score + secrets.SystemRandom().uniform(-0.1, 0.1)))
        reliability = max(0.0, min(1.0, base_score + secrets.SystemRandom().uniform(-0.15, 0.15)))

        return {
            "task_name": task["name"],
            "accuracy": accuracy,
            "efficiency": efficiency,
            "reliability": reliability,
            "completion_time": secrets.SystemRandom().uniform(1.0, 10.0),
            "tool_calls": len(task.get("expected_tools", [])),
            "success": accuracy > 0.6,
        }

    def _compare_to_baseline(self, scores: Dict[str, float], baseline: Dict[str, Any]) -> Dict[str, Any]:
        """Compare performance to baseline.

        Args:
            scores: Current performance scores by metric.
            baseline: Baseline performance data for comparison.

        Returns:
            Dict[str, Any]: Comparison results including ranking and improvements.
        """

        baseline_scores = baseline.get("scores", {})

        improvements = {}
        overall_improvement = 0.0

        for metric, score in scores.items():
            baseline_score = baseline_scores.get(metric, 0.5)
            improvement = score - baseline_score
            improvements[metric] = improvement
            overall_improvement += improvement

        overall_improvement /= len(scores)

        # Determine ranking (simplified)
        if overall_improvement > 0.1:
            ranking = "Above baseline"
        elif overall_improvement > -0.1:
            ranking = "Similar to baseline"
        else:
            ranking = "Below baseline"

        return {"ranking": ranking, "improvements": improvements, "overall_improvement": overall_improvement, "baseline_name": baseline.get("name", "Unknown")}

    def _analyze_strengths_weaknesses(self, scores: Dict[str, float]) -> Dict[str, Any]:
        """Analyze agent strengths and weaknesses.

        Args:
            scores: Performance scores by metric.

        Returns:
            Dict[str, Any]: Analysis of strengths, weaknesses, and top/bottom metrics.
        """

        sorted_scores = sorted(scores.items(), key=lambda x: x[1], reverse=True)

        strengths = []
        weaknesses = []

        for metric, score in sorted_scores:
            if score > 0.8:
                strengths.append(f"Excellent {metric} ({score:.2f})")
            elif score > 0.6:
                strengths.append(f"Good {metric} ({score:.2f})")
            elif score < 0.4:
                weaknesses.append(f"Poor {metric} ({score:.2f})")
            elif score < 0.6:
                weaknesses.append(f"Below average {metric} ({score:.2f})")

        return {"strengths": strengths, "weaknesses": weaknesses, "top_metric": sorted_scores[0][0] if sorted_scores else None, "bottom_metric": sorted_scores[-1][0] if sorted_scores else None}

    def _generate_benchmark_recommendations(self, scores: Dict[str, float], detailed_results: List[Dict[str, Any]]) -> List[str]:
        """Generate benchmark-based recommendations.

        Args:
            scores: Aggregate performance scores by metric.
            detailed_results: Detailed results for each benchmark task.

        Returns:
            List[str]: List of recommendation messages based on benchmark results.
        """

        recommendations = []

        # Analyze overall performance
        avg_score = statistics.mean(scores.values())
        if avg_score < 0.5:
            recommendations.append("Overall performance needs significant improvement")
        elif avg_score < 0.7:
            recommendations.append("Performance is below average - focus on key weaknesses")

        # Specific metric recommendations
        if scores.get("accuracy", 0) < 0.6:
            recommendations.append("Improve decision-making accuracy")

        if scores.get("efficiency", 0) < 0.6:
            recommendations.append("Optimize task execution efficiency")

        if scores.get("reliability", 0) < 0.6:
            recommendations.append("Enhance system reliability and error handling")

        # Task-specific recommendations
        failed_tasks = [r for r in detailed_results if not r.get("success", False)]
        if len(failed_tasks) > len(detailed_results) * 0.3:
            recommendations.append("Address high task failure rate")

        return recommendations

    def _analyze_task_failures(self, failed_criteria: List[Dict[str, Any]], agent_trace: Dict[str, Any]) -> Dict[str, Any]:  # pylint: disable=unused-argument
        """Analyze why tasks failed.

        Args:
            failed_criteria: List of criteria that were not met.
            agent_trace: Complete execution trace for analysis.

        Returns:
            Dict[str, Any]: Failure analysis with types, issues, and primary failure type.
        """

        failure_types = {}
        common_issues = []

        for criterion in failed_criteria:
            failure_type = criterion["type"]
            if failure_type not in failure_types:
                failure_types[failure_type] = 0
            failure_types[failure_type] += 1

        # Identify common failure patterns
        if failure_types.get("output_check", 0) > 0:
            common_issues.append("Output quality issues")

        if failure_types.get("state_check", 0) > 0:
            common_issues.append("State management problems")

        if failure_types.get("process_check", 0) > 0:
            common_issues.append("Process adherence failures")

        return {
            "failure_types": failure_types,
            "common_issues": common_issues,
            "total_failures": len(failed_criteria),
            "primary_failure_type": max(failure_types.items(), key=lambda x: x[1])[0] if failure_types else None,
        }
