"""BaseAdapter ABC — shared interface for all tool adapters."""
from __future__ import annotations

import asyncio
import shutil
from abc import ABC, abstractmethod

import structlog

from proxym.tools import ToolDefinition
from proxym.tools.adapters._result import ToolResult

logger = structlog.get_logger()


class BaseAdapter(ABC):
    """Base class for tool adapters."""

    def __init__(self, tool: ToolDefinition) -> None:
        self.tool = tool

    @abstractmethod
    def build_args(
        self, prompt: str, project_dir: str, mode: str = "", model: str = "",
        files: list[str] | None = None,
    ) -> list[str]:
        """Build CLI argument list."""
        ...

    async def run(
        self,
        prompt: str,
        project_dir: str,
        ticket_id: str = "",
        mode: str = "",
        model: str = "",
        files: list[str] | None = None,
        timeout: float = 300,
    ) -> ToolResult:
        """Launch the tool and wait for completion.

        If the tool binary is not found, delegates to LLM fallback
        for real execution via the proxy API.
        """
        import time
        from proxym.tools.adapters._llm_fallback import _llm_fallback_execute

        binary = shutil.which(self.tool.binary) if self.tool.binary else None
        if not binary:
            logger.info("adapter_llm_fallback", tool=self.tool.name,
                        binary=self.tool.binary, reason="binary not found")
            return await _llm_fallback_execute(
                self.tool.name, prompt, ticket_id, project_dir, mode, model, timeout,
            )

        args = self.build_args(prompt, project_dir, mode, model, files)
        logger.info("tool_adapter_run", tool=self.tool.name, args=args[:3], cwd=project_dir)
        t0 = time.monotonic()
        try:
            proc = await asyncio.create_subprocess_exec(
                *args,
                cwd=project_dir,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )
            stdout_b, stderr_b = await asyncio.wait_for(
                proc.communicate(), timeout=timeout,
            )
            duration = time.monotonic() - t0
            result = ToolResult(
                tool=self.tool.name,
                ticket_id=ticket_id,
                exit_code=proc.returncode or 0,
                stdout=stdout_b.decode(errors="replace"),
                stderr=stderr_b.decode(errors="replace"),
                duration_s=duration,
                execution_metadata={"adapter": self.tool.name, "binary": binary, "args": args[:5]},
            )
            logger.info("tool_adapter_done", tool=self.tool.name,
                        exit_code=result.exit_code, duration=f"{duration:.1f}s")
            return result
        except asyncio.TimeoutError:
            duration = time.monotonic() - t0
            return ToolResult(
                tool=self.tool.name, ticket_id=ticket_id,
                exit_code=-1, stderr=f"Timeout after {timeout}s",
                duration_s=duration,
            )
        except FileNotFoundError:
            duration = time.monotonic() - t0
            logger.info("adapter_binary_vanished", tool=self.tool.name)
            return await _llm_fallback_execute(
                self.tool.name, prompt, ticket_id, project_dir, mode, model, timeout,
            )
        except Exception as exc:
            duration = time.monotonic() - t0
            return ToolResult(
                tool=self.tool.name, ticket_id=ticket_id,
                exit_code=-1, stderr=str(exc), duration_s=duration,
            )
