"""Prompt builder for Dobby task dispatch agents."""

from __future__ import annotations

from dobby.models import InfraConfig, Project, Task, TaskMode

DOBBY_RESULT_FORMAT = """\
When you are done, output a summary in this exact format:
---DOBBY_RESULT---
{
  "status": "completed" or "failed",
  "summary": "one-line summary of what was done",
  "pr_url": "url or null",
  "files_changed": ["list", "of", "files"],
  "metrics": { ... any numeric results ... },
  "error": "error message if failed, null otherwise"
}
---END_DOBBY_RESULT---"""

# MCP tools that Dobby agents can use for personal assistant tasks.
# Each entry: (tool_name, description).  The prompt builder injects these
# so the agent knows what external services are reachable.
AVAILABLE_MCP_SERVICES: list[tuple[str, str]] = [
    ("slack", "Read/send Slack messages, list channels, search conversations"),
    ("gmail", "Read emails, search inbox, list unreplied messages (read-only)"),
    ("google-calendar", "Read calendar events, check upcoming meetings and deadlines"),
    ("notion", "Read/write Notion pages and databases"),
    ("github", "Create PRs, read issues, manage repositories"),
]


class PromptBuilder:
    """Builds text prompts for Dobby agent dispatch."""

    @staticmethod
    def build(
        task: Task,
        project: Project,
        infra_name: str | None = None,
        user_preferences: list[dict] | None = None,
        mcp_services: list[tuple[str, str]] | None = None,
    ) -> str:
        """Construct the full prompt for a task agent.

        Args:
            task: The task to dispatch.
            project: The project the task belongs to.
            infra_name: Optional name of an InfraConfig from project.infra to
                include in the prompt (e.g. for SSH/GPU tasks).
            user_preferences: Optional list of preference dicts (key, value)
                to inject so the agent respects user wishes.
            mcp_services: Optional list of (name, description) tuples for
                available MCP services the agent can use.

        Returns:
            A complete prompt string ready to pass to the agent.
        """
        sections: list[str] = []

        # --- Project context ---
        project_lines = [f"# Project: {project.name}"]
        project_lines.append(f"Repository: {project.repo}")
        if project.context:
            project_lines.append(f"Context: {project.context}")
        sections.append("\n".join(project_lines))

        # --- Task description ---
        task_lines = ["# Task"]
        task_lines.append(f"Description: {task.description}")
        if task.exit_criteria:
            task_lines.append(f"Exit criteria: {task.exit_criteria}")
        sections.append("\n".join(task_lines))

        # --- Plan steps (if present) ---
        if task.plan:
            plan_lines = ["# Plan"]
            for i, step in enumerate(task.plan, start=1):
                plan_lines.append(f"{i}. {step}")
            sections.append("\n".join(plan_lines))

        # --- Infra config (if requested) ---
        if infra_name is not None:
            infra: InfraConfig | None = next(
                (ic for ic in project.infra if ic.name == infra_name), None
            )
            if infra is not None:
                infra_lines = [f"# Infrastructure: {infra.name}"]
                infra_lines.append(f"Type: {infra.type}")
                if infra.host:
                    infra_lines.append(f"SSH host: {infra.host}")
                if infra.conda_env:
                    infra_lines.append(f"Conda env: {infra.conda_env}")
                if infra.gpus is not None:
                    infra_lines.append(f"GPUs: {infra.gpus}")
                if infra.gpu:
                    infra_lines.append(f"GPU model: {infra.gpu}")

                # GPU/SSH-specific guidance
                if infra.type in ("ssh", "gpu"):
                    infra_lines.append(
                        "\nIMPORTANT: Launch long-running jobs in the background using nohup "
                        "so they continue after this session ends. "
                        "Report the PID and log file path. Do NOT wait for the job to complete."
                    )

                sections.append("\n".join(infra_lines))

        # --- Planned mode: explore only, produce a plan ---
        if task.mode == TaskMode.PLANNED:
            sections.append(
                "# Mode: Planning Only\n"
                "You are in PLANNING mode."
                " Do NOT make any code changes or execute any modifications.\n"
                "Instead:\n"
                "1. Explore the codebase by reading relevant files\n"
                "2. Understand the current architecture and constraints\n"
                "3. Produce a detailed, actionable plan with specific steps\n"
                "4. Include which files need to be modified and how\n"
                "5. Note any risks, dependencies, or questions\n\n"
                "Your output should be the plan itself. A human will review and approve it "
                "before execution begins."
            )

        # --- Checkpoint instructions ---
        if task.checkpoint:
            checkpoint_section = (
                "# Checkpoint / Approval Required\n"
                "Before making any significant or irreversible decisions, pause and ask the user "
                "for confirmation. Describe the action you are about to take and wait for explicit "
                "approval before proceeding."
            )
            sections.append(checkpoint_section)

        # --- User preferences ---
        if user_preferences:
            pref_lines = [
                "# User Preferences",
                "The following user preferences MUST be respected when executing this task:",
            ]
            for pref in user_preferences:
                key = pref.get("key", "").removeprefix("pref:")
                value = pref.get("value", "")
                pref_lines.append(f"- **{key}**: {value}")
            pref_lines.append(
                "\nYou can query additional preferences via the dobby_user_prefs MCP tool."
            )
            sections.append("\n".join(pref_lines))

        # --- Available MCP services ---
        services = mcp_services if mcp_services is not None else AVAILABLE_MCP_SERVICES
        if services:
            svc_lines = [
                "# Available External Services (MCP)",
                "You have access to these external services via MCP tools. "
                "Use them when the task requires interacting with external systems:",
            ]
            for name, desc in services:
                svc_lines.append(f"- **{name}**: {desc}")
            svc_lines.append(
                "\nYou also have access to Dobby tools: dobby_remember, dobby_recall, "
                "dobby_user_prefs for persisting information and querying user preferences."
            )
            sections.append("\n".join(svc_lines))

        # --- Output format ---
        sections.append(DOBBY_RESULT_FORMAT)

        return "\n\n".join(sections)

    @staticmethod
    def build_gpu_parse_prompt(log_content: str, task_description: str) -> str:
        """Build a prompt for a Phase 2 log-parsing agent.

        The agent reads GPU job logs and extracts metrics into DOBBY_RESULT format.

        Args:
            log_content: Raw stdout/stderr from the GPU job.
            task_description: Human-readable description of the original task.

        Returns:
            A prompt string for the log-parsing agent.
        """
        sections: list[str] = []

        sections.append(
            "# GPU Job Log Parser\n"
            f"Original task: {task_description}\n\n"
            "You are a log-parsing agent. Read the job log below and extract all relevant "
            "metrics, errors, and status information."
        )

        sections.append(f"# Job Log\n```\n{log_content}\n```")

        sections.append(
            "Determine whether the job completed successfully or failed, "
            "extract all numeric metrics (loss, accuracy, throughput, etc.), "
            "and summarise what happened."
        )

        sections.append(DOBBY_RESULT_FORMAT)

        return "\n\n".join(sections)
