"""Supabase REST API client using httpx.

Talks directly to the PostgREST endpoint at {supabase_url}/rest/v1/.
No supabase SDK required.

Supports two authentication modes:
- Admin: service_role key (full access)
- Team member: anon key + personal API key (RLS-controlled access)
"""

from __future__ import annotations

import re
from collections import Counter

import httpx

from sphinx_lookup.config import LookupConfig


_UUID_RE = re.compile(
    r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE
)


class LookupClient:
    """Lightweight Supabase PostgREST client for attack lookups."""

    def __init__(self, config: LookupConfig) -> None:
        self.base_url = config.supabase_url.rstrip("/") + "/rest/v1"
        self._config = config

        # Build headers based on auth mode
        if config.supabase_key:
            # Admin mode: service_role key (bypasses RLS)
            self._headers = {
                "apikey": config.supabase_key,
                "Authorization": f"Bearer {config.supabase_key}",
                "Content-Type": "application/json",
            }
        elif config.anon_key and config.api_key:
            # Team member mode: anon key + personal API key
            # anon key for Supabase auth, x-api-key for RLS policy
            self._headers = {
                "apikey": config.anon_key,
                "Authorization": f"Bearer {config.anon_key}",
                "Content-Type": "application/json",
                "x-api-key": config.api_key,
            }
        else:
            raise ValueError("No valid authentication configured")

    def _client(self, **kwargs: object) -> httpx.Client:
        return httpx.Client(
            base_url=self.base_url,
            headers=self._headers,
            timeout=30.0,
            **kwargs,
        )

    # ------------------------------------------------------------------
    # Queries
    # ------------------------------------------------------------------

    def search(self, query: str, limit: int = 20) -> list[dict]:
        """Search attacks by name or description (ILIKE)."""
        q = query.replace(" ", "*")
        params = {
            "or": f"(name.ilike.*{q}*,description.ilike.*{q}*)",
            "select": "id,name,category,cvss_estimate,confidence_score,first_seen,modality_tags",
            "order": "first_seen.desc",
            "limit": str(limit),
        }
        with self._client() as client:
            resp = client.get("/attacks", params=params)
            resp.raise_for_status()
            return resp.json()

    def list_attacks(
        self,
        category: str | None = None,
        modality: str | None = None,
        limit: int = 20,
        offset: int = 0,
    ) -> list[dict]:
        """List attacks with optional filters."""
        params: dict[str, str] = {
            "select": "id,name,category,cvss_estimate,confidence_score,first_seen,modality_tags,"
                      "target_model_families",
            "order": "first_seen.desc",
            "limit": str(limit),
            "offset": str(offset),
        }
        if category:
            params["category"] = f"eq.{category}"
        if modality:
            params["modality_tags"] = f"cs.{{{modality}}}"
        with self._client() as client:
            resp = client.get("/attacks", params=params)
            resp.raise_for_status()
            return resp.json()

    def get_attack(self, attack_id: str) -> dict | None:
        """Get a single attack by exact UUID or prefix match.

        PostgREST only supports eq/neq/in on UUID columns (no LIKE),
        so for prefix lookups we fetch all IDs and match client-side,
        then retrieve the full record by exact UUID.
        """
        # Embed sources via the attack_sources junction table.
        select = "*,attack_sources(sources(url,title,authors,published_date))"

        with self._client() as client:
            # Full UUID — direct lookup
            if _UUID_RE.match(attack_id):
                resp = client.get(
                    "/attacks", params={"id": f"eq.{attack_id}", "select": select, "limit": "1"},
                )
                resp.raise_for_status()
                data = resp.json()
                if data:
                    return data[0]
                return None

            # Prefix — paginate through all IDs (Supabase caps at 1000/request),
            # match client-side, then get full record.
            prefix = attack_id.lower()
            match_id = None
            page_size = 1000
            offset = 0

            while match_id is None:
                resp = client.get(
                    "/attacks",
                    params={"select": "id", "limit": str(page_size), "offset": str(offset)},
                )
                resp.raise_for_status()
                rows = resp.json()
                if not rows:
                    break

                match_id = next(
                    (r["id"] for r in rows if str(r["id"]).lower().startswith(prefix)),
                    None,
                )
                offset += page_size

            if not match_id:
                return None

            resp = client.get(
                "/attacks", params={"id": f"eq.{match_id}", "select": select, "limit": "1"},
            )
            resp.raise_for_status()
            data = resp.json()
            return data[0] if data else None

    def get_categories(self) -> list[dict]:
        """Get distinct categories with counts (client-side grouping)."""
        with self._client() as client:
            resp = client.get("/attacks", params={"select": "category"})
            resp.raise_for_status()
            rows = resp.json()

        counts = Counter(row["category"] for row in rows)
        return [
            {"category": cat, "count": cnt}
            for cat, cnt in counts.most_common()
        ]

    def get_stats(self) -> dict:
        """Get database statistics: totals, category breakdown, quality distribution."""
        with self._client() as client:
            # Total count
            resp = client.get(
                "/attacks",
                params={"select": "id", "limit": "0"},
                headers={**self._headers, "Prefer": "count=exact"},
            )
            resp.raise_for_status()
            content_range = resp.headers.get("content-range", "")
            total = int(content_range.split("/")[-1]) if "/" in content_range else 0

            # Category breakdown
            resp = client.get("/attacks", params={"select": "category"})
            resp.raise_for_status()
            cats = Counter(r["category"] for r in resp.json())

            # CVSS distribution
            resp = client.get("/attacks", params={"select": "cvss_estimate"})
            resp.raise_for_status()
            cvss_values = [r["cvss_estimate"] for r in resp.json() if r.get("cvss_estimate")]
            cvss_buckets: dict[str, int] = {}
            for v in cvss_values:
                bucket = f"{int(v)}-{int(v) + 1}"
                cvss_buckets[bucket] = cvss_buckets.get(bucket, 0) + 1

            # Confidence distribution
            resp = client.get("/attacks", params={"select": "confidence_score"})
            resp.raise_for_status()
            conf_values = [r["confidence_score"] for r in resp.json() if r.get("confidence_score")]
            avg_confidence = sum(conf_values) / len(conf_values) if conf_values else 0

            # Modality breakdown
            resp = client.get("/attacks", params={"select": "modality_tags"})
            resp.raise_for_status()
            modalities: Counter[str] = Counter()
            for r in resp.json():
                for tag in r.get("modality_tags") or ["text"]:
                    modalities[tag] += 1

        return {
            "total": total,
            "categories": dict(cats.most_common()),
            "cvss_distribution": cvss_buckets,
            "avg_confidence": round(avg_confidence, 3),
            "modalities": dict(modalities.most_common()),
        }

    def count(self) -> int:
        """Total attack count."""
        with self._client() as client:
            resp = client.get(
                "/attacks",
                params={"select": "id", "limit": "0"},
                headers={**self._headers, "Prefer": "count=exact"},
            )
            resp.raise_for_status()
            content_range = resp.headers.get("content-range", "")
            return int(content_range.split("/")[-1]) if "/" in content_range else 0
