"""Synchronous HTTP client for Sprocket to talk to the Peon REST API.

Same pattern as ``grukk/api.py`` — uses ``urllib.request`` for zero external
dependencies and shares the failure-suppression logic.
"""

from __future__ import annotations

import json
import logging
import urllib.error
import urllib.parse
import urllib.request
from typing import Any

log = logging.getLogger("peon-goblin")

# Track consecutive failures per URL to suppress log spam.
_failure_counts: dict[str, int] = {}
_SUPPRESS_AFTER = 5


class GoblinAPIClient:
    """Synchronous HTTP client for the Peon REST API.

    Parameters
    ----------
    api_url:
        Base URL for the Peon REST API (e.g. ``http://127.0.0.1:8420``).
    api_key:
        Optional API key for ``X-API-Key`` authentication.
    """

    def __init__(self, api_url: str, api_key: str = "") -> None:
        self.api_url = api_url.rstrip("/")
        self.api_key = api_key

    # ------------------------------------------------------------------
    # Core request helper
    # ------------------------------------------------------------------

    def _request(
        self,
        method: str,
        path: str,
        payload: dict | None = None,
        timeout: int = 10,
        *,
        quiet_statuses: set[int] | None = None,
    ) -> dict | list | None:
        """Make a synchronous HTTP request, returning parsed JSON or *None* on failure."""
        url = f"{self.api_url}{path}"
        body = json.dumps(payload).encode("utf-8") if payload is not None else None
        headers: dict[str, str] = {"Content-Type": "application/json"}
        if self.api_key:
            headers["X-API-Key"] = self.api_key

        req = urllib.request.Request(url, data=body, headers=headers, method=method)
        try:
            with urllib.request.urlopen(req, timeout=timeout) as resp:
                data = json.loads(resp.read())
                _failure_counts.pop(url, None)
                return data
        except urllib.error.HTTPError as exc:
            if quiet_statuses and exc.code in quiet_statuses:
                return None
            _failure_counts[url] = _failure_counts.get(url, 0) + 1
            if _failure_counts[url] <= _SUPPRESS_AFTER:
                log.warning("API call failed (%s %s): HTTP %d", method, url, exc.code)
            elif _failure_counts[url] == _SUPPRESS_AFTER + 1:
                log.warning(
                    "API call failed (%s %s): HTTP %d (suppressing further warnings)",
                    method, url, exc.code,
                )
            return None
        except (urllib.error.URLError, json.JSONDecodeError, OSError) as exc:
            _failure_counts[url] = _failure_counts.get(url, 0) + 1
            if _failure_counts[url] <= _SUPPRESS_AFTER:
                log.warning("API call failed (%s %s): %s", method, url, exc)
            elif _failure_counts[url] == _SUPPRESS_AFTER + 1:
                log.warning(
                    "API call failed (%s %s): %s (suppressing further warnings)",
                    method, url, exc,
                )
            return None

    # ------------------------------------------------------------------
    # Leads
    # ------------------------------------------------------------------

    def get_unresearched_leads(self, project_id: str, limit: int = 3) -> list[dict]:
        """Fetch leads that haven't been researched yet.

        Retrieves leads from ``GET /api/projects/{project_id}/leads`` and
        filters client-side for leads where ``metadata`` has no
        ``researched_at`` field.
        """
        # Fetch a larger batch so we have enough after filtering.
        encoded_id = urllib.parse.quote(project_id, safe="")
        # The project_id is owner/repo, split for the URL path.
        parts = project_id.split("/", 1)
        if len(parts) == 2:
            path = f"/api/projects/{urllib.parse.quote(parts[0], safe='')}/{urllib.parse.quote(parts[1], safe='')}/leads?limit=200"
        else:
            path = f"/api/projects/{encoded_id}/leads?limit=200"

        result = self._request("GET", path)

        items: list[dict] = []
        if isinstance(result, dict):
            items = result.get("items", [])
        elif isinstance(result, list):
            items = result

        # Filter for unresearched leads.
        unresearched: list[dict] = []
        for lead in items:
            metadata = lead.get("metadata") or {}
            if not metadata.get("researched_at"):
                unresearched.append(lead)
                if len(unresearched) >= limit:
                    break

        return unresearched

    def update_lead_metadata(self, lead_id: str, project_id: str, metadata: dict) -> dict | None:
        """Update a lead's metadata via ``PATCH /api/projects/{project_id}/leads/{lead_id}``."""
        parts = project_id.split("/", 1)
        if len(parts) == 2:
            path = f"/api/projects/{urllib.parse.quote(parts[0], safe='')}/{urllib.parse.quote(parts[1], safe='')}/leads/{lead_id}"
        else:
            path = f"/api/projects/{urllib.parse.quote(project_id, safe='')}/leads/{lead_id}"

        payload = {"metadata": metadata}
        return self._request("PATCH", path, payload)

    # ------------------------------------------------------------------
    # Discord
    # ------------------------------------------------------------------

    def send_discord_message(
        self,
        config_id: int,
        channel_id: str,
        content: str,
    ) -> dict | None:
        """Send a message to a Discord channel via ``POST /api/discord/configs/{config_id}/send``."""
        payload = {
            "channel_id": channel_id,
            "content": content,
        }
        return self._request("POST", f"/api/discord/configs/{config_id}/send", payload)
