"""Planner for confidence-based task planning."""

from __future__ import annotations

import json
from uuid import uuid4

from promethian.config import PromethianConfig
from promethian.core.types import (
    Plan, PlanStep, Skill, Tool,
    ConfidenceLevel, ExecutionStrategy,
)
from promethian.utils.claude import ClaudeClient
from promethian.utils.logging import get_logger


PLANNING_SYSTEM_PROMPT = """You are a planning assistant for a coding AI agent.

Your job is to analyze a task and create an execution plan. You have access to:
1. Skills - procedural knowledge (guides on HOW to approach problems)
2. Tools - executable functions that perform specific actions
3. Built-in tools: read, write, edit, glob, grep, bash, web_search, web_fetch

## Core Principles

CRITICAL: Skills are GUIDANCE documents that teach you how to solve problems!
- When a skill is provided, READ IT and follow its approach using built-in tools
- Skills show you patterns, code examples, and steps - USE THEM
- You CAN execute code, make web requests, and perform actions via built-in tools

IMPORTANT: Only use direct_response when the task is purely informational AND you already know the answer.
- If skills were retrieved, they were retrieved because they're RELEVANT - use them!
- If the task involves any action (create, build, fetch, scrape, etc.), you MUST plan steps
- If the task requires CURRENT/LIVE data (scores, prices, weather, news), use web_fetch or web_search
- You ALWAYS have access to built-in tools (bash, web_fetch, web_search, etc.) even if no skills/tools from knowledge base

## Planning Guidelines

1. First check: Were skills or tools provided? If yes, plan steps that USE them
2. Skills contain HOW-TO guides - reference them in your steps (e.g., "Following the Web Scraping skill...")
3. Break into logical steps that are concrete and actionable
4. Use built-in tools (bash, write, read, web_fetch) to execute the skill's guidance
5. Keep plans minimal - don't add unnecessary steps

## Code Tasks

For coding tasks:
- Always include a step to read and understand existing code first
- Identify files to modify based on the task
- Don't over-engineer: plan only what's directly requested
- Consider existing patterns in the codebase

For direct responses (ONLY when no skills/tools provided AND purely informational):
{
    "direct_response": true,
    "response": "The answer is...",
    "confidence": 1.0,
    "reasoning": "No skills/tools provided and this is a simple factual question"
}

NEVER use direct_response if:
- Skills or tools were provided (they're relevant - use them!)
- The task contains action words: create, build, scrape, fetch, write, run, etc.
- The user explicitly asks to "use" a skill or tool
- The task requires CURRENT/LIVE data you don't have (scores, prices, weather, stock, news)
  → Use web_fetch to get current data from URLs, or web_search to find sources

For tasks requiring tools:
{
    "direct_response": false,
    "steps": [
        {
            "id": "step_1",
            "description": "What this step accomplishes",
            "expected_output": "What we expect to produce",
            "skills_to_use": ["skill_id_1"],
            "tools_to_use": ["tool_id_1"],
            "tools_to_build": ["Description of new tool if needed"],
            "dependencies": []
        }
    ],
    "confidence": 0.85,
    "reasoning": "Why this confidence level",
    "needs_new_tools": false
}"""


class Planner:
    """
    Creates execution plans with confidence assessment.

    The planner retrieves relevant skills and tools, then asks Claude
    to create a plan with confidence scoring.
    """

    def __init__(
        self,
        config: PromethianConfig,
        claude: ClaudeClient,
    ):
        self.config = config
        self.claude = claude
        self._logger = get_logger()

    async def create_plan(
        self,
        task: str,
        context: str | None = None,
        skills: list[Skill] | None = None,
        tools: list[Tool] | None = None,
    ) -> Plan:
        """
        Create an execution plan for a task.

        Args:
            task: The task to plan
            context: Additional context
            skills: Retrieved relevant skills
            tools: Retrieved relevant tools

        Returns:
            Plan with steps, confidence, and strategy
        """
        skills = skills or []
        tools = tools or []

        # Build the planning prompt
        prompt = self._build_planning_prompt(task, context, skills, tools)

        # Get plan from Claude
        self._logger.thought(
            context="planning",
            thought=f"Creating plan for task with {len(skills)} skills and {len(tools)} tools available",
        )
        plan_data = await self.claude.complete_json(
            prompt=prompt,
            system=PLANNING_SYSTEM_PROMPT,
        )

        # Parse and build Plan object
        plan = self._parse_plan(task, plan_data, skills, tools, context)

        # Log planning decision
        self._logger.decision(
            decision_type="planning_strategy",
            choice=plan.strategy.value if hasattr(plan.strategy, 'value') else str(plan.strategy),
            reasoning=plan.reasoning,
            alternatives=["skills_only", "skills_then_tools", "build_tools"],
        )

        return plan

    def _build_planning_prompt(
        self,
        task: str,
        context: str | None,
        skills: list[Skill],
        tools: list[Tool],
    ) -> str:
        """Build the prompt for planning."""
        parts = [f"# Task\n{task}"]

        if context:
            parts.append(f"\n# Additional Context\n{context}")

        if skills:
            parts.append("\n# Available Skills")
            for skill in skills:
                parts.append(f"""
## {skill.name} (ID: {skill.id})
Description: {skill.description}
Success Rate: {skill.success_rate:.0%}
Tags: {', '.join(skill.tags)}

Content Preview:
{skill.content[:500]}{'...' if len(skill.content) > 500 else ''}
""")

        if tools:
            parts.append("\n# Available Tools")
            for tool in tools:
                parts.append(f"""
## {tool.name} (ID: {tool.id})
Description: {tool.description}
Function: {tool.function_name}
Parameters: {json.dumps(tool.parameters, indent=2)}
Success Rate: {tool.success_rate:.0%}
""")

        if not skills and not tools:
            parts.append("""
# No Existing Skills or Tools Available

You'll need to plan this task from scratch. Consider what tools might be
useful to build and what skills could be extracted after completion.
""")

        parts.append("\nCreate an execution plan as JSON.")

        return "\n".join(parts)

    def _parse_plan(
        self,
        task: str,
        plan_data: dict,
        skills: list[Skill],
        tools: list[Tool],
        context: str | None = None,
    ) -> Plan:
        """Parse the plan data into a Plan object."""
        # Check for direct response (no tools needed)
        if plan_data.get("direct_response"):
            return Plan(
                task=task,
                steps=[],
                confidence=plan_data.get("confidence", 1.0),
                confidence_level=ConfidenceLevel.HIGH,
                strategy=ExecutionStrategy.SKILLS_ONLY,
                reasoning=plan_data.get("reasoning", "Direct response"),
                relevant_skills=skills,
                relevant_tools=tools,
                direct_response=plan_data.get("response", ""),
                gathered_context=context,
            )

        # Parse steps
        steps = []
        for step_data in plan_data.get("steps", []):
            step = PlanStep(
                id=step_data.get("id", f"step_{uuid4().hex[:8]}"),
                description=step_data.get("description", ""),
                expected_output=step_data.get("expected_output", ""),
                skills_to_use=step_data.get("skills_to_use", []),
                tools_to_use=step_data.get("tools_to_use", []),
                tools_to_build=step_data.get("tools_to_build", []),
                dependencies=step_data.get("dependencies", []),
            )
            steps.append(step)

        # Get confidence
        confidence = plan_data.get("confidence", 0.5)

        # Determine confidence level
        if confidence >= self.config.high_confidence_threshold:
            confidence_level = ConfidenceLevel.HIGH
        elif confidence >= self.config.confidence_threshold:
            confidence_level = ConfidenceLevel.MEDIUM
        else:
            confidence_level = ConfidenceLevel.LOW

        # Determine strategy based on confidence and needs
        needs_new_tools = plan_data.get("needs_new_tools", False)
        if needs_new_tools or confidence_level == ConfidenceLevel.LOW:
            strategy = ExecutionStrategy.BUILD_TOOLS
        elif confidence_level == ConfidenceLevel.MEDIUM:
            strategy = ExecutionStrategy.SKILLS_THEN_TOOLS
        else:
            strategy = ExecutionStrategy.SKILLS_ONLY

        return Plan(
            task=task,
            steps=steps,
            confidence=confidence,
            confidence_level=confidence_level,
            strategy=strategy,
            reasoning=plan_data.get("reasoning", ""),
            relevant_skills=skills,
            relevant_tools=tools,
            gathered_context=context,
        )

    async def reassess_confidence(
        self,
        plan: Plan,
        failure_reason: str,
        attempt_number: int,
    ) -> Plan:
        """
        Reassess plan confidence after a failure.

        May lower confidence and change strategy.
        """
        prompt = f"""The following plan failed:

Task: {plan.task}
Attempt: {attempt_number}
Failure Reason: {failure_reason}

Original Confidence: {plan.confidence}
Original Strategy: {plan.strategy.value}

Please reassess the plan:
1. Analyze why it failed
2. Adjust confidence based on the failure
3. Suggest strategy changes if needed

Respond with JSON:
{{
    "adjusted_confidence": 0.6,
    "adjusted_strategy": "build_tools",
    "reasoning": "Why this adjustment",
    "suggested_fixes": ["What to try next"]
}}"""

        result = await self.claude.complete_json(prompt=prompt)

        # Update plan with new values
        new_confidence = result.get("adjusted_confidence", plan.confidence * 0.8)

        if new_confidence >= self.config.high_confidence_threshold:
            new_level = ConfidenceLevel.HIGH
        elif new_confidence >= self.config.confidence_threshold:
            new_level = ConfidenceLevel.MEDIUM
        else:
            new_level = ConfidenceLevel.LOW

        new_strategy_str = result.get("adjusted_strategy", "build_tools")
        strategy_map = {
            "skills_only": ExecutionStrategy.SKILLS_ONLY,
            "skills_then_tools": ExecutionStrategy.SKILLS_THEN_TOOLS,
            "build_tools": ExecutionStrategy.BUILD_TOOLS,
        }
        new_strategy = strategy_map.get(new_strategy_str, ExecutionStrategy.BUILD_TOOLS)

        return Plan(
            task=plan.task,
            steps=plan.steps,
            confidence=new_confidence,
            confidence_level=new_level,
            strategy=new_strategy,
            reasoning=result.get("reasoning", plan.reasoning),
            relevant_skills=plan.relevant_skills,
            relevant_tools=plan.relevant_tools,
        )
