"""Claude Code CLI coding agent tool — thin subclass of BaseCodingAgentTool."""

import uuid
from pathlib import Path
from typing import Any

from bao.agent.tools._claudecode_json import extract_contract_fields
from bao.agent.tools._coding_agent_schema import build_coding_agent_parameters
from bao.agent.tools.coding_agent_base import (
    BaseCodingAgentTool,
    BaseCodingDetailsTool,
    DetailCache,
)
from bao.agent.tools.coding_session_store import CodingSessionStore

_claudecode_cache = DetailCache()


class ClaudeCodeTool(BaseCodingAgentTool):
    _SCHEMA_VERSION = 1

    def __init__(
        self,
        workspace: Path,
        allowed_dir: Path | None = None,
        default_timeout_seconds: int = 1800,
        session_store: CodingSessionStore | None = None,
    ):
        super().__init__(
            workspace=workspace,
            allowed_dir=allowed_dir,
            default_timeout_seconds=default_timeout_seconds,
            detail_cache=_claudecode_cache,
            session_store=session_store,
        )

    @property
    def name(self) -> str:
        return "claudecode"

    @property
    def description(self) -> str:
        return (
            "Delegate coding tasks to Claude Code CLI (`claude -p`) with per-chat session tracking. "
            "Use for code writing, refactoring, debugging, and iterative follow-ups."
        )

    @property
    def cli_binary(self) -> str:
        return "claude"

    @property
    def _tool_label(self) -> str:
        return "Claude Code"

    @property
    def _meta_prefix(self) -> str:
        return "CLAUDECODE_META"

    @property
    def parameters(self) -> dict[str, Any]:
        return build_coding_agent_parameters(
            prompt_description="Task prompt sent to Claude Code",
            session_description="Optional explicit Claude Code session ID to continue",
        )

    def _build_command(
        self,
        *,
        prompt: str,
        resolved_session: str | None,
        model: str | None,
        context_key: str,
        extra_params: dict[str, Any],
    ) -> tuple[list[str], dict[str, Any]]:
        del context_key, extra_params

        cmd = ["claude", "-p", "--output-format", "json"]
        if model:
            cmd.extend(["--model", model])

        if resolved_session:
            cmd.extend(["--resume", resolved_session])
            cmd.append(prompt)
            return cmd, {"session_id": resolved_session}

        new_session = str(uuid.uuid4())
        cmd.extend(["--session-id", new_session, prompt])
        return cmd, {"session_id": new_session}

    async def _extract_output(self, *, stdout_text: str, exec_state: dict[str, Any]) -> str:
        raw_stdout = stdout_text.strip()
        if raw_stdout:
            exec_state["raw_stdout"] = raw_stdout
        parsed_text, parsed_session = extract_contract_fields(stdout_text)
        if parsed_session:
            exec_state["session_id_from_output"] = parsed_session
        return parsed_text or raw_stdout or "(no output)"

    async def _resolve_session_after_success(
        self,
        *,
        stdout_text: str,
        resolved_session: str | None,
        cwd: Path,
        exec_state: dict[str, Any],
        timeout: int,
    ) -> str | None:
        del stdout_text, cwd, timeout
        sid_from_output = exec_state.get("session_id_from_output")
        if isinstance(sid_from_output, str) and sid_from_output.strip():
            return sid_from_output.strip()
        if resolved_session:
            return resolved_session
        sid = exec_state.get("session_id")
        if isinstance(sid, str) and sid:
            return sid
        return None

    def _detail_stdout_for_cache(
        self,
        *,
        final_output: str,
        stdout_text: str,
        exec_state: dict[str, Any],
    ) -> str:
        raw_stdout = exec_state.get("raw_stdout")
        if isinstance(raw_stdout, str) and raw_stdout:
            return raw_stdout
        if stdout_text.strip():
            return stdout_text.strip()
        return final_output

    def _error_type_impl(self, stdout_text: str, stderr_text: str) -> str:
        lowered = f"{stdout_text}\n{stderr_text}".lower()
        if "auth" in lowered or "login" in lowered:
            return "auth_not_configured"
        if "permission" in lowered:
            return "permission_blocked"
        if "timed out" in lowered or "timeout" in lowered:
            return "timeout"
        return "execution_failed"

    def _build_failure_hints(self, stdout_text: str, stderr_text: str) -> list[str]:
        lowered = f"{stdout_text}\n{stderr_text}".lower()
        hints: list[str] = []
        if "auth" in lowered or "login" in lowered:
            hints.append("Run `claude auth login` to configure Claude Code authentication.")
        if "permission" in lowered:
            hints.append("Adjust Claude Code permission mode/settings for non-interactive runs.")
        return hints

    def _build_details_hint(
        self,
        request_id: str,
        session_id: str | None,
        include_details: bool,
        details_available: bool,
    ) -> str | None:
        if include_details or not details_available:
            return None
        if session_id:
            return (
                "Detailed output omitted to protect context budget. "
                f"Use coding_agent_details with request_id '{request_id}', "
                f"or resume session '{session_id}' via `claude --resume`."
            )
        return (
            "Detailed output omitted to protect context budget. "
            f"Use coding_agent_details with request_id '{request_id}' to view full stdout/stderr."
        )

class ClaudeCodeDetailsTool(BaseCodingDetailsTool):
    def __init__(self, default_max_chars: int = 12000):
        super().__init__(detail_cache=_claudecode_cache, default_max_chars=default_max_chars)

    @property
    def name(self) -> str:
        return "claudecode_details"

    @property
    def description(self) -> str:
        return (
            "Fetch cached detailed Claude Code stdout/stderr by request_id, session_id, "
            "or current chat context latest run."
        )

    @property
    def _tool_label(self) -> str:
        return "Claude Code"

    @property
    def _meta_prefix(self) -> str:
        return "CLAUDECODE_DETAIL_META"
