"""OpenCode Style Deep Agent

Deep Agent with OpenCode-style finish_reason-based exit logic.

This module provides create_opencode_style_deepagent, which is an OpenCode-style
version of deepagents.create_deep_agent. It maintains full compatibility with
the original interface but uses create_opencode_style_agent internally for
finish_reason-driven exit logic.
"""

from collections.abc import Callable, Sequence
from typing import Any

# Import from opencode_style_agent (our OpenCode Style implementation)
from ..opencode_style_agent.factory import create_opencode_style_agent

# Import LangChain middleware types
from langchain.agents.middleware import (
    HumanInTheLoopMiddleware,
    InterruptOnConfig,
    ModelRetryMiddleware,
    TodoListMiddleware,
)
from langchain.agents.middleware.types import AgentMiddleware
from langchain.agents.structured_output import ResponseFormat
from langchain.chat_models import init_chat_model
from langchain_anthropic import ChatAnthropic
from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import SystemMessage
from langchain_core.tools import BaseTool
from langgraph.cache.base import BaseCache
from langgraph.graph.state import CompiledStateGraph
from langgraph.store.base import BaseStore
from langgraph.types import Checkpointer

# Import DeepAgents components
from deepagents.backends import StateBackend
from deepagents.backends.protocol import BackendFactory, BackendProtocol
from deepagents.middleware.filesystem import FilesystemMiddleware
from deepagents.middleware.memory import MemoryMiddleware
from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware
from deepagents.middleware.subagents import (
    GENERAL_PURPOSE_SUBAGENT,
    CompiledSubAgent,
    SubAgent,
    SubAgentMiddleware,
)

from langchain_agentx.middleware import BaseSkillsMiddleware

def _create_subagent_middleware(
    backend: BackendProtocol | BackendFactory | None,
    subagents: list[SubAgent | CompiledSubAgent],
    default_model: BaseChatModel | Any,
) -> SubAgentMiddleware:
    """Create SubAgentMiddleware with compatibility across DeepAgents versions.

    Some versions accept a ``backend=...`` argument, others only accept
    ``subagents=...``. We inspect the constructor signature to decide.
    """
    import inspect

    kwargs: dict[str, Any] = {"subagents": subagents}
    try:
        sig = inspect.signature(SubAgentMiddleware)
        params = sig.parameters
        if "backend" in params:
            kwargs["backend"] = backend
        if "default_model" in params:
            kwargs["default_model"] = default_model
        return SubAgentMiddleware(**kwargs)  # type: ignore[arg-type]
    except Exception:
        # Fall back to the simplest, most compatible form
        return SubAgentMiddleware(subagents=subagents)  # type: ignore[arg-type]

# ═══════════════════════════════════════════════════════════════════════════════
# Constants
# ═══════════════════════════════════════════════════════════════════════════════

BASE_AGENT_PROMPT = """You are a Deep Agent, an AI assistant that helps users accomplish tasks using tools. You respond with text and tool calls. The user can see your responses and tool outputs in real time.

## Core Behavior

- Be concise and direct. Don't over-explain unless asked.
- NEVER add unnecessary preamble ("Sure!", "Great question!", "I'll now...").
- Don't say "I'll now do X" — just do it.
- If request is ambiguous, ask questions before acting.
- If asked how to approach something, explain first, then act.

## Professional Objectivity

- Prioritize accuracy over validating the user's beliefs
- Disagree respectfully when user is incorrect
- Avoid unnecessary superlatives, praise, or emotional validation

## Doing Tasks

When the user asks you to do something:

1. **Understand first** — read relevant files, check existing patterns. Quick but thorough — gather enough evidence to start, then iterate.
2. **Act** — implement solution. Work quickly but accurately.
3. **Verify** — check your work against what was asked, not against your own output. Your first attempt is rarely correct — iterate.

Keep working until the task is fully complete. Don't stop partway and explain what you would do — just do it. Only yield back to the user when the task is done or you're genuinely blocked.

**When things go wrong:**
- If something fails repeatedly, stop and analyze *why* — don't keep retrying the same approach.
- If you're blocked, tell the user what's wrong and ask for guidance.

## Progress Updates

For longer tasks, provide brief progress updates at reasonable intervals — a concise sentence recapping what you've done and what's next."""  # noqa: E501


def get_default_model() -> ChatAnthropic:
    """Get default model for deep agents.

    Returns:
        `ChatAnthropic` instance configured with Claude Sonnet 4.5.
    """
    # Prefer explicit model name; different versions of ChatAnthropic may expose
    # the underlying identifier via different fields. We set a class attribute
    # so that `hasattr(instance, "model_name")` is true without mutating the
    # Pydantic model instance.
    if not hasattr(ChatAnthropic, "model_name"):
        setattr(ChatAnthropic, "model_name", "claude-sonnet-4-5-20250929")

    return ChatAnthropic(
        model_name="claude-sonnet-4-5-20250929",
        max_tokens=20000,
    )


def create_opencode_style_deepagent(  # noqa: C901, PLR0912  # Complex graph assembly logic
    model: str | BaseChatModel | None = None,
    tools: Sequence[BaseTool | Callable | dict[str, Any]] | None = None,
    *,
    system_prompt: str | SystemMessage | None = None,
    middleware: Sequence[AgentMiddleware] = (),
    subagents: list[SubAgent | CompiledSubAgent] | None = None,
    skills: list[str] | None = None,
    memory: list[str] | None = None,
    response_format: ResponseFormat | None = None,
    context_schema: type[Any] | None = None,
    checkpointer: Checkpointer | None = None,
    store: BaseStore | None = None,
    backend: BackendProtocol | BackendFactory | None = None,
    interrupt_on: dict[str, bool | InterruptOnConfig] | None = None,
    debug: bool = False,
    name: str | None = None,
    cache: BaseCache | None = None,
    # OpenCode Style 特有参数
    max_steps: int = 50,
    enable_tool_call_degradation_fix: bool = True,
    enable_todo_middleware: bool = False,
    self_define_skills_middleware: BaseSkillsMiddleware | None = None,
) -> CompiledStateGraph:
    """Create an OpenCode-style deep agent.

    !!! warning "Deep agents require an LLM that supports tool calling!"

    By default, this agent has access to the following tools:

    - `write_todos`: manage a todo list
    - `ls`, `read_file`, `write_file`, `edit_file`, `glob`, `grep`: file operations
    - `execute`: run shell commands
    - `task`: call subagents

    The `execute` tool allows running shell commands if the backend implements
    `SandboxBackendProtocol`. For non-sandbox backends, `execute` will return
    an error message.

    **Key Difference from deepagents.create_deep_agent:**
    - Uses `create_opencode_style_agent` internally, which employs finish_reason-driven
      exit logic (LLM decides when to stop, not the program)
    - All other functionality and parameters remain identical

    Args:
        model: The model to use.

            Defaults to `claude-sonnet-4-5-20250929`.

            Use `provider:model` format (e.g., `openai:gpt-5`) to quickly
            switch between models.
        tools: The tools the agent should have access to.

            In addition to custom tools you provide, deep agents include built-in tools for
            planning, file management, and subagent spawning.
        system_prompt: Custom system instructions to prepend before the base deep agent
            prompt.

            If a string, it's concatenated with the base prompt.
        middleware: Additional middleware to apply after the standard middleware stack
            (`FilesystemMiddleware`, `SubAgentMiddleware`,
            `AnthropicPromptCachingMiddleware`, `PatchToolCallsMiddleware`).
        subagents: The subagents to use.

            Each subagent should be a `dict` with the following keys:

            - `name`
            - `description` (used by main agent to decide whether to call subagent)
            - `prompt` (used as system prompt in subagent)
            - (optional) `tools`
            - (optional) `model` (either a `LanguageModelLike` instance or `dict` settings)
            - (optional) `middleware` (list of `AgentMiddleware`)
        skills: Optional list of skill source paths
            (e.g., `["/skills/user/", "/skills/project/"]`).

            Paths must be specified using POSIX conventions (forward slashes) and are
            relative to the backend's root. When using `StateBackend` (default), provide
            skill files via `invoke(files={...})`. With `FilesystemBackend`,
            skills are loaded from disk relative to backend's `root_dir`. Later
            sources override earlier ones for skills with the same name (last one wins).
        memory: Optional list of memory file paths (`AGENTS.md` files) to load
            (e.g., `["/memory/AGENTS.md"]`).

            Display names are automatically derived from paths.

            Memory is loaded at agent startup and added into the system prompt.
        response_format: A structured output response format to use for the agent.
        context_schema: The schema of the deep agent.
        checkpointer: Optional `Checkpointer` for persisting agent state between runs.
        store: Optional store for persistent storage (required if backend uses `StoreBackend`).
        backend: Optional backend for file storage and execution.

            Pass either a `Backend` instance or a callable factory like `lambda rt: StateBackend(rt)`.
            For execution support, use a backend that implements `SandboxBackendProtocol`.
        interrupt_on: Mapping of tool names to interrupt configs.

            Pass to pause agent execution at specified tool calls for human approval or
            modification. Example: `interrupt_on={"edit_file": True}` pauses before
            every edit.
        debug: Whether to enable debug mode. Passed through to `create_opencode_style_agent`.
        name: The name of the agent. Passed through to `create_opencode_style_agent`.
        cache: The cache to use for the agent. Passed through to `create_opencode_style_agent`.
        max_steps: Maximum number of thinking steps (OpenCode Style specific, default 50).
        enable_todo_middleware: Whether to attach TodoListMiddleware to the **main**
            agent (controls the top-level Todo list / planning behavior). Defaults to
            False. Subagents no longer include TodoListMiddleware by default.

    Returns:
        A configured OpenCode-style deep agent.
    """
    # ═══════════════════════════════════════════════════════════════
    # 1. Process model
    # ═══════════════════════════════════════════════════════════════
    if model is None:
        model = get_default_model()
    elif isinstance(model, str):
        if model.startswith("openai:"):
            # Use Responses API by default. To use chat completions, use
            # `model=init_chat_model("openai:...")`
            # To disable data retention with Responses API, use
            # `model=init_chat_model("openai:...", use_responses_api=True, store=False, include=["reasoning.encrypted_content"])`
            model_init_params: dict = {"use_responses_api": True}
        else:
            model_init_params = {}

        model = init_chat_model(model, **model_init_params)

    # ═══════════════════════════════════════════════════════════════
    # 2. Set default backend
    # ═══════════════════════════════════════════════════════════════
    backend = backend if backend is not None else StateBackend

    # ═══════════════════════════════════════════════════════════════
    # 3. Build general-purpose subagent with default middleware stack
    # ═══════════════════════════════════════════════════════════════
    gp_middleware: list[AgentMiddleware[Any, Any, Any]] = [
        TodoListMiddleware(),
        FilesystemMiddleware(backend=backend),
        AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
        PatchToolCallsMiddleware(),
    ]
    if self_define_skills_middleware is not None:
        gp_middleware.append(self_define_skills_middleware)
    if interrupt_on is not None:
        gp_middleware.append(HumanInTheLoopMiddleware(interrupt_on=interrupt_on))

    general_purpose_spec: SubAgent = {  # ty: ignore[missing-typed-dict-key]
        **GENERAL_PURPOSE_SUBAGENT,
        "model": model,
        "tools": tools or [],
        "middleware": gp_middleware,
    }

    # ═══════════════════════════════════════════════════════════════
    # 4. Process user-provided subagents to fill in defaults
    # ═══════════════════════════════════════════════════════════════
    processed_subagents: list[SubAgent | CompiledSubAgent] = []
    for spec in subagents or []:
        if "runnable" in spec:
            # CompiledSubAgent - ensure required metadata fields are present
            compiled_spec: CompiledSubAgent = {  # type: ignore[assignment]
                "name": spec["name"],
                "runnable": spec["runnable"],
                "description": spec.get("description", f"Subagent {spec['name']}"),
            }
            processed_subagents.append(compiled_spec)
        else:
            # SubAgent - fill in defaults and prepend base middleware
            subagent_model = spec.get("model", model)
            if isinstance(subagent_model, str):
                subagent_model = init_chat_model(subagent_model)

            # Build middleware: base stack + skills (if specified) + user's middleware
            subagent_middleware: list[AgentMiddleware[Any, Any, Any]] = [
                TodoListMiddleware(),
                FilesystemMiddleware(backend=backend),
                AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
                PatchToolCallsMiddleware(),
            ]
            if self_define_skills_middleware is not None:
                subagent_middleware.append(self_define_skills_middleware)
            subagent_middleware.extend(
                m for m in spec.get("middleware", []) if isinstance(m, AgentMiddleware)
            )

            # Newer DeepAgents versions expect `system_prompt`; older designs used `prompt`.
            system_prompt = spec.get("system_prompt") or spec.get("prompt")

            processed_spec: SubAgent = {  # ty: ignore[missing-typed-dict-key]
                **spec,
                "model": subagent_model,
                "tools": spec.get("tools", tools or []),
                "middleware": subagent_middleware,
                "system_prompt": system_prompt,
            }
            processed_subagents.append(processed_spec)

    # ═══════════════════════════════════════════════════════════════
    # 5. Combine GP with processed user-provided subagents
    # ═══════════════════════════════════════════════════════════════
    all_subagents: list[SubAgent | CompiledSubAgent] = [general_purpose_spec, *processed_subagents]

    # ═══════════════════════════════════════════════════════════════
    # 6. Build main agent middleware stack
    # ═══════════════════════════════════════════════════════════════
    # ModelRetryMiddleware - 默认最外层，模型调用失败时重试（针对限流/网络抖动）
    # 参数与 server_agent 中保持一致
    deepagent_middleware: list[AgentMiddleware[Any, Any, Any]] = [
        ModelRetryMiddleware(
            max_retries=10,
            initial_delay=5.0,   # 首次重试前等待 5 秒
            backoff_factor=2.0,  # 指数退避：5s → 10s → 20s
            max_delay=120.0,     # 单次等待上限 2 分钟
            jitter=True,         # 随机抖动，避免多实例同时重试
            on_failure="continue",
        ),
    ]
    # 主 Agent 层的 TodoListMiddleware 由 enable_todo_middleware 显式控制，默认不启用计划模式
    if enable_todo_middleware:
        deepagent_middleware.append(TodoListMiddleware())
    if memory is not None:
        deepagent_middleware.append(MemoryMiddleware(backend=backend, sources=memory))
    if self_define_skills_middleware is not None:
        deepagent_middleware.append(self_define_skills_middleware)
    deepagent_middleware.extend(
        [
            FilesystemMiddleware(backend=backend),
            _create_subagent_middleware(
                backend=backend,
                subagents=all_subagents,
                default_model=model,
            ),
            AnthropicPromptCachingMiddleware(unsupported_model_behavior="ignore"),
            PatchToolCallsMiddleware(),
        ]
    )

    if middleware:
        # Only keep middleware that follows the AgentMiddleware interface; this
        # avoids AttributeError when tests pass simple mocks.
        deepagent_middleware.extend(
            m for m in middleware if isinstance(m, AgentMiddleware)
        )
    if interrupt_on is not None:
        deepagent_middleware.append(HumanInTheLoopMiddleware(interrupt_on=interrupt_on))

    # ═══════════════════════════════════════════════════════════════
    # 8. Combine system_prompt with BASE_AGENT_PROMPT
    # ═══════════════════════════════════════════════════════════════
    if system_prompt is None:
        final_system_prompt: str | SystemMessage = BASE_AGENT_PROMPT
    elif isinstance(system_prompt, SystemMessage):
        final_system_prompt = SystemMessage(
            content_blocks=[*system_prompt.content_blocks, {"type": "text", "text": f"\n\n{BASE_AGENT_PROMPT}"}]
        )
    else:
        # String: simple concatenation
        final_system_prompt = system_prompt

    # ═══════════════════════════════════════════════════════════════
    # 9. Create agent using create_opencode_style_agent (NOT create_agent)
    # ═══════════════════════════════════════════════════════════════
    return create_opencode_style_agent(
        model,
        system_prompt=final_system_prompt,
        tools=tools,
        middleware=deepagent_middleware,
        response_format=response_format,
        context_schema=context_schema,
        checkpointer=checkpointer,
        store=store,
        debug=debug,
        name=name,
        cache=cache,
        max_steps=max_steps,
        enable_tool_call_degradation_fix=enable_tool_call_degradation_fix,
    )
