"""Target connector — sends prompts to the target LLM system and extracts responses."""

from __future__ import annotations

import asyncio
import time
from typing import Any

import httpx

from llm_pentest.models import AuthType, TargetConfig


def _resolve_path(obj: Any, path: str) -> str:
    """Extract a value from a nested dict/list using dot/bracket notation."""
    for part in path.replace("[", ".").replace("]", "").split("."):
        if not part:
            continue
        if isinstance(obj, list):
            obj = obj[int(part)]
        else:
            obj = obj[part]
    return str(obj)


def _build_body(template: dict[str, Any], messages: list[dict[str, str]]) -> dict[str, Any]:
    """Replace {{messages}} placeholder in body template."""
    body: dict[str, Any] = {}
    for k, v in template.items():
        if v == "{{messages}}":
            body[k] = messages
        else:
            body[k] = v
    return body


class TargetConnector:
    def __init__(self, config: TargetConfig):
        self.config = config
        self._min_interval = 1.0 / config.rate_limit if config.rate_limit > 0 else 0
        self._last_request = 0.0

    def _auth_headers(self) -> dict[str, str]:
        auth = self.config.auth
        if auth.type == AuthType.bearer:
            return {"Authorization": f"Bearer {auth.token}"}
        if auth.type == AuthType.header:
            return {auth.header_name or "X-Api-Key": auth.header_value or ""}
        return {}

    async def send(self, prompt: str, history: list[dict[str, str]] | None = None) -> str:
        # Rate limiting
        elapsed = time.monotonic() - self._last_request
        if elapsed < self._min_interval:
            await asyncio.sleep(self._min_interval - elapsed)

        messages = list(history or [])
        messages.append({"role": "user", "content": prompt})

        body = _build_body(self.config.request.body_template, messages)
        headers = {**self.config.request.headers, **self._auth_headers()}

        async with httpx.AsyncClient(timeout=self.config.timeout) as client:
            resp = await client.request(
                self.config.request.method,
                self.config.endpoint,
                json=body,
                headers=headers,
            )
            resp.raise_for_status()

        self._last_request = time.monotonic()
        return _resolve_path(resp.json(), self.config.response_path)
