"""Config loading with deep-merge against defaults."""
from pathlib import Path
import yaml

DEFAULT_CONFIG: dict = {
    "target": "https://bredelar.info",
    "user_agent": "integrity-scanner/1.0 (+security-monitoring)",
    "request_timeout": 20,
    "data_dir": "data",
    "reports_dir": "reports",
    "logs_dir": "logs",
    "config_dir": "config",
    "crawl": {
        "max_pages": 200,
        "delay_seconds": 1.0,
        "respect_robots_txt": False,
        "skip_extensions": [
            ".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico",
            ".pdf", ".zip", ".woff", ".woff2", ".ttf", ".otf", ".eot",
            ".mp4", ".mp3", ".webm", ".avi", ".mov",
        ],
        "exclude_paths": [],
        "include_paths": [],
        "sitemap": True,
    },
    "scoring": {
        "new_external_domain": 50,
        "new_internal_url": 30,
        "missing_internal_url": 20,
        "hidden_content": 40,
        "unexpected_canonical": 35,
        "meta_refresh": 40,
        "unexpected_jsonld": 35,
        "large_text_addition": 20,
        "new_inline_script_suspicious": 30,
        "comment_links": 25,
        "new_broken_link": 20,
        "suspicious_filename": 60,
        "cloaking_extra_link": 60,
        "cloaking_extra_text": 40,
        "cloaking_vary_ua": 25,
        "changed_script": 40,
        "changed_stylesheet": 30,
        "changed_asset": 20,
        # KI-Inhaltsanalyse (additiv; Gesamt-Level aus KI ist auf gelb gedeckelt)
        "ai_pornography": 50,
        "ai_defamation_illegal": 50,
        "ai_propaganda": 40,
        "ai_hidden_spam": 40,
        "ai_off_topic_commercial": 30,
        "ai_contradiction": 20,
        "ai_suspicious_image": 40,
    },
    "thresholds": {
        "yellow": 20,
        "red": 60,
        "large_text_block_chars": 200,
    },
    "periodic_checks": {
        "enabled": True,
        "interval_days": 7,
        "cloak_check": True,
        "ext_links": True,
        "assets": True,
    },
    "alerting": {
        "min_level": "yellow",
        "email": {
            "enabled": False,
            "smtp_host": "linix.de",
            "smtp_port": 587,
            "smtp_tls": True,
            "smtp_user": "eliza",
            "smtp_password_env": "SCANNER_SMTP_PASSWORD",
            "from": "eliza@linix.de",
            "to": ["dieter.schlueter@linix.de"],
        },
        "webhook": {
            "enabled": False,
            "url": "",
        },
    },
    # KI-gestützte semantische Inhaltsanalyse (OpenRouter, primär).
    # Hash-gegated: nur neue/geänderte Inhalte lösen einen API-Call aus — siehe
    # ai_ledger.json. Der API-Key kommt NUR aus der Umgebungsvariable (nie in config.yaml).
    # Lokale Server (localhost:8001, :8002) dienen nur als Dev-Fallback.
    "ai_analysis": {
        "enabled": False,
        "api_key_env": "OPENROUTER_API_KEY",
        "site_context": "",          # Themenbeschreibung der Website (Pflicht wenn enabled)
        "ai_confidence_min": 0.7,    # Mindest-Konfidenz, damit ein Fund gewertet wird
        # KI-Funde sind auf Gelb gedeckelt — AUSSER diese schwerwiegenden Kategorien
        # lösen bei hoher Schwere + hoher Konfidenz ROT aus. Leere Liste = immer max. Gelb.
        "red_categories": ["pornography", "defamation_illegal"],
        "red_min_severity": "high",
        "red_min_confidence": 0.9,
        "request_timeout": 60,       # Ceiling (auch für Asset-Fetch der Bilder)
        "attempt_timeout": 20,       # HARTES Wanduhr-Limit je Modell-Versuch → Eskalation
        "max_retries": 1,            # Wiederholungen der ganzen Modell-Kette bei Komplettausfall
        "retry_backoff_seconds": 2.0,
        # Parallelität: gleichzeitige API-Requests. I/O-gebunden → an die Rate-Limits der
        # API gebunden, NICHT an CPU-Kerne (vhost wie 24-Kern-Maschine gleich steuerbar).
        "max_concurrency": 8,
        # Adaptiver Router: ausgefallene/rate-limited Modelle werden nach so vielen Fehlern
        # für die Cooldown-Dauer übersprungen; das schnellste gesunde Modell zuerst.
        "breaker_failure_threshold": 2,
        "breaker_cooldown_seconds": 120,
        "refresh_models": True,      # tote Slugs failsafe über OpenRouter /models aussortieren
        # Verhalten, wenn Inhalte trotz Retry NICHT geprüft werden konnten (KI nicht erreichbar):
        #   "warn"   = sichtbarer Hinweis in Report/Terminal/E-Mail (Level bleibt unberührt)
        #   "yellow" = zusätzlich Gesamt-Level auf mindestens Gelb anheben
        # Für ein Sicherheitswerkzeug gilt: "konnte nicht prüfen" ≠ "sauber".
        "unchecked_level": "warn",
        # Modell-Kette mit zweifacher Eskalation: Stufe 1+2 free, Stufe 3 günstig bezahlt.
        # Schlägt ein Modell fehl oder antwortet zu langsam, wird zur nächsten Stufe eskaliert.
        "text": {
            "enabled": True,
            "models": [
                "qwen/qwen3-next-80b-a3b-instruct:free",   # Stufe 1: free
                "meta-llama/llama-3.3-70b-instruct:free",  # Stufe 2: free
                "google/gemini-2.5-flash-lite",            # Stufe 3: günstig bezahlt, zuverlässig
            ],
            "min_chars": 200,        # nur Seitentexte ab dieser Größe prüfen
            # Drossel NUR für den historischen Bestand; 0 = unbegrenzt (volle Abdeckung im
            # ersten Scan). Geänderte/neue Seiten werden IMMER geprüft, ungeachtet dieses Werts.
            "max_pages_per_scan": 0,
        },
        "image": {
            "enabled": True,
            "models": [
                "google/gemma-4-31b-it:free",            # Stufe 1: free, multimodal + OCR
                "nvidia/nemotron-nano-12b-v2-vl:free",   # Stufe 2: free, Vision
                "google/gemini-2.5-flash-lite",          # Stufe 3: günstig bezahlt, Vision
            ],
            # Drossel NUR für den Bild-Altbestand; 0 = unbegrenzt (volle Abdeckung im ersten
            # Scan). Geänderte/neue/geflaggte Bilder werden IMMER geprüft, ungeachtet des Werts.
            "max_images_per_scan": 0,
        },
        # Vorbereitet, default aus — Medien sind auf statischen Heimat-Seiten selten.
        "audio": {
            "enabled": False,
            "models": ["google/gemini-2.5-flash"],
            "max_files_per_scan": 0,   # 0 = unbegrenzt
        },
        "video": {
            "enabled": False,
            "models": ["google/gemini-2.5-flash"],
            "max_files_per_scan": 3,
        },
    },
    "normalization": {
        "strip_html_comments": True,
        "collapse_whitespace": True,
        "ignore_selectors": [],
        "ignore_patterns": [],
    },
    "allowed_jsonld_types": [
        "Organization", "WebSite", "WebPage", "Article", "BreadcrumbList",
        "ItemList", "LocalBusiness", "Place", "Person", "Event",
        "FAQPage", "Question", "Answer", "ImageObject", "SiteLinksSearchBox",
        "ContactPage", "AboutPage",
    ],
    "security_headers": [
        "Content-Security-Policy",
        "Strict-Transport-Security",
        "X-Content-Type-Options",
        "Referrer-Policy",
    ],
}


def load_config(config_file: str = "config.yaml") -> dict:
    """Load config from YAML, deep-merged on top of defaults."""
    cfg = _deep_copy(DEFAULT_CONFIG)
    p = Path(config_file)
    if p.exists():
        with p.open(encoding="utf-8") as f:
            user_cfg = yaml.safe_load(f) or {}
        _deep_update(cfg, user_cfg)
    return cfg


def resolve_paths(cfg: dict, base_dir: Path) -> dict:
    """Resolve relative paths in config against base_dir."""
    for key in ("data_dir", "reports_dir", "logs_dir", "config_dir"):
        cfg[key] = str(base_dir / cfg[key])
    return cfg


def _deep_copy(d: dict) -> dict:
    import copy
    return copy.deepcopy(d)


def _deep_update(base: dict, override: dict) -> None:
    for k, v in override.items():
        if isinstance(v, dict) and isinstance(base.get(k), dict):
            _deep_update(base[k], v)
        elif v is None and isinstance(base.get(k), list):
            pass  # YAML null on a list field = "not set" → keep default
        else:
            base[k] = v
