"""Claude Code adapter — interactive subprocess with stdin/stdout passthrough."""

from __future__ import annotations

import logging
import os
import shutil
import signal
import subprocess
import sys
from datetime import datetime, timezone

from nestdx.adapters.base import ToolAdapter
from nestdx.config.schema import ToolConfig
from nestdx.session.models import ToolRun
from nestdx.tracking.cost import TokenUsage, parse_claude_code_output

logger = logging.getLogger(__name__)

try:
    import pty as _pty

    _HAS_PTY = True
except ImportError:
    _HAS_PTY = False


class ClaudeCodeAdapter(ToolAdapter):
    """Adapter for the Claude Code CLI (``claude``).

    Uses a PTY to preserve full interactivity while capturing output for
    token/cost parsing.  Falls back to plain ``subprocess.run`` (no capture)
    when a PTY is unavailable or stdout is not a terminal.
    """

    def __init__(self) -> None:
        self._last_token_usage: TokenUsage | None = None

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

    @property
    def last_token_usage(self) -> TokenUsage | None:
        """Return the token usage parsed from the last run, if any."""
        return self._last_token_usage

    def resolve_command(self, config: ToolConfig) -> list[str]:
        cmd = ["claude"]
        model = config.config.get("model")
        if model:
            cmd.extend(["--model", model])
        max_tokens = config.config.get("max_tokens")
        if max_tokens:
            cmd.extend(["--max-tokens", str(max_tokens)])
        return cmd

    def validate(self) -> bool:
        return shutil.which("claude") is not None

    @property
    def install_hint(self) -> str:
        return "Install Claude Code: npm install -g @anthropic-ai/claude-code"

    def _parse_cost(self, output: str) -> TokenUsage | None:
        """Parse token/cost data from Claude Code output."""
        return parse_claude_code_output(output)

    def _run_interactive(self, cmd: list[str]) -> tuple[int, str]:
        """Run command interactively, capturing output via PTY tee.

        Returns ``(exit_code, captured_output)``.  When a PTY is available
        and stdout is a real terminal, output is tee'd — the user sees
        everything in real-time while we accumulate a copy for cost parsing.
        Otherwise falls back to plain subprocess (no capture).
        """
        if _HAS_PTY and sys.stdout.isatty():
            output_chunks: list[bytes] = []

            def _tee_read(fd: int) -> bytes:
                data = os.read(fd, 1024)
                output_chunks.append(data)
                return data

            try:
                exit_status = _pty.spawn(cmd, master_read=_tee_read)
                exit_code = (
                    os.WEXITSTATUS(exit_status) if os.WIFEXITED(exit_status) else 1
                )
            except Exception:
                return 1, ""

            captured = b"".join(output_chunks).decode("utf-8", errors="replace")
            return exit_code, captured

        # Fallback: interactive but no capture
        try:
            result = subprocess.run(cmd)
            return result.returncode, ""
        except Exception:
            return 1, ""

    def run(self, config: ToolConfig, extra_args: list[str]) -> ToolRun:
        cmd = self.resolve_command(config) + extra_args
        start_time = datetime.now(timezone.utc)
        self._last_token_usage = None

        # Ignore SIGINT in the parent so Ctrl+C goes to the child only
        original_sigint = signal.getsignal(signal.SIGINT)
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        try:
            exit_code, captured_output = self._run_interactive(cmd)
        except Exception:
            exit_code = 1
            captured_output = ""
        finally:
            signal.signal(signal.SIGINT, original_sigint)

        # Try to parse token/cost data from captured output
        if captured_output:
            try:
                self._last_token_usage = self._parse_cost(captured_output)
            except Exception:
                logger.debug(
                    "Failed to parse cost data from Claude Code output",
                    exc_info=True,
                )

        return ToolRun(
            tool=self.name,
            command=" ".join(cmd),
            start_time=start_time,
            end_time=datetime.now(timezone.utc),
            exit_code=exit_code,
        )
