"""Global bibliography registry for all knowledge bases.

Stores structured bibliographic metadata (author, title, date, publisher, URL)
for every ingested source across all KBs. Supports BibTeX and RIS export.
"""

from __future__ import annotations

import json
import logging
import re
import threading
import unicodedata
from typing import Any

from agntspace.vault.reader import VaultReader
from agntspace.vault.writer import VaultWriter

log = logging.getLogger(__name__)

# Logical path — resolved by VaultPaths to agntspace-knowledge/bibliography.json
REGISTRY_PATH = "knowledge/bibliography.json"


# Cite keys are intentionally ASCII — BibTeX/RIS tooling across the
# ecosystem treats them as plain identifiers, and non-ASCII in `\cite{}`
# breaks many LaTeX pipelines. Transliteration (NFKD → ASCII) converts
# "Müller" → "muller" and "Grönberg" → "gronberg". Scripts that have no
# ASCII decomposition (Cyrillic, Greek, CJK, Arabic, Hebrew, Hindi, …)
# fall back to the generic "source" prefix below — the cite key stays
# valid even if the source language isn't Latin-based.
def _to_ascii_lower(text: str) -> str:
    """Transliterate any Unicode string to lowercase ASCII letters."""
    if not text:
        return ""
    decomposed = unicodedata.normalize("NFKD", text)
    ascii_bytes = decomposed.encode("ascii", "ignore")
    return re.sub(r"[^a-z]", "", ascii_bytes.decode("ascii").lower())


def _significant_words(title: str) -> list[str]:
    """Extract significant words from a title, language-agnostic.

    Uses a Unicode-aware word regex, filters short tokens (length < 3)
    as a language-agnostic proxy for "stop words" (the/an/of/in/et/le/…
    are all 2–3 characters in most European languages), then keeps only
    tokens that still have an ASCII form for cite-key inclusion.
    """
    # \w with the default Python 3 Unicode flag matches letters in any script
    words = re.findall(r"\w+", title, re.UNICODE)
    out: list[str] = []
    for w in words:
        if len(w) < 3:
            continue
        ascii_form = _to_ascii_lower(w)
        if ascii_form:
            out.append(ascii_form)
    return out


class BibliographyService:
    """Global bibliography registry — one registry across all KBs."""

    def __init__(self, vault: VaultReader, writer: VaultWriter) -> None:
        self._vault = vault
        self._writer = writer
        self._lock = threading.Lock()

    # ── persistence ──────────────────────────────────────────────

    def _load(self) -> list[dict[str, Any]]:
        try:
            doc = self._vault.read(REGISTRY_PATH)
            return json.loads(doc.content) if doc.content.strip() else []
        except Exception:
            return []

    def _save(self, entries: list[dict[str, Any]]) -> None:
        self._writer.write(REGISTRY_PATH, json.dumps(entries, indent=2, ensure_ascii=False))

    # ── cite key generation ──────────────────────────────────────

    @staticmethod
    def _generate_cite_key(
        title: str,
        authors: list[str],
        date: str,
        existing_keys: set[str],
    ) -> str:
        # Extract year
        year_match = re.search(r"(\d{4})", date or "")
        year = year_match.group(1) if year_match else "nd"

        # First author surname or first significant word of title.
        # Names and titles in any script are transliterated to ASCII via
        # _to_ascii_lower so cite keys remain LaTeX-safe identifiers.
        if authors and authors[0] and authors[0].lower() != "unknown":
            # Take last word as surname (handles "First Last" and "Last, First")
            name = authors[0].split(",")[0].strip() if "," in authors[0] else authors[0].split()[-1]
            prefix = _to_ascii_lower(name)[:12]
        else:
            sig_words = _significant_words(title)
            prefix = (sig_words[0] if sig_words else "source")[:12]

        # Fallback: if the name transliterated to nothing (non-Latin script
        # author with no ASCII form) use "source" so the key stays valid.
        if not prefix:
            prefix = "source"

        # First significant word of title for disambiguation
        sig_title = _significant_words(title)
        suffix = (sig_title[0] if sig_title else "")[:10]

        base = f"{prefix}{year}{suffix}"
        key = base
        i = 1
        while key in existing_keys:
            key = f"{base}{chr(96 + i)}" if i <= 26 else f"{base}{i}"
            i += 1
        return key

    # ── CRUD ─────────────────────────────────────────────────────

    def list_entries(
        self,
        kb: str | None = None,
        source_type: str | None = None,
    ) -> list[dict[str, Any]]:
        entries = self._load()
        if kb:
            entries = [e for e in entries if e.get("kb_slug") == kb]
        if source_type:
            entries = [e for e in entries if e.get("source_type") == source_type]
        return entries

    def get_entry(self, cite_key: str) -> dict[str, Any] | None:
        for e in self._load():
            if e.get("cite_key") == cite_key:
                return e
        return None

    def add_entry(self, entry: dict[str, Any]) -> dict[str, Any]:
        with self._lock:
            entries = self._load()
            existing_keys = {e["cite_key"] for e in entries}
            if "cite_key" not in entry or not entry["cite_key"]:
                entry["cite_key"] = self._generate_cite_key(
                    entry.get("title", ""),
                    entry.get("authors", []),
                    entry.get("date", ""),
                    existing_keys,
                )
            entries.append(entry)
            self._save(entries)
        return entry

    def update_entry(self, cite_key: str, updates: dict[str, Any]) -> dict[str, Any] | None:
        with self._lock:
            entries = self._load()
            for e in entries:
                if e.get("cite_key") == cite_key:
                    e.update(updates)
                    self._save(entries)
                    return e
        return None

    def delete_entry(self, cite_key: str) -> bool:
        with self._lock:
            entries = self._load()
            before = len(entries)
            entries = [e for e in entries if e.get("cite_key") != cite_key]
            if len(entries) < before:
                self._save(entries)
                return True
        return False

    def find_by_source(self, source_file: str, kb_slug: str) -> dict[str, Any] | None:
        for e in self._load():
            if e.get("source_file") == source_file and e.get("kb_slug") == kb_slug:
                return e
        return None

    # ── ingest integration ───────────────────────────────────────

    def register_from_ingest(self, metadata: dict[str, Any]) -> dict[str, Any]:
        """Create or update a bibliography entry from ingest metadata."""
        existing = self.find_by_source(
            metadata.get("source_file", ""),
            metadata.get("kb_slug", ""),
        )
        if existing:
            # Update with new metadata (keep cite_key stable)
            updates = {k: v for k, v in metadata.items() if v and k != "cite_key"}
            return self.update_entry(existing["cite_key"], updates) or existing
        return self.add_entry(metadata)

    # ── export formats ───────────────────────────────────────────

    @staticmethod
    def export_bibtex(entries: list[dict[str, Any]]) -> str:
        type_map = {
            "paper": "article",
            "article": "article",
            "data": "misc",
            "image": "misc",
            "code": "misc",
            "chat_history": "misc",
            "export": "misc",
            "transcript": "misc",
        }
        lines: list[str] = []
        for e in entries:
            bib_type = type_map.get(e.get("source_type", ""), "misc")
            key = e.get("cite_key", "unknown")
            lines.append(f"@{bib_type}{{{key},")

            # Authors
            authors = e.get("authors", [])
            if authors and authors != ["unknown"]:
                lines.append(f"  author = {{{' and '.join(authors)}}},")

            title = e.get("title", "")
            if title:
                lines.append(f"  title = {{{title}}},")

            date = e.get("date", "")
            year_match = re.search(r"(\d{4})", date)
            if year_match:
                lines.append(f"  year = {{{year_match.group(1)}}},")

            url = e.get("url", "")
            if url:
                lines.append(f"  url = {{{url}}},")

            publisher = e.get("publisher", "")
            if publisher and publisher != "unknown":
                lines.append(f"  publisher = {{{publisher}}},")

            doi = e.get("doi", "")
            if doi:
                lines.append(f"  doi = {{{doi}}},")

            volume = e.get("volume", "")
            if volume:
                lines.append(f"  volume = {{{volume}}},")

            number = e.get("issue", "")
            if number:
                lines.append(f"  number = {{{number}}},")

            pages = e.get("pages", "")
            if pages:
                lines.append(f"  pages = {{{pages}}},")

            issn = e.get("issn", "")
            if issn:
                lines.append(f"  issn = {{{issn}}},")

            isbn = e.get("isbn", "")
            if isbn:
                lines.append(f"  isbn = {{{isbn}}},")

            language = e.get("language", "")
            if language:
                lines.append(f"  language = {{{language}}},")

            kb = e.get("kb_slug", "")
            if kb:
                lines.append(f"  note = {{Ingested from KB: {kb}}},")

            lines.append("}")
            lines.append("")
        return "\n".join(lines)

    @staticmethod
    def export_ris(entries: list[dict[str, Any]]) -> str:
        type_map = {
            "paper": "JOUR",
            "article": "JOUR",
            "data": "DATA",
            "image": "FIGURE",
            "code": "COMP",
            "chat_history": "GEN",
            "export": "GEN",
            "transcript": "GEN",
        }
        lines: list[str] = []
        for e in entries:
            ris_type = type_map.get(e.get("source_type", ""), "GEN")
            lines.append(f"TY  - {ris_type}")

            for author in e.get("authors", []):
                if author and author != "unknown":
                    lines.append(f"AU  - {author}")

            title = e.get("title", "")
            if title:
                lines.append(f"TI  - {title}")

            date = e.get("date", "")
            year_match = re.search(r"(\d{4})", date)
            if year_match:
                lines.append(f"PY  - {year_match.group(1)}")

            url = e.get("url", "")
            if url:
                lines.append(f"UR  - {url}")

            publisher = e.get("publisher", "")
            if publisher and publisher != "unknown":
                lines.append(f"PB  - {publisher}")

            doi = e.get("doi", "")
            if doi:
                lines.append(f"DO  - {doi}")

            volume = e.get("volume", "")
            if volume:
                lines.append(f"VL  - {volume}")

            issue = e.get("issue", "")
            if issue:
                lines.append(f"IS  - {issue}")

            pages = e.get("pages", "")
            if pages:
                # RIS uses SP (start page) and EP (end page)
                if "-" in pages:
                    sp, _, ep = pages.partition("-")
                    lines.append(f"SP  - {sp.strip()}")
                    lines.append(f"EP  - {ep.strip()}")
                else:
                    lines.append(f"SP  - {pages}")

            issn = e.get("issn", "")
            if issn:
                lines.append(f"SN  - {issn}")

            isbn = e.get("isbn", "")
            if isbn:
                lines.append(f"SN  - {isbn}")

            language = e.get("language", "")
            if language:
                lines.append(f"LA  - {language}")

            kb = e.get("kb_slug", "")
            if kb:
                lines.append(f"N1  - Ingested from KB: {kb}")

            lines.append("ER  - ")
            lines.append("")
        return "\n".join(lines)

    def stats(self) -> dict[str, Any]:
        entries = self._load()
        by_kb: dict[str, int] = {}
        by_type: dict[str, int] = {}
        for e in entries:
            kb = e.get("kb_slug", "unknown")
            by_kb[kb] = by_kb.get(kb, 0) + 1
            st = e.get("source_type", "unknown")
            by_type[st] = by_type.get(st, 0) + 1
        return {
            "total": len(entries),
            "by_kb": by_kb,
            "by_type": by_type,
        }
