"""Sources-Router: Quellenverwaltung (Multi-Tenant)."""
import json
import logging
from collections import defaultdict
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
from models import SourceCreate, SourceUpdate, SourceResponse, DiscoverRequest, DiscoverResponse, DiscoverMultiResponse, DomainActionRequest
from auth import get_current_user
from database import db_dependency, get_db, refresh_source_counts
from services.external_reputation import apply_reputation_overrides, sync_all as sync_external_reputation
from services.source_classifier import bulk_classify, classify_source
from source_rules import discover_source, discover_all_feeds, evaluate_feeds_with_claude, _extract_domain, _detect_category, domain_to_display_name, _DOMAIN_ALIASES
import aiosqlite

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

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

SOURCE_UPDATE_COLUMNS = {
    "name", "url", "domain", "source_type", "category", "status", "notes",
    "language", "bias",
    "political_orientation", "media_type", "reliability",
    "state_affiliated", "country_code",
}
SOURCE_CLASSIFICATION_FIELDS = {
    "political_orientation", "media_type", "reliability",
    "state_affiliated", "country_code",
}
ALLOWED_ALIGNMENTS = {
    "prorussisch", "proiranisch", "prowestlich", "proukrainisch",
    "prochinesisch", "projapanisch", "proisraelisch", "propalaestinensisch",
    "protuerkisch", "panarabisch", "neutral", "sonstige",
}


async def _load_alignments_for(db: aiosqlite.Connection, source_ids: list[int]) -> dict[int, list[str]]:
    """Lädt alignments fuer mehrere Quellen in einer Query und gibt {source_id: [alignment, ...]} zurück."""
    if not source_ids:
        return {}
    placeholders = ",".join("?" for _ in source_ids)
    cursor = await db.execute(
        f"SELECT source_id, alignment FROM source_alignments WHERE source_id IN ({placeholders}) ORDER BY alignment",
        source_ids,
    )
    out: dict[int, list[str]] = {sid: [] for sid in source_ids}
    for row in await cursor.fetchall():
        out.setdefault(row["source_id"], []).append(row["alignment"])
    return out


async def _replace_alignments(db: aiosqlite.Connection, source_id: int, alignments: list[str]):
    """Ersetzt die alignments-Liste einer Quelle (DELETE + INSERT) — Aufrufer muss commit() machen."""
    await db.execute("DELETE FROM source_alignments WHERE source_id = ?", (source_id,))
    seen: set[str] = set()
    for raw in alignments:
        a = (raw or "").strip().lower()
        if not a or a in seen:
            continue
        if a not in ALLOWED_ALIGNMENTS:
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=f"Ungueltiger alignment-Wert: '{a}'",
            )
        seen.add(a)
        await db.execute(
            "INSERT INTO source_alignments (source_id, alignment) VALUES (?, ?)",
            (source_id, a),
        )


def _check_source_ownership(source: dict, username: str):
    """Prueft ob der Nutzer die Quelle bearbeiten/loeschen darf.

    System-Quellen (auto-entdeckt) duerfen von jedem bearbeitet werden.
    Nutzer-Quellen nur vom Ersteller.
    """
    added_by = source.get("added_by", "")
    if added_by and added_by != "system" and added_by != username:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Nur der Ersteller kann diese Quelle bearbeiten",
        )


@router.get("", response_model=list[SourceResponse])
async def list_sources(
    source_type: str = None,
    category: str = None,
    source_status: str = None,
    political_orientation: str = None,
    media_type: str = None,
    reliability: str = None,
    state_affiliated: bool = None,
    alignment: str = None,
    ifcn_signatory: bool = None,
    eu_disinfo_listed: bool = None,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Alle Quellen auflisten (global + org-spezifisch)."""
    tenant_id = current_user.get("tenant_id")

    # Global (tenant_id=NULL) + eigene Org
    query = "SELECT s.* FROM sources s WHERE (s.tenant_id IS NULL OR s.tenant_id = ?)"
    params: list = [tenant_id]

    if source_type:
        query += " AND s.source_type = ?"
        params.append(source_type)
    if category:
        query += " AND s.category = ?"
        params.append(category)
    if source_status:
        query += " AND s.status = ?"
        params.append(source_status)
    if political_orientation:
        query += " AND s.political_orientation = ?"
        params.append(political_orientation)
    if media_type:
        query += " AND s.media_type = ?"
        params.append(media_type)
    if reliability:
        query += " AND s.reliability = ?"
        params.append(reliability)
    if state_affiliated is not None:
        query += " AND s.state_affiliated = ?"
        params.append(1 if state_affiliated else 0)
    if alignment:
        query += " AND EXISTS (SELECT 1 FROM source_alignments sa WHERE sa.source_id = s.id AND sa.alignment = ?)"
        params.append(alignment.lower())
    if ifcn_signatory is not None:
        query += " AND s.ifcn_signatory = ?"
        params.append(1 if ifcn_signatory else 0)
    if eu_disinfo_listed is not None:
        query += " AND s.eu_disinfo_listed = ?"
        params.append(1 if eu_disinfo_listed else 0)

    query += " ORDER BY s.source_type, s.category, s.name"
    cursor = await db.execute(query, params)
    rows = await cursor.fetchall()
    results = [dict(row) for row in rows]
    alignments_map = await _load_alignments_for(db, [r["id"] for r in results])
    for d in results:
        d["is_global"] = d.get("tenant_id") is None
        d["state_affiliated"] = bool(d.get("state_affiliated"))
        d["ifcn_signatory"] = bool(d.get("ifcn_signatory"))
        d["eu_disinfo_listed"] = bool(d.get("eu_disinfo_listed"))
        d["alignments"] = alignments_map.get(d["id"], [])
    return results


@router.get("/stats")
async def get_source_stats(
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Aggregierte Quellen-Statistiken (global + eigene Org)."""
    tenant_id = current_user.get("tenant_id")

    cursor = await db.execute("""
        SELECT
            source_type,
            COUNT(*) as count,
            SUM(article_count) as total_articles
        FROM sources
        WHERE status = 'active' AND (tenant_id IS NULL OR tenant_id = ?)
        GROUP BY source_type
    """, (tenant_id,))
    rows = await cursor.fetchall()

    stats = {
        "rss_feed": {"count": 0, "articles": 0},
        "web_source": {"count": 0, "articles": 0},
        "telegram_channel": {"count": 0, "articles": 0},
        "excluded": {"count": 0, "articles": 0},
    }
    for row in rows:
        st = row["source_type"]
        if st in stats:
            stats[st]["count"] = row["count"]
            stats[st]["articles"] = row["total_articles"] or 0

    cursor = await db.execute(
        "SELECT COUNT(*) as cnt FROM articles WHERE tenant_id = ?",
        (tenant_id,),
    )
    total_row = await cursor.fetchone()

    return {
        "by_type": stats,
        "total_sources": sum(s["count"] for s in stats.values()),
        "total_articles": total_row["cnt"],
    }


@router.post("/discover", response_model=DiscoverResponse)
async def discover_source_endpoint(
    data: DiscoverRequest,
    current_user: dict = Depends(get_current_user),
):
    """RSS-Feed, Name, Kategorie und Domain einer URL automatisch erkennen."""
    try:
        result = await discover_source(data.url)
        return result
    except Exception as e:
        logger.error(f"Discovery fehlgeschlagen: {e}", exc_info=True)
        raise HTTPException(status_code=500, detail="Discovery fehlgeschlagen")


@router.post("/discover-multi", response_model=DiscoverMultiResponse)
async def discover_multi_endpoint(
    data: DiscoverRequest,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Findet ALLE RSS-Feeds einer Domain, bewertet sie mit Claude und legt relevante als Quellen an."""
    tenant_id = current_user.get("tenant_id")
    try:
        multi = await discover_all_feeds(data.url)
        domain = multi["domain"]
        category = multi["category"]

        if not multi["feeds"]:
            single = await discover_source(data.url)
            sources = []
            if single.get("rss_url"):
                cursor = await db.execute(
                    "SELECT id FROM sources WHERE url = ?", (single["rss_url"],)
                )
                existing = await cursor.fetchone()
                if not existing:
                    cursor = await db.execute(
                        """INSERT INTO sources (name, url, domain, source_type, category, status, added_by, tenant_id)
                           VALUES (?, ?, ?, ?, ?, 'active', ?, ?)""",
                        (single["name"], single["rss_url"], single["domain"],
                         single["source_type"], single["category"], current_user["username"], tenant_id),
                    )
                    await db.commit()
                    cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (cursor.lastrowid,))
                    row = await cursor.fetchone()
                    sources.append(dict(row))

            return DiscoverMultiResponse(
                domain=single.get("domain", domain),
                category=single.get("category", category),
                added_count=len(sources),
                skipped_count=1 if not sources and single.get("rss_url") else 0,
                total_found=1 if single.get("rss_url") else 0,
                sources=sources,
                fallback_single=True,
            )

        relevant_feeds = await evaluate_feeds_with_claude(domain, multi["feeds"])

        cursor = await db.execute("SELECT url FROM sources WHERE url IS NOT NULL")
        existing_urls = {row["url"] for row in await cursor.fetchall()}

        new_ids = []
        skipped = 0
        for feed in relevant_feeds:
            if feed["url"] in existing_urls:
                skipped += 1
                continue

            cursor = await db.execute(
                """INSERT INTO sources (name, url, domain, source_type, category, status, added_by, tenant_id)
                   VALUES (?, ?, ?, 'rss_feed', ?, 'active', ?, ?)""",
                (feed["name"], feed["url"], domain, category, current_user["username"], tenant_id),
            )
            new_ids.append(cursor.lastrowid)
            existing_urls.add(feed["url"])

        cursor = await db.execute(
            "SELECT id FROM sources WHERE LOWER(domain) = ? AND source_type = 'web_source'",
            (domain.lower(),),
        )
        if not await cursor.fetchone():
            cursor = await db.execute(
                """INSERT INTO sources (name, url, domain, source_type, category, status, added_by, tenant_id)
                   VALUES (?, ?, ?, 'web_source', ?, 'active', ?, ?)""",
                (domain_to_display_name(domain), f"https://{domain}", domain, category, current_user["username"], tenant_id),
            )
            new_ids.append(cursor.lastrowid)

        await db.commit()

        added_sources = []
        if new_ids:
            placeholders = ",".join("?" for _ in new_ids)
            cursor = await db.execute(
                f"SELECT * FROM sources WHERE id IN ({placeholders}) ORDER BY id",
                new_ids,
            )
            added_sources = [dict(row) for row in await cursor.fetchall()]

        return DiscoverMultiResponse(
            domain=domain,
            category=category,
            added_count=len(added_sources),
            skipped_count=skipped,
            total_found=len(multi["feeds"]),
            sources=added_sources,
            fallback_single=False,
        )

    except Exception as e:
        logger.error(f"Multi-Discovery fehlgeschlagen: {e}", exc_info=True)
        raise HTTPException(status_code=500, detail="Multi-Discovery fehlgeschlagen")


@router.post("/rediscover-existing")
async def rediscover_existing_endpoint(
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Einmalige Migration: Bestehende RSS-Quellen nach zusaetzlichen Feeds durchsuchen."""
    tenant_id = current_user.get("tenant_id")
    try:
        cursor = await db.execute(
            "SELECT * FROM sources WHERE source_type = 'rss_feed' AND status = 'active' AND (tenant_id IS NULL OR tenant_id = ?)",
            (tenant_id,),
        )
        existing_sources = [dict(row) for row in await cursor.fetchall()]

        domains = defaultdict(list)
        for src in existing_sources:
            if src["domain"]:
                domains[src["domain"]].append(src)

        cursor = await db.execute("SELECT url FROM sources WHERE url IS NOT NULL")
        existing_urls = {row["url"] for row in await cursor.fetchall()}

        domains_processed = 0
        feeds_added = 0
        feeds_skipped = 0

        for domain, sources in domains.items():
            domains_processed += 1
            base_url = f"https://{domain}"

            try:
                multi = await discover_all_feeds(base_url)
                if not multi["feeds"]:
                    continue

                relevant_feeds = await evaluate_feeds_with_claude(domain, multi["feeds"])
                category = _detect_category(domain)

                for feed in relevant_feeds:
                    if feed["url"] in existing_urls:
                        feeds_skipped += 1
                        continue

                    await db.execute(
                        """INSERT INTO sources (name, url, domain, source_type, category, status, added_by, tenant_id)
                           VALUES (?, ?, ?, 'rss_feed', ?, 'active', ?, ?)""",
                        (feed["name"], feed["url"], domain, category, current_user["username"], tenant_id),
                    )
                    existing_urls.add(feed["url"])
                    feeds_added += 1

                await db.commit()
            except Exception as e:
                logger.warning(f"Rediscovery fuer {domain} fehlgeschlagen: {e}")
                continue

        return {
            "domains_processed": domains_processed,
            "feeds_added": feeds_added,
            "feeds_skipped": feeds_skipped,
        }

    except Exception as e:
        logger.error(f"Rediscovery fehlgeschlagen: {e}", exc_info=True)
        raise HTTPException(status_code=500, detail="Rediscovery fehlgeschlagen")


@router.get("/my-exclusions")
async def get_my_exclusions(
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Gibt die vom aktuellen User ausgeschlossenen Domains zurück."""
    user_id = current_user["id"]
    cursor = await db.execute(
        "SELECT domain, notes, created_at FROM user_excluded_domains WHERE user_id = ? ORDER BY domain",
        (user_id,),
    )
    rows = await cursor.fetchall()
    return [dict(row) for row in rows]


@router.post("/block-domain")
async def block_domain(
    data: DomainActionRequest,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Domain fuer den aktuellen User ausschließen (per-User, nicht org-weit)."""
    user_id = current_user["id"]
    domain = data.domain.lower().strip()

    # Pruefen ob bereits ausgeschlossen
    cursor = await db.execute(
        "SELECT id FROM user_excluded_domains WHERE user_id = ? AND domain = ?",
        (user_id, domain),
    )
    existing = await cursor.fetchone()

    if existing:
        if data.notes:
            await db.execute(
                "UPDATE user_excluded_domains SET notes = ? WHERE id = ?",
                (data.notes, existing["id"]),
            )
            await db.commit()
        return {"domain": domain, "status": "already_excluded"}

    await db.execute(
        "INSERT INTO user_excluded_domains (user_id, domain, notes) VALUES (?, ?, ?)",
        (user_id, domain, data.notes),
    )
    await db.commit()

    return {"domain": domain, "status": "excluded"}


@router.post("/unblock-domain")
async def unblock_domain(
    data: DomainActionRequest,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Domain-Ausschluss fuer den aktuellen User aufheben."""
    user_id = current_user["id"]
    domain = data.domain.lower().strip()

    cursor = await db.execute(
        "DELETE FROM user_excluded_domains WHERE user_id = ? AND domain = ?",
        (user_id, domain),
    )
    removed = cursor.rowcount
    await db.commit()

    return {"domain": domain, "removed": removed > 0}


@router.delete("/domain/{domain}")
async def delete_domain(
    domain: str,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Alle Quellen einer Domain loeschen (nur org-eigene, nicht globale)."""
    tenant_id = current_user.get("tenant_id")
    domain_lower = domain.lower().strip()

    cursor = await db.execute(
        "SELECT * FROM sources WHERE LOWER(domain) = ? AND tenant_id = ?",
        (domain_lower, tenant_id),
    )
    rows = await cursor.fetchall()

    if not rows:
        raise HTTPException(status_code=404, detail="Keine Quellen fuer diese Domain gefunden")

    username = current_user["username"]
    for row in rows:
        source = dict(row)
        if source["added_by"] == "system":
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Domain enthaelt System-Quellen, die nicht geloescht werden koennen",
            )
        if source["added_by"] and source["added_by"] != username:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Domain enthaelt Quellen anderer Nutzer",
            )

    await db.execute(
        "DELETE FROM sources WHERE LOWER(domain) = ? AND tenant_id = ?",
        (domain_lower, tenant_id),
    )
    await db.commit()

    return {
        "domain": domain_lower,
        "deleted_count": len(rows),
    }


@router.post("", response_model=SourceResponse, status_code=status.HTTP_201_CREATED)
async def create_source(
    data: SourceCreate,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Neue Quelle hinzufuegen (org-spezifisch)."""
    tenant_id = current_user.get("tenant_id")

    # Domain normalisieren (Subdomain-Aliase auflösen, aus URL extrahieren)
    domain = data.domain
    if not domain and data.url:
        domain = _extract_domain(data.url)
    if domain:
        domain = _DOMAIN_ALIASES.get(domain.lower(), domain.lower())

    # Duplikat-Prüfung 1: gleiche URL bereits vorhanden? (tenant-übergreifend)
    if data.url:
        cursor = await db.execute(
            "SELECT id, name FROM sources WHERE url = ? AND status = 'active'",
            (data.url,),
        )
        existing = await cursor.fetchone()
        if existing:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Feed-URL bereits vorhanden: {existing['name']} (ID {existing['id']})",
            )

    # Duplikat-Prüfung 2: Domain bereits vorhanden? (tenant-übergreifend)
    if domain:
        cursor = await db.execute(
            "SELECT id, name, source_type FROM sources WHERE LOWER(domain) = ? AND status = 'active' AND (tenant_id IS NULL OR tenant_id = ?) LIMIT 1",
            (domain.lower(), tenant_id),
        )
        domain_existing = await cursor.fetchone()
        if domain_existing:
            if data.source_type == "web_source":
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail=f"Web-Quelle für '{domain}' bereits vorhanden: {domain_existing['name']}",
                )
            if not data.url:
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail=f"Domain '{domain}' bereits als Quelle vorhanden: {domain_existing['name']}. Für einen neuen RSS-Feed bitte die Feed-URL angeben.",
                )

    payload = data.model_dump(exclude_unset=True)
    alignments = payload.pop("alignments", None)
    classification_touched = bool(SOURCE_CLASSIFICATION_FIELDS & payload.keys()) or alignments is not None

    cols = ["name", "url", "domain", "source_type", "category", "status", "notes",
            "language", "bias",
            "political_orientation", "media_type", "reliability",
            "state_affiliated", "country_code",
            "added_by", "tenant_id"]
    vals = [
        data.name,
        data.url,
        domain,
        data.source_type,
        data.category,
        data.status,
        data.notes,
        payload.get("language"),
        payload.get("bias"),
        payload.get("political_orientation"),
        payload.get("media_type"),
        payload.get("reliability"),
        1 if payload.get("state_affiliated") else 0,
        payload.get("country_code"),
        current_user["username"],
        tenant_id,
    ]
    if classification_touched:
        cols += ["classification_source", "classified_at"]
        vals += ["manual"]
        ts_marker = True
    else:
        ts_marker = False

    placeholders = ", ".join(["?"] * len(vals) + (["CURRENT_TIMESTAMP"] if ts_marker else []))
    cursor = await db.execute(
        f"INSERT INTO sources ({', '.join(cols)}) VALUES ({placeholders})",
        vals,
    )
    new_id = cursor.lastrowid

    if alignments:
        await _replace_alignments(db, new_id, alignments)

    await db.commit()

    cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (new_id,))
    row = await cursor.fetchone()
    result = dict(row)
    result["is_global"] = result.get("tenant_id") is None
    result["state_affiliated"] = bool(result.get("state_affiliated"))
    alignments_map = await _load_alignments_for(db, [new_id])
    result["alignments"] = alignments_map.get(new_id, [])
    return result


@router.put("/{source_id}", response_model=SourceResponse)
async def update_source(
    source_id: int,
    data: SourceUpdate,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Quelle bearbeiten."""
    cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (source_id,))
    row = await cursor.fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Quelle nicht gefunden")

    if row["tenant_id"] is None:
        raise HTTPException(status_code=403, detail="Grundquellen koennen nur ueber die Verwaltung bearbeitet werden")

    _check_source_ownership(dict(row), current_user["username"])

    payload = data.model_dump(exclude_unset=True)
    alignments = payload.pop("alignments", None)

    updates = {}
    for field, value in payload.items():
        if field not in SOURCE_UPDATE_COLUMNS:
            continue
        # Domain normalisieren
        if field == "domain" and value:
            value = _DOMAIN_ALIASES.get(value.lower(), value.lower())
        if field == "state_affiliated":
            value = 1 if value else 0
        updates[field] = value

    classification_touched = bool(SOURCE_CLASSIFICATION_FIELDS & updates.keys()) or alignments is not None
    if classification_touched:
        updates["classification_source"] = "manual"
        updates["classified_at"] = "CURRENT_TIMESTAMP_MARKER"

    if updates:
        set_parts = []
        values = []
        for k, v in updates.items():
            if v == "CURRENT_TIMESTAMP_MARKER":
                set_parts.append(f"{k} = CURRENT_TIMESTAMP")
            else:
                set_parts.append(f"{k} = ?")
                values.append(v)
        values.append(source_id)
        await db.execute(f"UPDATE sources SET {', '.join(set_parts)} WHERE id = ?", values)

    if alignments is not None:
        await _replace_alignments(db, source_id, alignments)

    if updates or alignments is not None:
        await db.commit()

    cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (source_id,))
    row = await cursor.fetchone()
    result = dict(row)
    result["is_global"] = result.get("tenant_id") is None
    result["state_affiliated"] = bool(result.get("state_affiliated"))
    alignments_map = await _load_alignments_for(db, [source_id])
    result["alignments"] = alignments_map.get(source_id, [])
    return result


@router.delete("/{source_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_source(
    source_id: int,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Quelle loeschen."""
    cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (source_id,))
    row = await cursor.fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Quelle nicht gefunden")

    if row["tenant_id"] is None:
        raise HTTPException(status_code=403, detail="Grundquellen koennen nicht geloescht werden")

    _check_source_ownership(dict(row), current_user["username"])

    await db.execute("DELETE FROM sources WHERE id = ?", (source_id,))
    await db.commit()




@router.post("/telegram/validate")
async def validate_telegram_channel(
    data: dict,
    current_user: dict = Depends(get_current_user),
):
    """Prueft ob ein Telegram-Kanal erreichbar ist und gibt Kanalinfo zurueck."""
    channel_id = data.get("channel_id", "").strip()
    if not channel_id:
        raise HTTPException(status_code=400, detail="channel_id ist erforderlich")

    try:
        from feeds.telegram_parser import TelegramParser
        parser = TelegramParser()
        result = await parser.validate_channel(channel_id)
        if result:
            return result
        raise HTTPException(status_code=404, detail="Kanal nicht erreichbar oder nicht gefunden")
    except HTTPException:
        raise
    except Exception as e:
        logger.error("Telegram-Validierung fehlgeschlagen: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Telegram-Validierung fehlgeschlagen")


@router.post("/refresh-counts")
async def trigger_refresh_counts(
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Artikelzaehler fuer alle Quellen neu berechnen."""
    await refresh_source_counts(db)
    return {"status": "ok"}


# === Klassifikations-Review (LLM-Vorschlaege approve/reject/reclassify) ===

def _require_admin_for_global(row: dict, current_user: dict):
    """Globale Quellen (tenant_id IS NULL) duerfen nur org_admins approve-en/reclassify-en."""
    if row.get("tenant_id") is None and current_user.get("role") != "org_admin":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Globale Quellen koennen nur von Admins klassifiziert werden",
        )


@router.get("/classification/stats")
async def classification_stats(
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Counts pro classification_source-Wert (global + eigene Org)."""
    tenant_id = current_user.get("tenant_id")
    cursor = await db.execute(
        """SELECT classification_source, COUNT(*) as cnt
           FROM sources
           WHERE (tenant_id IS NULL OR tenant_id = ?) AND status = 'active'
           GROUP BY classification_source""",
        (tenant_id,),
    )
    by_source = {row["classification_source"] or "legacy": row["cnt"] for row in await cursor.fetchall()}
    cursor = await db.execute(
        """SELECT COUNT(*) as cnt FROM sources
           WHERE (tenant_id IS NULL OR tenant_id = ?) AND status = 'active'
           AND proposed_political_orientation IS NOT NULL""",
        (tenant_id,),
    )
    pending = (await cursor.fetchone())["cnt"]
    return {
        "by_classification_source": by_source,
        "pending_review": pending,
        "total": sum(by_source.values()),
    }


@router.get("/classification/queue")
async def classification_queue(
    limit: int = 50,
    min_confidence: float = 0.0,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Liefert Quellen mit nicht-leeren proposed_*-Spalten (Review-Queue)."""
    tenant_id = current_user.get("tenant_id")
    cursor = await db.execute(
        """SELECT s.* FROM sources s
           WHERE (s.tenant_id IS NULL OR s.tenant_id = ?)
             AND s.proposed_political_orientation IS NOT NULL
             AND COALESCE(s.proposed_confidence, 0) >= ?
           ORDER BY s.proposed_confidence DESC, s.proposed_at DESC
           LIMIT ?""",
        (tenant_id, min_confidence, limit),
    )
    rows = [dict(r) for r in await cursor.fetchall()]
    alignments_map = await _load_alignments_for(db, [r["id"] for r in rows])
    out = []
    for d in rows:
        try:
            proposed_aligns = json.loads(d.get("proposed_alignments_json") or "[]")
        except (json.JSONDecodeError, TypeError):
            proposed_aligns = []
        out.append({
            "id": d["id"],
            "name": d["name"],
            "url": d.get("url"),
            "domain": d.get("domain"),
            "source_type": d.get("source_type"),
            "category": d.get("category"),
            "is_global": d.get("tenant_id") is None,
            "current": {
                "political_orientation": d.get("political_orientation"),
                "media_type": d.get("media_type"),
                "reliability": d.get("reliability"),
                "state_affiliated": bool(d.get("state_affiliated")),
                "country_code": d.get("country_code"),
                "alignments": alignments_map.get(d["id"], []),
                "classification_source": d.get("classification_source"),
            },
            "proposed": {
                "political_orientation": d.get("proposed_political_orientation"),
                "media_type": d.get("proposed_media_type"),
                "reliability": d.get("proposed_reliability"),
                "state_affiliated": bool(d.get("proposed_state_affiliated")),
                "country_code": d.get("proposed_country_code"),
                "alignments": proposed_aligns,
                "confidence": d.get("proposed_confidence"),
                "reasoning": d.get("proposed_reasoning"),
                "proposed_at": d.get("proposed_at"),
            },
        })
    return out


async def _clear_proposed(db: aiosqlite.Connection, source_id: int):
    """Loescht die proposed_*-Felder einer Quelle (ohne commit)."""
    await db.execute(
        """UPDATE sources SET
            proposed_political_orientation = NULL,
            proposed_media_type = NULL,
            proposed_reliability = NULL,
            proposed_state_affiliated = NULL,
            proposed_country_code = NULL,
            proposed_alignments_json = NULL,
            proposed_confidence = NULL,
            proposed_reasoning = NULL,
            proposed_at = NULL
        WHERE id = ?""",
        (source_id,),
    )


@router.post("/{source_id}/classification/approve")
async def approve_classification(
    source_id: int,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Uebernimmt proposed_* in echte Felder, setzt classification_source='llm_approved'."""
    cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (source_id,))
    row = await cursor.fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Quelle nicht gefunden")
    src = dict(row)
    _require_admin_for_global(src, current_user)

    if src.get("proposed_political_orientation") is None:
        raise HTTPException(status_code=400, detail="Keine LLM-Vorschlaege fuer diese Quelle vorhanden")

    try:
        proposed_aligns = json.loads(src.get("proposed_alignments_json") or "[]")
    except (json.JSONDecodeError, TypeError):
        proposed_aligns = []

    await db.execute(
        """UPDATE sources SET
            political_orientation = ?,
            media_type = ?,
            reliability = ?,
            state_affiliated = ?,
            country_code = ?,
            classification_source = 'llm_approved',
            classified_at = CURRENT_TIMESTAMP
        WHERE id = ?""",
        (
            src["proposed_political_orientation"],
            src["proposed_media_type"],
            src["proposed_reliability"],
            1 if src.get("proposed_state_affiliated") else 0,
            src.get("proposed_country_code"),
            source_id,
        ),
    )
    await _replace_alignments(db, source_id, [a for a in proposed_aligns if a in ALLOWED_ALIGNMENTS])
    await _clear_proposed(db, source_id)
    await db.commit()
    # Reliability-Override anwenden (IFCN/EUvsDisinfo)
    try:
        await apply_reputation_overrides(db, source_id)
    except Exception as e:
        logger.warning("Reputation-Override fuer source_id=%s fehlgeschlagen: %s", source_id, e)
    return {"source_id": source_id, "status": "approved"}


@router.post("/{source_id}/classification/reject")
async def reject_classification(
    source_id: int,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Verwirft die LLM-Vorschlaege ohne Uebernahme. classification_source bleibt unveraendert."""
    cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (source_id,))
    row = await cursor.fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Quelle nicht gefunden")
    src = dict(row)
    _require_admin_for_global(src, current_user)

    await _clear_proposed(db, source_id)
    # Wenn classification_source noch 'llm_pending' war, zurueck auf 'legacy'
    if src.get("classification_source") == "llm_pending":
        await db.execute(
            "UPDATE sources SET classification_source = 'legacy' WHERE id = ?",
            (source_id,),
        )
    await db.commit()
    return {"source_id": source_id, "status": "rejected"}


@router.post("/{source_id}/classification/reclassify")
async def reclassify_source(
    source_id: int,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Triggert eine LLM-Klassifikation einer einzelnen Quelle (synchron, ~3-5s)."""
    cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (source_id,))
    row = await cursor.fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="Quelle nicht gefunden")
    src = dict(row)
    _require_admin_for_global(src, current_user)

    try:
        result = await classify_source(db, source_id)
    except Exception as e:
        logger.error("Reclassify source_id=%s fehlgeschlagen: %s", source_id, e, exc_info=True)
        raise HTTPException(status_code=500, detail=f"Klassifikation fehlgeschlagen: {e}")
    return result


async def _bulk_classify_background(limit: int, only_unclassified: bool):
    """Hintergrund-Task: oeffnet eigene DB-Connection."""
    db = await get_db()
    try:
        await bulk_classify(db, limit=limit, only_unclassified=only_unclassified)
    finally:
        await db.close()


@router.post("/classification/bulk-classify")
async def trigger_bulk_classify(
    background_tasks: BackgroundTasks,
    limit: int = 50,
    only_unclassified: bool = True,
    current_user: dict = Depends(get_current_user),
):
    """Startet eine Bulk-Klassifikation im Hintergrund (nur Admins)."""
    if current_user.get("role") != "org_admin":
        raise HTTPException(status_code=403, detail="Nur Admins koennen Bulk-Klassifikation starten")
    if limit < 1 or limit > 500:
        raise HTTPException(status_code=400, detail="limit muss zwischen 1 und 500 liegen")
    background_tasks.add_task(_bulk_classify_background, limit, only_unclassified)
    return {"status": "started", "limit": limit, "only_unclassified": only_unclassified}


@router.post("/external-reputation/sync")
async def trigger_external_reputation_sync(
    background_tasks: BackgroundTasks,
    current_user: dict = Depends(get_current_user),
):
    """Startet Sync von IFCN- und EUvsDisinfo-Daten (Admin, Hintergrund)."""
    if current_user.get("role") != "org_admin":
        raise HTTPException(status_code=403, detail="Nur Admins koennen den externen Sync starten")

    async def _bg():
        db = await get_db()
        try:
            await sync_external_reputation(db)
        finally:
            await db.close()

    background_tasks.add_task(_bg)
    return {"status": "started"}


@router.post("/classification/bulk-approve")
async def bulk_approve_classifications(
    min_confidence: float = 0.85,
    current_user: dict = Depends(get_current_user),
    db: aiosqlite.Connection = Depends(db_dependency),
):
    """Genehmigt alle Pending-Vorschlaege ueber dem confidence-Schwellwert (nur Admins).

    Globale Quellen werden nur bearbeitet, wenn der Aufrufer org_admin ist;
    Tenant-eigene Quellen sowieso.
    """
    if current_user.get("role") != "org_admin":
        raise HTTPException(status_code=403, detail="Nur Admins koennen Bulk-Approve nutzen")
    tenant_id = current_user.get("tenant_id")
    cursor = await db.execute(
        """SELECT id, proposed_political_orientation, proposed_media_type,
                  proposed_reliability, proposed_state_affiliated,
                  proposed_country_code, proposed_alignments_json, tenant_id
           FROM sources
           WHERE proposed_political_orientation IS NOT NULL
             AND COALESCE(proposed_confidence, 0) >= ?
             AND (tenant_id IS NULL OR tenant_id = ?)""",
        (min_confidence, tenant_id),
    )
    rows = [dict(r) for r in await cursor.fetchall()]
    approved_ids: list[int] = []
    for src in rows:
        try:
            proposed_aligns = json.loads(src.get("proposed_alignments_json") or "[]")
        except (json.JSONDecodeError, TypeError):
            proposed_aligns = []
        await db.execute(
            """UPDATE sources SET
                political_orientation = ?,
                media_type = ?,
                reliability = ?,
                state_affiliated = ?,
                country_code = ?,
                classification_source = 'llm_approved',
                classified_at = CURRENT_TIMESTAMP
            WHERE id = ?""",
            (
                src["proposed_political_orientation"],
                src["proposed_media_type"],
                src["proposed_reliability"],
                1 if src.get("proposed_state_affiliated") else 0,
                src.get("proposed_country_code"),
                src["id"],
            ),
        )
        await _replace_alignments(
            db, src["id"], [a for a in proposed_aligns if a in ALLOWED_ALIGNMENTS]
        )
        await _clear_proposed(db, src["id"])
        approved_ids.append(src["id"])
    await db.commit()
    # Reliability-Override fuer alle gerade Approved
    try:
        for sid in approved_ids:
            await apply_reputation_overrides(db, sid)
    except Exception as e:
        logger.warning("Bulk Reputation-Override fehlgeschlagen: %s", e)
    return {"approved_count": len(approved_ids), "min_confidence": min_confidence}
