#!/usr/bin/env python3
"""
Auditor — Wöchentlicher Orchestrator.

Drei Modi:
    --mode scheduled   Vom Scheduler aufgerufen. Prüft Mittwoch-Cutoff.
    --mode manual      Vom Nutzer direkt. Kein Cutoff-Check.
    --mode scan-only   Nur Stufe 1 (Scan), kein Report.

Testbarer Zeit-Hook:
    --override-now ISO-TIMESTAMP    Fixiert _get_now() für deterministische Tests.
                                    Beispiel: --override-now 2026-05-20T08:00

Beispiele:
    python weekly_run.py --mode scheduled
    python weekly_run.py --mode manual --vault /Pfad/zum/Vault
    python weekly_run.py --mode scan-only
    python weekly_run.py --mode scheduled --override-now 2026-05-20T08:00
"""

from __future__ import annotations

import argparse
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path

_SKILL_DIR = Path(__file__).resolve().parent
_SKILLS_ROOT = _SKILL_DIR.parent

if str(_SKILLS_ROOT) not in sys.path:
    sys.path.insert(0, str(_SKILLS_ROOT))

from _lib.config_loader import load_skill_config  # noqa: E402

SCAN_SCRIPT = _SKILL_DIR / "scan.py"
REPORT_SCRIPT = _SKILL_DIR / "render-raw-report.py"

# Mittwoch = weekday() == 2
CUTOFF_WEEKDAY = 2


def _get_now(override: str | None = None) -> datetime:
    """Liefert den aktuellen Zeitpunkt in lokaler Zeit.

    override akzeptiert ISO-Timestamp für deterministische Tests.
    Beispiel: '2026-05-20T08:00' — dann wird dieser Zeitpunkt als 'jetzt' behandelt.
    Wichtig: Falls override kein Timezone-Info enthält, wird UTC angenommen.
    """
    if override:
        dt = datetime.fromisoformat(override)
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=timezone.utc)
        return dt
    return datetime.now(tz=timezone.utc)


def is_past_cutoff(now: datetime, cutoff_hour: int) -> bool:
    """Gibt True zurück, wenn der aktuelle Zeitpunkt nach dem Mittwoch-Cutoff liegt.

    Definition: nach Mittwoch `cutoff_hour:00` UTC gilt der Cutoff als abgelaufen.
    Beim nächsten Mittwoch nach dem letzten Lauf (Dienstag 20:00) ist der Cutoff
    Mittwoch `cutoff_hour:00`. Liegt 'now' nach diesem Zeitpunkt → überspringen.
    """
    local = now.replace(tzinfo=timezone.utc) if now.tzinfo is None else now
    wd = local.weekday()
    hour = local.hour
    # Cutoff: Mittwoch nach cutoff_hour
    return wd == CUTOFF_WEEKDAY and hour >= cutoff_hour


def run_subprocess(cmd: list[str], description: str) -> int:
    """Führt einen Subprocess aus und gibt den Return-Code zurück."""
    print(f"\n[weekly_run] {description}")
    print(f"  Aufruf: {' '.join(str(c) for c in cmd)}")
    env = {
        "PYTHONUTF8": "1",
        "PYTHONIOENCODING": "utf-8",
    }
    import os
    full_env = {**os.environ, **env}
    result = subprocess.run(cmd, env=full_env)
    if result.returncode != 0:
        print(f"[weekly_run] FEHLER: Exit-Code {result.returncode} bei '{description}'", file=sys.stderr)
    return result.returncode


def prune_logs(logs_dir: Path, retention_days: int, now: datetime) -> int:
    """Löscht Log-Dateien älter als retention_days. Gibt Anzahl gelöschter Dateien zurück."""
    if not logs_dir.exists():
        return 0
    deleted = 0
    cutoff_ts = now.timestamp() - retention_days * 86400
    for log_file in logs_dir.glob("*.log"):
        try:
            if log_file.stat().st_mtime < cutoff_ts:
                log_file.unlink()
                deleted += 1
        except OSError:
            pass
    return deleted


def main(argv: list[str] | None = None) -> int:
    ap = argparse.ArgumentParser(description="Auditor wöchentlicher Orchestrator.")
    ap.add_argument(
        "--mode",
        required=True,
        choices=["scheduled", "manual", "scan-only"],
        help="Ausführungsmodus.",
    )
    ap.add_argument(
        "--override-now",
        dest="override_now",
        default=None,
        help="ISO-Timestamp für deterministischen Zeit-Override (Eval-Tests). Beispiel: 2026-05-20T08:00",
    )
    ap.add_argument(
        "--vault",
        default=None,
        help="Vault-Root-Pfad (optional, wird sonst aus Bundle-Kontext abgeleitet).",
    )
    args = ap.parse_args(argv)

    now = _get_now(args.override_now)
    vault_arg = Path(args.vault) if args.vault else None

    cfg = load_skill_config(
        skill_dir=_SKILL_DIR,
        vault_root=vault_arg,
        bundle_root=None,
        default_paths={"reports_dir": "{system}/Auditor/reports"},
    )
    features = cfg.features if hasattr(cfg, "features") else {}
    cutoff_hour = int(features.get("cutoff_hour", 7))
    retention_days = int(features.get("log_retention_days", 56))

    print(f"[weekly_run] Modus: {args.mode} | Zeitpunkt: {now.isoformat()}")

    # Cutoff-Check nur im scheduled-Modus
    if args.mode == "scheduled" and is_past_cutoff(now, cutoff_hour):
        print(
            f"[weekly_run] Mittwoch-Cutoff ({cutoff_hour}:00) bereits abgelaufen. "
            f"Lauf wird übersprungen."
        )
        return 0

    python = sys.executable
    vault_args = ["--vault", str(vault_arg)] if vault_arg else []

    # Stufe 1: Scan
    rc = run_subprocess(
        [python, str(SCAN_SCRIPT)] + vault_args,
        "Stufe 1 — Scan",
    )
    if rc != 0:
        print("[weekly_run] Scan fehlgeschlagen. Abbruch.", file=sys.stderr)
        return rc

    if args.mode == "scan-only":
        print("[weekly_run] scan-only: Report-Render übersprungen.")
        return 0

    # Stufe 4: Roh-Report
    rc = run_subprocess(
        [python, str(REPORT_SCRIPT), "--run-type", "geplant" if args.mode == "scheduled" else "manuell"]
        + vault_args,
        "Stufe 4 — Roh-Report",
    )
    if rc != 0:
        print("[weekly_run] Report-Render fehlgeschlagen.", file=sys.stderr)
        return rc

    # Log-Retention
    logs_dir = _SKILL_DIR / "logs"
    deleted = prune_logs(logs_dir, retention_days, now)
    if deleted > 0:
        print(f"[weekly_run] Log-Retention: {deleted} Log(s) gelöscht (älter als {retention_days} Tage).")

    print("\n[weekly_run] Lauf abgeschlossen.")
    return 0


if __name__ == "__main__":
    sys.exit(main())
