"""Öffentliche API für die Lagebild-Seite auf aegissight.de.

Authentifizierung via X-API-Key Header (getrennt von der JWT-Auth).
Exponiert öffentliche Lagen als read-only.
"""
import json
import logging
import os
import time
from collections import defaultdict
from datetime import datetime
from config import TIMEZONE
from fastapi import APIRouter, Depends, Header, HTTPException, Request
from database import db_dependency

logger = logging.getLogger("osint.public_api")

router = APIRouter(prefix="/api/public", tags=["public"])

VALID_API_KEY = os.environ.get("AEGIS_PUBLIC_API_KEY")

# Alle Iran-Incident-IDs (Haupt-Incident #6 + Ableger)
IRAN_INCIDENT_IDS = [6, 18, 19, 20]
PRIMARY_INCIDENT_ID = 6

# Simple in-memory rate limiter: max 120 requests per hour per IP
_rate_limit: dict[str, list[float]] = defaultdict(list)
RATE_LIMIT_MAX = 120
RATE_LIMIT_WINDOW = 3600  # 1 hour


def _check_rate_limit(ip: str):
    now = time.time()
    _rate_limit[ip] = [t for t in _rate_limit[ip] if now - t < RATE_LIMIT_WINDOW]
    if len(_rate_limit[ip]) >= RATE_LIMIT_MAX:
        raise HTTPException(status_code=429, detail="Rate limit exceeded")
    _rate_limit[ip].append(now)


async def verify_api_key(request: Request, x_api_key: str = Header(...)):
    """Prüft API-Key und Rate-Limit."""
    if not VALID_API_KEY or x_api_key != VALID_API_KEY:
        logger.warning(f"Ungültiger API-Key von {request.client.host}")
        raise HTTPException(status_code=403, detail="Invalid API key")
    _check_rate_limit(request.client.host)


def _in_clause(ids):
    """Erzeugt sichere IN-Klausel für mehrere IDs."""
    return ",".join(str(int(i)) for i in ids)


# ──────────────────────────────────────────────────────────────────
# Shared-Logik für Lagebild-Responses
# ──────────────────────────────────────────────────────────────────

async def _build_lagebild_response(db, incident_ids: list, primary_id: int) -> dict:
    """Baut die Lagebild-Response für beliebige Incidents.

    Args:
        db: Datenbankverbindung
        incident_ids: Liste der Incident-IDs (für Iran: [6,18,19,20], sonst: [55])
        primary_id: ID des Haupt-Incidents für Metadaten
    """
    ids = _in_clause(incident_ids)

    # Haupt-Incident laden (für Summary, Sources)
    cursor = await db.execute(
        "SELECT * FROM incidents WHERE id = ?", (primary_id,)
    )
    incident = await cursor.fetchone()
    if not incident:
        raise HTTPException(status_code=404, detail="Incident not found")
    incident = dict(incident)

    # Category-Labels laden
    category_labels = None
    if incident.get("category_labels"):
        try:
            category_labels = json.loads(incident["category_labels"])
        except (json.JSONDecodeError, TypeError):
            pass

    # Alle Artikel laden
    cursor = await db.execute(
        f"""SELECT id, headline, headline_de, source, source_url, language,
                  published_at, collected_at, verification_status, incident_id
           FROM articles WHERE incident_id IN ({ids})
           ORDER BY published_at DESC, collected_at DESC"""
    )
    articles = [dict(r) for r in await cursor.fetchall()]

    # Alle Faktenchecks laden
    cursor = await db.execute(
        f"""SELECT id, claim, status, sources_count, evidence, status_history, checked_at, incident_id
           FROM fact_checks WHERE incident_id IN ({ids})
           ORDER BY checked_at DESC"""
    )
    fact_checks = []
    for r in await cursor.fetchall():
        fc = dict(r)
        try:
            fc["status_history"] = json.loads(fc.get("status_history") or "[]")
        except (json.JSONDecodeError, TypeError):
            fc["status_history"] = []
        fact_checks.append(fc)

    # Quellenanzahl über alle Incidents
    cursor = await db.execute(
        f"SELECT COUNT(DISTINCT source) as cnt FROM articles WHERE incident_id IN ({ids})"
    )
    source_count = (await cursor.fetchone())["cnt"]

    # Snapshots
    cursor = await db.execute(
        f"""SELECT id, incident_id, article_count, fact_check_count, created_at
           FROM incident_snapshots WHERE incident_id IN ({ids})
           ORDER BY created_at DESC"""
    )
    available_snapshots = [dict(r) for r in await cursor.fetchall()]

    # Sources JSON aus Haupt-Incident
    try:
        sources_json = json.loads(incident.get("sources_json") or "[]")
    except (json.JSONDecodeError, TypeError):
        sources_json = []

    # Locations aggregiert nach normalisierten Ortsnamen
    cursor = await db.execute(
        f"""SELECT
                al.location_name_normalized as name,
                al.latitude as lat,
                al.longitude as lon,
                al.country_code,
                al.category,
                COUNT(*) as article_count,
                MAX(al.confidence) as confidence
           FROM article_locations al
           WHERE al.incident_id IN ({ids})
           GROUP BY al.location_name_normalized
           ORDER BY article_count DESC"""
    )
    locations = [dict(r) for r in await cursor.fetchall()]

    # Top-3-Artikel pro Location (neueste zuerst)
    cursor = await db.execute(
        f"""SELECT al.location_name_normalized as loc_name,
                  a.headline_de, a.headline, a.source, a.source_url
           FROM article_locations al
           JOIN articles a ON a.id = al.article_id
           WHERE al.incident_id IN ({ids})
           ORDER BY a.published_at DESC"""
    )
    loc_articles = {}
    for r in await cursor.fetchall():
        r = dict(r)
        name = r["loc_name"]
        if name not in loc_articles:
            loc_articles[name] = []
        if len(loc_articles[name]) < 3:
            loc_articles[name].append({
                "headline": r["headline_de"] or r["headline"] or "",
                "source": r["source"] or "",
                "url": r["source_url"] or "",
            })
    for loc in locations:
        loc["top_articles"] = loc_articles.get(loc["name"], [])

    return {
        "generated_at": datetime.now(TIMEZONE).isoformat(),
        "incident": {
            "id": incident["id"],
            "title": incident["title"],
            "description": incident.get("description", ""),
            "status": incident["status"],
            "type": incident.get("type", "adhoc"),
            "created_at": incident["created_at"],
            "updated_at": incident["updated_at"],
            "article_count": len(articles),
            "source_count": source_count,
            "factcheck_count": len(fact_checks),
            "latest_developments": incident.get("latest_developments") or "",
        },
        "current_lagebild": {
            "summary_markdown": incident.get("summary", ""),
            "sources_json": sources_json,
            "updated_at": incident["updated_at"],
        },
        "articles": articles,
        "fact_checks": fact_checks,
        "available_snapshots": available_snapshots,
        "locations": locations,
        "category_labels": category_labels,
    }


async def _get_snapshot_response(db, snapshot_id: int, incident_ids: list) -> dict:
    """Liefert einen historischen Snapshot für die angegebenen Incidents."""
    ids = _in_clause(incident_ids)
    cursor = await db.execute(
        f"""SELECT id, summary, sources_json, article_count, fact_check_count, created_at
           FROM incident_snapshots
           WHERE id = ? AND incident_id IN ({ids})""",
        (snapshot_id,),
    )
    snap = await cursor.fetchone()
    if not snap:
        raise HTTPException(status_code=404, detail="Snapshot not found")

    snap = dict(snap)
    try:
        snap["sources_json"] = json.loads(snap.get("sources_json") or "[]")
    except (json.JSONDecodeError, TypeError):
        snap["sources_json"] = []

    return snap


# ──────────────────────────────────────────────────────────────────
# Endpunkte
# ──────────────────────────────────────────────────────────────────

@router.get("/lagebild", dependencies=[Depends(verify_api_key)])
async def get_lagebild(db=Depends(db_dependency)):
    """Liefert das aktuelle Lagebild (Irankonflikt) mit allen Daten.

    Abwärtskompatibel — aggregiert die Iran-Incidents 6, 18, 19, 20.
    """
    return await _build_lagebild_response(db, IRAN_INCIDENT_IDS, PRIMARY_INCIDENT_ID)


@router.post("/globe-ingest", dependencies=[Depends(verify_api_key)])
async def globe_ingest(
    request: Request,
    db=Depends(db_dependency),
):
    """Nimmt externe Ereignisse (EONET, USGS) als Artikel in eine Lage auf."""
    import json as _json
    body = await request.json()
    incident_id = body.get("incident_id")
    events = body.get("events", [])

    if not incident_id or not events:
        raise HTTPException(status_code=400, detail="incident_id und events erforderlich")

    # Pruefen ob Lage existiert
    cursor = await db.execute("SELECT id FROM incidents WHERE id = ?", (incident_id,))
    if not await cursor.fetchone():
        raise HTTPException(status_code=404, detail="Lage nicht gefunden")

    inserted = 0
    for evt in events[:50]:  # Max 50 pro Call
        headline = evt.get("title", "")[:500]
        if not headline:
            continue

        # Duplikat-Check per Headline + Lage
        cursor = await db.execute(
            "SELECT id FROM articles WHERE incident_id = ? AND headline = ? LIMIT 1",
            (incident_id, headline),
        )
        if await cursor.fetchone():
            continue

        source = evt.get("source", "Globe GEOINT")
        source_url = evt.get("url", "")
        content = evt.get("description", "")
        lat = evt.get("lat")
        lon = evt.get("lon")
        category = evt.get("category", "primary")

        await db.execute(
            """INSERT INTO articles (incident_id, headline, headline_de, source, source_url,
               content_original, language, collected_at, verification_status)
               VALUES (?, ?, ?, ?, ?, ?, 'de', datetime('now'), 'pending')""",
            (incident_id, headline, headline, source, source_url, content),
        )
        article_id = (await db.execute("SELECT last_insert_rowid()")).fetchone()
        article_id = (await article_id)[0] if article_id else None

        # Location direkt einfuegen wenn Koordinaten vorhanden
        if article_id and lat and lon:
            await db.execute(
                """INSERT INTO article_locations
                   (article_id, incident_id, location_name, location_name_normalized,
                    latitude, longitude, confidence, category)
                   VALUES (?, ?, ?, ?, ?, ?, 0.9, ?)""",
                (article_id, incident_id, evt.get("location", headline[:50]),
                 evt.get("location", headline[:50]).lower(), lat, lon, category),
            )

        inserted += 1

    await db.commit()
    return {"ok": True, "inserted": inserted, "total_sent": len(events)}


@router.get("/globe-incidents", dependencies=[Depends(verify_api_key)])
async def get_globe_incidents(db=Depends(db_dependency)):
    """Liste aller oeffentlichen aktiven Lagen fuer Globe-Auswahl."""
    cursor = await db.execute(
        """SELECT id, title, type, status, updated_at
           FROM incidents
           WHERE status = 'active' AND type = 'adhoc' AND visibility = 'public'
           ORDER BY updated_at DESC LIMIT 30"""
    )
    return [dict(r) for r in await cursor.fetchall()]

@router.get("/globe-feed", dependencies=[Depends(verify_api_key)])
async def get_globe_feed(
    incident_id: int = None,
    db=Depends(db_dependency),
):
    """Globe-Feed: Geoparsete Standorte mit Artikeln pro Ort."""
    import json as _json

    if incident_id:
        cursor = await db.execute(
            "SELECT id, title, description, summary, updated_at, type, status, category_labels "
            "FROM incidents WHERE id = ?", (incident_id,)
        )
    else:
        cursor = await db.execute(
            "SELECT id, title, description, summary, updated_at, type, status, category_labels "
            "FROM incidents WHERE visibility = 'public' AND status = 'active' AND type = 'adhoc' "
            "ORDER BY updated_at DESC LIMIT 10"
        )
    incidents = [dict(r) for r in await cursor.fetchall()]
    if not incidents:
        return {"type": "FeatureCollection", "features": [], "incidents": []}

    inc_ids = [i["id"] for i in incidents]
    ids_sql = ",".join(str(i) for i in inc_ids)

    # Alle Locations mit Artikel-IDs holen
    cursor = await db.execute(
        f"""SELECT al.location_name_normalized as name,
                   ROUND(al.latitude, 4) as lat, ROUND(al.longitude, 4) as lon,
                   al.country_code, al.category, al.incident_id, al.article_id
            FROM article_locations al
            WHERE al.incident_id IN ({ids_sql})"""
    )
    loc_rows = [dict(r) for r in await cursor.fetchall()]

    # Alle referenzierten Artikel laden
    art_ids = list(set(r["article_id"] for r in loc_rows if r.get("article_id")))
    articles_by_id = {}
    if art_ids:
        for chunk_start in range(0, len(art_ids), 500):
            chunk = art_ids[chunk_start:chunk_start+500]
            aids = ",".join(str(a) for a in chunk)
            cursor = await db.execute(
                f"SELECT id, headline_de, headline, source, source_url, content_de, "
                f"published_at, collected_at FROM articles WHERE id IN ({aids})"
            )
            for a in await cursor.fetchall():
                a = dict(a)
                articles_by_id[a["id"]] = a

    # Nach Ort gruppieren
    loc_map = {}
    for r in loc_rows:
        key = (r["name"] or "unknown", r["incident_id"])
        if key not in loc_map:
            loc_map[key] = {
                "lat": r["lat"], "lon": r["lon"], "country": r["country_code"],
                "category": r["category"], "incident_id": r["incident_id"],
                "seen_ids": set(), "articles": [],
            }
        g = loc_map[key]
        aid = r.get("article_id")
        if aid and aid in articles_by_id and aid not in g["seen_ids"]:
            g["seen_ids"].add(aid)
            g["articles"].append(articles_by_id[aid])

    # GeoJSON bauen
    features = []
    for (name, inc_id), g in list(loc_map.items())[:500]:
        inc = next((i for i in incidents if i["id"] == inc_id), None)
        features.append({
            "type": "Feature",
            "geometry": {"type": "Point", "coordinates": [g["lon"], g["lat"]]},
            "properties": {
                "name": name,
                "country": g["country"],
                "category": g["category"],
                "article_count": len(g["articles"]),
                "incident_id": inc_id,
                "incident_title": inc["title"] if inc else "",
                "articles": [{
                    "headline": a.get("headline_de") or a.get("headline", ""),
                    "source": a.get("source", ""),
                    "url": a.get("source_url", ""),
                    "summary": (a.get("content_de") or "")[:300],
                    "date": a.get("published_at") or a.get("collected_at", ""),
                } for a in g["articles"][:5]],
            },
        })

    inc_summaries = []
    for i in incidents:
        inc_summaries.append({
            "id": i["id"], "title": i["title"], "type": i["type"],
            "status": i["status"], "summary": (i.get("summary") or "")[:1000],
            "updated_at": i["updated_at"],
        })

    return {
        "type": "FeatureCollection",
        "features": features,
        "incidents": inc_summaries,
        "generated_at": datetime.now(TIMEZONE).isoformat(),
    }


# WICHTIG: Snapshot-Routen VOR der generischen /{incident_id}-Route,
# damit /lagebild/snapshot/123 nicht als incident_id="snapshot" gematcht wird.

@router.get("/lagebild/snapshot/{snapshot_id}", dependencies=[Depends(verify_api_key)])
async def get_snapshot(snapshot_id: int, db=Depends(db_dependency)):
    """Liefert einen historischen Snapshot (Irankonflikt, abwärtskompatibel)."""
    return await _get_snapshot_response(db, snapshot_id, IRAN_INCIDENT_IDS)


@router.get("/lagebild/{incident_id}/snapshot/{snapshot_id}", dependencies=[Depends(verify_api_key)])
async def get_snapshot_by_incident(incident_id: int, snapshot_id: int, db=Depends(db_dependency)):
    """Liefert einen historischen Snapshot für eine beliebige öffentliche Lage."""
    cursor = await db.execute(
        "SELECT id FROM incidents WHERE id = ? AND visibility = 'public'",
        (incident_id,),
    )
    if not await cursor.fetchone():
        raise HTTPException(status_code=404, detail="Lage nicht gefunden oder nicht öffentlich")
    return await _get_snapshot_response(db, snapshot_id, [incident_id])


@router.get("/lagebild/{incident_id}", dependencies=[Depends(verify_api_key)])
async def get_lagebild_by_id(incident_id: int, db=Depends(db_dependency)):
    """Liefert das Lagebild für eine beliebige öffentliche Lage."""
    cursor = await db.execute(
        "SELECT id FROM incidents WHERE id = ? AND visibility = 'public'",
        (incident_id,),
    )
    if not await cursor.fetchone():
        raise HTTPException(status_code=404, detail="Lage nicht gefunden oder nicht öffentlich")
    return await _build_lagebild_response(db, [incident_id], incident_id)
