"""LLM fallback — real execution via proxy API when tool binary is unavailable."""
from __future__ import annotations

import asyncio
from pathlib import Path
from typing import Any

import structlog

from proxym.tools.adapters._result import ToolResult

logger = structlog.get_logger()

_TOOL_SYSTEM_PROMPTS: dict[str, str] = {
    "aider": (
        "You are Aider, an AI pair programmer. Analyze the codebase and implement "
        "changes as requested. Show exact file edits with unified diff format. "
        "List every file you would modify and explain each change."
    ),
    "claude-code": (
        "You are Claude Code, Anthropic's coding agent. Analyze the project, "
        "reason step by step about the problem, then provide a complete solution "
        "with code changes. Show file paths and exact edits."
    ),
    "windsurf": (
        "You are Windsurf Cascade, an AI coding agent. Analyze the project "
        "structure, understand the task, and provide a detailed implementation plan "
        "with code changes. Show file paths and explain each modification."
    ),
    "cursor": (
        "You are Cursor Composer, an AI coding agent. Analyze the codebase, "
        "understand the task requirements, and provide complete code changes. "
        "Show file paths, explain reasoning, and list all modifications."
    ),
    "roo-code": (
        "You are Roo Code, an AI coding assistant running in VS Code. Analyze the "
        "project, understand the task, and provide detailed code changes with file "
        "paths and explanations for each modification."
    ),
    "cline": (
        "You are Cline, an autonomous coding agent in VS Code. Analyze the project "
        "structure, plan your approach, then provide complete code implementations "
        "with file paths and exact changes."
    ),
    "browser": (
        "You are a coding assistant accessed via browser. Analyze the task and "
        "provide a detailed response with code changes, file paths, and explanations."
    ),
    "vscode": (
        "You are a VS Code coding assistant. Analyze the project structure, "
        "understand the task, and provide detailed code changes with file paths."
    ),
}


def _build_project_context(project_dir: str) -> str:
    """Build a short project context string from the project directory."""
    project_path = Path(project_dir)
    if not project_path.is_dir():
        return ""
    try:
        entries = sorted(project_path.iterdir())
        file_list = []
        for e in entries[:50]:
            if e.name.startswith(".") and e.name not in (".env.example", ".gitignore"):
                continue
            if e.is_dir():
                file_list.append(f"  {e.name}/ (dir)")
            else:
                file_list.append(f"  {e.name}")
        if file_list:
            return f"\n\nProject directory: {project_dir}\nFiles:\n" + "\n".join(file_list[:30])
    except OSError:
        pass
    return ""


def _resolve_litellm_model(resolved_model: str, settings) -> tuple[str, str]:
    """Resolve to a concrete litellm model + provider. Returns (model, provider)."""
    _OR_FALLBACK = "openrouter/anthropic/claude-sonnet-4.6"
    try:
        from proxym.api.completions import _find_model_spec
        spec = _find_model_spec(resolved_model)
        configured = settings.available_providers()
        if spec and spec.provider in configured:
            return spec.litellm_model, spec.provider
        if spec and "openrouter" in configured:
            return f"openrouter/{spec.litellm_model}", "openrouter"
        return _OR_FALLBACK, "openrouter"
    except Exception:
        return _OR_FALLBACK, "openrouter"


async def _llm_fallback_execute(
    tool_name: str,
    prompt: str,
    ticket_id: str,
    project_dir: str,
    mode: str = "",
    model_override: str = "",
    timeout: float = 120,
) -> ToolResult:
    """Execute a task via LLM API when the tool binary is not available.

    Calls litellm.acompletion through the proxy's own routing, using the
    tool's assigned provider and model. Returns real LLM output.
    """
    import time
    import litellm
    from proxym.config import get_settings
    from proxym.api.completions import _inject_provider_key

    settings = get_settings()
    t0 = time.monotonic()

    # Resolve model: override → "balanced" fallback, then resolve alias
    model_alias = model_override if model_override else "balanced"
    resolved_model = settings.resolve_model_alias(model_alias)
    # If alias didn't resolve (returned same string), try "balanced" as safety net
    if resolved_model == model_alias and model_alias not in ("balanced", "cheap", "premium", "free", "local"):
        resolved_model = settings.resolve_model_alias("balanced")

    system_prompt = _TOOL_SYSTEM_PROMPTS.get(tool_name, _TOOL_SYSTEM_PROMPTS["browser"])
    if mode:
        system_prompt += f"\n\nMode: {mode}. Adapt your approach accordingly."

    project_context = _build_project_context(project_dir)

    messages = [
        {"role": "system", "content": system_prompt + project_context},
        {"role": "user", "content": prompt},
    ]

    litellm_model, provider = _resolve_litellm_model(resolved_model, settings)

    kwargs: dict[str, Any] = {
        "model": litellm_model,
        "messages": messages,
        "max_tokens": 4096,
        "temperature": 0.3,
    }
    _inject_provider_key(kwargs, provider)

    logger.info(
        "llm_fallback_execute",
        tool=tool_name,
        ticket_id=ticket_id,
        model=litellm_model,
        provider=provider,
        project_dir=project_dir,
        prompt_length=len(prompt),
    )

    try:
        response = await asyncio.wait_for(
            litellm.acompletion(**kwargs),
            timeout=timeout,
        )
        duration = time.monotonic() - t0

        # Extract response content
        content = ""
        if hasattr(response, "choices") and response.choices:
            msg = response.choices[0].message
            content = getattr(msg, "content", "") or ""

        # Extract usage
        usage = getattr(response, "usage", None)
        prompt_tokens = getattr(usage, "prompt_tokens", 0) if usage else 0
        completion_tokens = getattr(usage, "completion_tokens", 0) if usage else 0
        actual_model = getattr(response, "model", litellm_model)

        # Build detailed stdout log
        stdout_parts = [
            f"═══ proxym LLM Execution Log ═══",
            f"Tool: {tool_name} (via LLM fallback)",
            f"Ticket: {ticket_id}",
            f"Model: {actual_model}",
            f"Provider: {provider}",
            f"Mode: {mode or 'code'}",
            f"Duration: {duration:.2f}s",
            f"Tokens: {prompt_tokens} in / {completion_tokens} out",
            f"Project: {project_dir}",
            f"═══════════════════════════════════",
            f"",
            content,
        ]
        stdout = "\n".join(stdout_parts)

        metadata = {
            "adapter": "llm_fallback",
            "model": actual_model,
            "provider": provider,
            "model_requested": litellm_model,
            "prompt_tokens": prompt_tokens,
            "completion_tokens": completion_tokens,
            "total_tokens": prompt_tokens + completion_tokens,
            "system_prompt_length": len(system_prompt),
            "user_prompt_length": len(prompt),
            "response_length": len(content),
        }

        logger.info(
            "llm_fallback_done",
            tool=tool_name,
            ticket_id=ticket_id,
            model=actual_model,
            tokens=f"{prompt_tokens}+{completion_tokens}",
            duration=f"{duration:.1f}s",
            response_len=len(content),
        )

        return ToolResult(
            tool=tool_name,
            ticket_id=ticket_id,
            exit_code=0,
            stdout=stdout,
            stderr="",
            duration_s=duration,
            execution_metadata=metadata,
        )

    except asyncio.TimeoutError:
        duration = time.monotonic() - t0
        return ToolResult(
            tool=tool_name, ticket_id=ticket_id,
            exit_code=-1,
            stderr=f"LLM call timed out after {timeout}s (model: {litellm_model})",
            duration_s=duration,
            execution_metadata={"adapter": "llm_fallback", "model": litellm_model, "error": "timeout"},
        )
    except Exception as exc:
        duration = time.monotonic() - t0
        logger.error("llm_fallback_error", tool=tool_name, error=str(exc)[:300])
        return ToolResult(
            tool=tool_name, ticket_id=ticket_id,
            exit_code=1,
            stderr=f"LLM fallback error: {exc}",
            duration_s=duration,
            execution_metadata={"adapter": "llm_fallback", "model": litellm_model, "error": str(exc)[:200]},
        )
