"""HatchDX adapter — wraps HatchDX agent commands."""

from __future__ import annotations

import logging
import os
import re
import shutil
import signal
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import PurePosixPath

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

logger = logging.getLogger(__name__)

try:
    import pty as _pty

    _HAS_PTY = True
except ImportError:
    _HAS_PTY = False

# Patterns for HatchDX cost/token output
_HDX_TOKENS_RE = re.compile(r"[Tt]okens?\s*[:=]\s*([\d,]+)", re.IGNORECASE)
_HDX_COST_RE = re.compile(r"[Cc]ost\s*[:=]\s*\$?([\d,]+\.?\d*)", re.IGNORECASE)
_HDX_MODEL_RE = re.compile(r"[Mm]odel\s*[:=]\s*(\S+)", re.IGNORECASE)


class HatchDXAdapter(ToolAdapter):
    """Adapter for HatchDX agent commands.

    Reads ``agent_path`` from :class:`ToolConfig` and launches
    ``hdx agent chat <agent_name>`` as a subprocess.

    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 "hdx-agent"

    @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]:
        agent_path = config.agent_path
        if not agent_path:
            raise ValueError(
                "HatchDX adapter requires 'agent_path' in tool config. "
                "Example: tools.hdx-agent.agent_path: './agents/my-agent'"
            )
        agent_name = PurePosixPath(agent_path).name
        return ["hdx", "agent", "chat", agent_name]

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

    @property
    def install_hint(self) -> str:
        return "Install HatchDX: pip install hatchdx (or see https://hatchdx.dev)"

    def _parse_cost(self, output: str) -> TokenUsage | None:
        """Parse token/cost data from HatchDX agent output."""
        if not output:
            return None

        total_tokens = 0
        cost = 0.0
        model = "unknown"

        m = _HDX_TOKENS_RE.search(output)
        if m:
            total_tokens = int(m.group(1).replace(",", ""))

        if total_tokens == 0:
            return None

        m = _HDX_COST_RE.search(output)
        if m:
            cost = float(m.group(1).replace("$", "").replace(",", ""))

        m = _HDX_MODEL_RE.search(output)
        if m:
            model = m.group(1)

        return TokenUsage(
            tool=self.name,
            input_tokens=0,
            output_tokens=0,
            total_tokens=total_tokens,
            estimated_cost_usd=cost,
            model=model,
        )

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

        Returns ``(exit_code, captured_output)``.
        """
        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

        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 HatchDX 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,
        )
