"""Fetch AI-related specs from ETSI (no auth needed, free PDFs).

ETSI has no REST API — we scrape the standards search page and download PDFs.
Focus on SAI (Securing AI) and ENI (Experiential Networked Intelligence) groups.
"""

from __future__ import annotations

import re
import time as time_mod

import httpx
from rich.console import Console
from rich.progress import (
    BarColumn,
    MofNCompleteColumn,
    Progress,
    SpinnerColumn,
    TextColumn,
)

from ..config import Config
from .base import SourceDocument

console = Console()

# ETSI portal search endpoint (returns HTML)
ETSI_SEARCH_URL = "https://www.etsi.org/standards-search"

# Known AI-relevant ETSI technical bodies and their spec prefixes
ETSI_AI_GROUPS = {
    "SAI": "Securing Artificial Intelligence",
    "ENI": "Experiential Networked Intelligence",
}

# Direct catalog of known ETSI AI specs (bootstrap — extend via search)
# Format: (doc_id, title, group, url_path)
ETSI_AI_CATALOG = [
    # SAI — Securing AI
    ("ETSI GR SAI 001", "AI Threat Ontology", "SAI",
     "etsi_gr/SAI/001_099/001/01.01.01_60/gr_SAI001v010101p.pdf"),
    ("ETSI GR SAI 002", "Data Supply Chain Security", "SAI",
     "etsi_gr/SAI/001_099/002/01.01.01_60/gr_SAI002v010101p.pdf"),
    ("ETSI GR SAI 003", "Security Testing of AI", "SAI",
     "etsi_gr/SAI/001_099/003/01.01.01_60/gr_SAI003v010101p.pdf"),
    ("ETSI GR SAI 004", "Problem Statement on AI and Automated Decision Making", "SAI",
     "etsi_gr/SAI/001_099/004/01.01.01_60/gr_SAI004v010101p.pdf"),
    ("ETSI GR SAI 005", "Mitigation Strategy Report", "SAI",
     "etsi_gr/SAI/001_099/005/01.01.01_60/gr_SAI005v010101p.pdf"),
    ("ETSI GR SAI 006", "Role of Hardware in AI Security", "SAI",
     "etsi_gr/SAI/001_099/006/01.01.01_60/gr_SAI006v010101p.pdf"),
    ("ETSI EN 304 223", "Baseline Cyber Security Requirements for AI Models and Systems", "SAI",
     "etsi_en/304200_304299/304223/02.01.01_60/en_304223v020101p.pdf"),
    # ENI — Experiential Networked Intelligence
    ("ETSI GS ENI 001", "ENI Use Cases", "ENI",
     "etsi_gs/ENI/001_099/001/03.01.01_60/gs_ENI001v030101p.pdf"),
    ("ETSI GS ENI 002", "ENI Requirements", "ENI",
     "etsi_gs/ENI/001_099/002/03.01.01_60/gs_ENI002v030101p.pdf"),
    ("ETSI GS ENI 005", "System Architecture", "ENI",
     "etsi_gs/ENI/001_099/005/02.01.01_60/gs_ENI005v020101p.pdf"),
    ("ETSI GR ENI 007", "ENI Definition of Categories for AI Application to Networks", "ENI",
     "etsi_gr/ENI/001_099/007/01.01.01_60/gr_ENI007v010101p.pdf"),
    ("ETSI GS ENI 019", "Representing, Inferring, and Applying Context Information", "ENI",
     "etsi_gs/ENI/001_099/019/02.01.01_60/gs_ENI019v020101p.pdf"),
]

ETSI_DELIVER_BASE = "https://www.etsi.org/deliver/"


def _doc_id_to_name(doc_id: str) -> str:
    """Convert ETSI doc ID to a slug name. E.g. 'ETSI GR SAI 001' -> 'etsi-gr-sai-001'."""
    return doc_id.lower().replace(" ", "-").replace("/", "-")


class ETSIFetcher:
    """Fetch AI-related specs from ETSI.

    Uses a curated catalog of known SAI/ENI specs plus keyword search
    on the ETSI portal for discovery.
    """

    def __init__(self, config: Config | None = None):
        self.config = config or Config.load()
        self.client = httpx.Client(timeout=30, follow_redirects=True)

    def search(
        self, keywords: list[str], since: str | None = None
    ) -> list[SourceDocument]:
        """Return AI-relevant ETSI specs from catalog + keyword search."""
        seen: dict[str, SourceDocument] = {}

        # Strategy 1: Curated catalog of known AI specs
        console.print("  Loading ETSI AI catalog (SAI + ENI)...")
        for doc_id, title, group, url_path in ETSI_AI_CATALOG:
            name = _doc_id_to_name(doc_id)
            url = f"{ETSI_DELIVER_BASE}{url_path}"
            seen[name] = SourceDocument(
                name=name,
                title=f"{doc_id}: {title}",
                abstract=f"ETSI {group} specification: {title}",
                source="etsi",
                source_id=doc_id,
                source_url=url,
                time="",
                doc_status="published",
                extra={"group": group},
            )

        # Strategy 2: Search ETSI portal for additional AI specs
        console.print("  Searching ETSI portal for AI-related specs...")
        search_terms = ["artificial intelligence", "machine learning", "autonomous", "neural network"]
        for term in search_terms:
            try:
                resp = self.client.get(
                    ETSI_SEARCH_URL,
                    params={"search": term, "page": 1, "size": 50, "sort": "date_desc"},
                    headers={"Accept": "text/html"},
                )
                if resp.status_code == 200:
                    # Parse titles and links from search results
                    new_docs = self._parse_search_results(resp.text, keywords)
                    for doc in new_docs:
                        if doc.name not in seen:
                            seen[doc.name] = doc
                time_mod.sleep(0.5)
            except httpx.HTTPError as e:
                console.print(f"[yellow]ETSI search error for '{term}': {e}[/]")

        console.print(f"  Found [bold green]{len(seen)}[/] ETSI specs")
        return list(seen.values())

    def _parse_search_results(self, html: str, keywords: list[str]) -> list[SourceDocument]:
        """Parse ETSI search results HTML for spec links and titles."""
        docs = []
        # ETSI search results contain links like /deliver/etsi_gr/SAI/...
        # and titles in specific patterns. This is best-effort parsing.
        kw_lower = [k.lower() for k in keywords]

        # Look for PDF download links in the HTML
        pattern = re.compile(
            r'href="(/deliver/[^"]+\.pdf)"[^>]*>.*?</a>.*?'
            r'<[^>]*class="[^"]*title[^"]*"[^>]*>([^<]+)',
            re.DOTALL | re.IGNORECASE,
        )
        for match in pattern.finditer(html):
            url_path = match.group(1)
            title = match.group(2).strip()

            if not any(kw in title.lower() for kw in kw_lower):
                continue

            # Extract doc ID from path
            doc_id = url_path.split("/")[-1].replace(".pdf", "").replace("_", " ").upper()
            name = f"etsi-{url_path.split('/')[-1].replace('.pdf', '').lower()}"

            docs.append(SourceDocument(
                name=name,
                title=title,
                abstract=title,
                source="etsi",
                source_id=doc_id,
                source_url=f"https://www.etsi.org{url_path}",
                time="",
                doc_status="published",
            ))
        return docs

    def download_text(self, doc: SourceDocument) -> str | None:
        """Download PDF and extract text (best-effort)."""
        url = doc.source_url
        if not url or not url.endswith(".pdf"):
            return None
        try:
            resp = self.client.get(url)
            resp.raise_for_status()
            # We get a PDF — try to extract text with pdfminer if available
            try:
                from io import BytesIO
                from pdfminer.high_level import extract_text
                text = extract_text(BytesIO(resp.content))
                return text[:100000] if text else None
            except ImportError:
                # No pdfminer — store as placeholder
                console.print(f"[dim]pdfminer not installed, cannot extract text from {doc.name}[/]")
                return f"[PDF document: {doc.title}. Install pdfminer.six to extract text.]"
        except httpx.HTTPError as e:
            console.print(f"[dim]Could not download {doc.name}: {e}[/]")
            return None

    def close(self) -> None:
        self.client.close()
