"""Oghma Data Ingestion - Loads CHIM's Oghma lore into ChromaDB."""

from __future__ import annotations

import argparse
import io
import re
import sys
import time
from typing import Iterator

import chromadb
import httpx
import structlog
from chromadb.config import Settings

logger = structlog.get_logger()

# Google Sheet ID for CHIM's Oghma Infinium
OGHMA_SHEET_ID = "1dcfctU-iOqprwy2BOc7___4Awteczgdlv8886KalPsQ"


def discover_sheet_gids(sheet_id: str) -> dict[str, str]:
    """
    Discover actual gids for all sheets by parsing the HTML page.

    Returns:
        Dict mapping sheet name to gid
    """
    url = f"https://docs.google.com/spreadsheets/d/{sheet_id}/htmlview"

    with httpx.Client(follow_redirects=True, timeout=30.0) as client:
        response = client.get(url)
        response.raise_for_status()
        html = response.text

    sheet_gids = {}

    # Pattern 1: items.push({name: "Sheet Name", ...gid: "12345"...})
    # This is the format Google Sheets uses in the htmlview page
    for match in re.finditer(
        r'items\.push\(\{name:\s*"([^"]+)"[^}]*gid:\s*"(\d+)"',
        html
    ):
        name = match.group(1)
        gid = match.group(2)
        sheet_gids[name] = gid

    # Pattern 2: Also check for gid in URL patterns as backup
    # ...gid=12345", gid: "12345"...
    if not sheet_gids:
        for match in re.finditer(r'gid=(\d+)"[^}]*gid:\s*"(\d+)"', html):
            gid = match.group(2)
            # Try to find associated name nearby
            # This is a fallback pattern

    # Pattern 3: Look for the sheet tabs JSON structure
    for match in re.finditer(
        r'\{name:\s*"([^"]+)"[^}]*gid:\s*"(\d+)"[^}]*\}',
        html
    ):
        name = match.group(1)
        gid = match.group(2)
        if name not in sheet_gids:
            sheet_gids[name] = gid

    logger.info("Discovered sheets", count=len(sheet_gids), sheets=list(sheet_gids.keys())[:10])
    return sheet_gids


def fetch_sheet_csv(sheet_id: str, gid: str, sheet_name: str = "") -> str:
    """Fetch a Google Sheet as CSV."""
    url = f"https://docs.google.com/spreadsheets/d/{sheet_id}/export?format=csv&gid={gid}"

    with httpx.Client(follow_redirects=True, timeout=60.0) as client:
        response = client.get(url)
        if response.status_code == 400:
            logger.warning("Sheet fetch failed with 400", sheet=sheet_name, gid=gid)
            raise httpx.HTTPStatusError(
                f"Failed to fetch sheet {sheet_name}",
                request=response.request,
                response=response,
            )
        response.raise_for_status()
        return response.text


def parse_csv(csv_text: str) -> Iterator[dict]:
    """Parse CSV text into rows."""
    import csv

    reader = csv.DictReader(io.StringIO(csv_text))
    for row in reader:
        yield row


def categorize_sheet(sheet_name: str) -> str | None:
    """
    Determine category for a sheet and whether to process it.

    Returns category name or None if sheet should be skipped.
    """
    # Normalize escaped slashes from JSON
    normalized_name = sheet_name.replace("\\/", "/").replace("\\", "")

    # Sheets to process and their categories
    sheet_categories = {
        "Figures/Gods": "figures_gods",
        "Artifacts": "artifacts",
        "Armor and Weapons": "armor_weapons",
        "Items": "items",
        "Spells": "spells",
        "Creatures": "creatures",
        "Groups/Lore/Books": "groups_lore",
        "Dynamic Oghma": "dynamic",
        "Visual Descriptions": "visual",
    }

    # Check direct match
    if normalized_name in sheet_categories:
        return sheet_categories[normalized_name]

    # Check location sheets - handles both "Locations - Whiterun" and "Locations (Whiterun)"
    if normalized_name.startswith("Locations"):
        # Try pattern: "Locations (Whiterun)"
        match = re.match(r"Locations\s*[\(\-]\s*([^\)]+)\)?", normalized_name)
        if match:
            hold = match.group(1).strip().lower().replace(" ", "_")
            return f"locations_{hold}"

    # Skip meta/reference sheets
    skip_sheets = ["Project Oghma", "Knowledge Classes Reference", "Vanilla NPCS", "Template"]
    if any(skip in normalized_name for skip in skip_sheets):
        return None

    return None


def ingest_oghma(
    chromadb_host: str = "iris-dev.eachpath.local",
    chromadb_port: int = 35000,
    dry_run: bool = False,
) -> dict:
    """
    Ingest all Oghma sheets into ChromaDB.

    Returns:
        Statistics about ingestion
    """
    stats = {
        "sheets_processed": 0,
        "sheets_skipped": 0,
        "lore_entries": 0,
        "basic_entries": 0,
        "visual_entries": 0,
        "errors": [],
    }

    # Discover actual sheet gids
    logger.info("Discovering sheet gids...")
    try:
        sheet_gids = discover_sheet_gids(OGHMA_SHEET_ID)
    except Exception as e:
        logger.error("Failed to discover sheets", error=str(e))
        # Fallback to known gids (manually discovered)
        sheet_gids = {
            "Figures/Gods": "0",
            # Add more as we discover them
        }

    if not sheet_gids:
        logger.error("No sheets discovered!")
        return stats

    if not dry_run:
        client = chromadb.HttpClient(
            host=chromadb_host,
            port=chromadb_port,
            settings=Settings(anonymized_telemetry=False),
        )

        # Get or create collections
        collection_lore = client.get_or_create_collection(
            name="oghma_lore",
            metadata={"description": "Full Tamrielic lore for educated NPCs"},
        )
        collection_basic = client.get_or_create_collection(
            name="oghma_basic",
            metadata={"description": "Basic Tamrielic lore for commoners"},
        )
        collection_visual = client.get_or_create_collection(
            name="oghma_visual",
            metadata={"description": "Visual descriptions for Omnisight perception"},
        )

        logger.info("Connected to ChromaDB", host=chromadb_host, port=chromadb_port)
    else:
        logger.info("DRY RUN - not connecting to ChromaDB")
        collection_lore = None
        collection_basic = None
        collection_visual = None

    for sheet_name, gid in sheet_gids.items():
        category = categorize_sheet(sheet_name)

        if category is None:
            logger.debug("Skipping sheet", sheet=sheet_name)
            stats["sheets_skipped"] += 1
            continue

        logger.info("Processing sheet", sheet=sheet_name, gid=gid, category=category)

        # Rate limit to avoid Google blocking
        time.sleep(1.0)

        try:
            csv_text = fetch_sheet_csv(OGHMA_SHEET_ID, gid, sheet_name)
            rows = list(parse_csv(csv_text))

            if not rows:
                logger.warning("Empty sheet", sheet=sheet_name)
                continue

            # Check if this sheet has the expected columns
            sample_row = rows[0]
            is_visual_sheet = category == "visual"

            # Visual Descriptions has different schema: baseid, name, description
            if is_visual_sheet:
                if "name" not in sample_row or "description" not in sample_row:
                    logger.warning("Visual sheet missing expected columns", sheet=sheet_name, columns=list(sample_row.keys())[:5])
                    continue
            elif "topic" not in sample_row:
                logger.warning("Sheet missing 'topic' column", sheet=sheet_name, columns=list(sample_row.keys())[:5])
                continue

            lore_docs = []
            lore_ids = []
            lore_metadatas = []

            basic_docs = []
            basic_ids = []
            basic_metadatas = []

            # Visual descriptions - universal perception (Omnisight)
            visual_docs = []
            visual_ids = []
            visual_metadatas = []

            # Track seen IDs to handle duplicates
            seen_ids = set()
            duplicates_skipped = 0

            for row in rows:
                # Handle visual descriptions separately
                if is_visual_sheet:
                    name = row.get("name", "").strip()
                    description = row.get("description", "").strip()
                    baseid = row.get("baseid", "").strip()

                    # Clean up Excel scientific notation for zero (0.00E+00)
                    if baseid and ("E+" in baseid or "E-" in baseid):
                        try:
                            if float(baseid) == 0:
                                baseid = ""
                        except ValueError:
                            pass

                    if name and description:
                        # Use baseid:name for uniqueness, fall back to name only
                        doc_id = f"visual:{baseid}:{name}" if baseid else f"visual:{name}"

                        if doc_id in seen_ids:
                            duplicates_skipped += 1
                            continue
                        seen_ids.add(doc_id)

                        visual_docs.append(description)
                        visual_ids.append(doc_id)
                        visual_metadatas.append({
                            "name": name,
                            "baseid": baseid,
                            "category": "visual",
                            "sheet": sheet_name,
                        })
                    continue

                topic = row.get("topic", "").strip()
                if not topic:
                    continue

                # Full lore entry
                topic_desc = row.get("topic_desc", "").strip()
                if topic_desc:
                    lore_id = f"{category}:{topic}"

                    if lore_id in seen_ids:
                        duplicates_skipped += 1
                    else:
                        seen_ids.add(lore_id)
                        knowledge_classes = row.get("knowledge_class", "").strip()

                        lore_docs.append(topic_desc)
                        lore_ids.append(lore_id)
                        lore_metadatas.append({
                            "topic": topic,
                            "category": category,
                            "sheet": sheet_name,
                            "knowledge_classes": knowledge_classes,
                            "tags": row.get("tags", "").strip(),
                        })

                # Basic lore entry
                topic_desc_basic = row.get("topic_desc_basic", "").strip()
                if topic_desc_basic:
                    basic_id = f"{category}:{topic}:basic"

                    if basic_id in seen_ids:
                        duplicates_skipped += 1
                    else:
                        seen_ids.add(basic_id)
                        knowledge_classes_basic = row.get("knowledge_class_basic", "").strip()

                        basic_docs.append(topic_desc_basic)
                        basic_ids.append(basic_id)
                        basic_metadatas.append({
                            "topic": topic,
                            "category": category,
                            "sheet": sheet_name,
                            "knowledge_classes": knowledge_classes_basic,
                            "tags": row.get("tags", "").strip(),
                        })

            # Batch insert to ChromaDB
            if not dry_run:
                if lore_docs:
                    collection_lore.upsert(
                        documents=lore_docs,
                        ids=lore_ids,
                        metadatas=lore_metadatas,
                    )
                if basic_docs:
                    collection_basic.upsert(
                        documents=basic_docs,
                        ids=basic_ids,
                        metadatas=basic_metadatas,
                    )
                if visual_docs:
                    collection_visual.upsert(
                        documents=visual_docs,
                        ids=visual_ids,
                        metadatas=visual_metadatas,
                    )

            stats["sheets_processed"] += 1
            stats["lore_entries"] += len(lore_docs)
            stats["basic_entries"] += len(basic_docs)
            stats["visual_entries"] += len(visual_docs)

            if duplicates_skipped > 0:
                logger.debug("Duplicates skipped", sheet=sheet_name, count=duplicates_skipped)

            logger.info(
                "Sheet processed",
                sheet=sheet_name,
                rows=len(rows),
                lore_entries=len(lore_docs),
                basic_entries=len(basic_docs),
                visual_entries=len(visual_docs),
            )

        except httpx.HTTPStatusError as e:
            logger.error("HTTP error fetching sheet", sheet=sheet_name, status=e.response.status_code)
            stats["errors"].append({"sheet": sheet_name, "error": f"HTTP {e.response.status_code}"})
        except Exception as e:
            logger.error("Failed to process sheet", sheet=sheet_name, error=str(e))
            stats["errors"].append({"sheet": sheet_name, "error": str(e)})

    logger.info(
        "Ingestion complete",
        sheets_processed=stats["sheets_processed"],
        sheets_skipped=stats["sheets_skipped"],
        lore_entries=stats["lore_entries"],
        basic_entries=stats["basic_entries"],
        visual_entries=stats["visual_entries"],
        errors=len(stats["errors"]),
    )

    return stats


def main():
    """CLI entry point."""
    parser = argparse.ArgumentParser(
        description="Ingest CHIM's Oghma Infinium lore into ChromaDB"
    )
    parser.add_argument(
        "--host",
        default="iris-dev.eachpath.local",
        help="ChromaDB host",
    )
    parser.add_argument(
        "--port",
        type=int,
        default=35000,
        help="ChromaDB port",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Fetch and parse sheets without writing to ChromaDB",
    )

    args = parser.parse_args()

    # Configure logging
    structlog.configure(
        processors=[
            structlog.stdlib.add_log_level,
            structlog.processors.TimeStamper(fmt="iso"),
            structlog.dev.ConsoleRenderer(),
        ],
    )

    try:
        stats = ingest_oghma(
            chromadb_host=args.host,
            chromadb_port=args.port,
            dry_run=args.dry_run,
        )

        if stats["errors"]:
            logger.warning("Ingestion completed with errors", errors=stats["errors"])
            # Don't exit 1 if we processed some sheets successfully
            if stats["sheets_processed"] == 0:
                sys.exit(1)

    except Exception as e:
        logger.error("Ingestion failed", error=str(e))
        sys.exit(1)


if __name__ == "__main__":
    main()
