"""
write_inbox_note.py — Schreibt eine Inbox-Notiz als atomare Markdown-Datei.

Wird vom memory-router-Skill aufgerufen, wenn der User signalisiert, dass
ein Inhalt im Vault festgehalten werden soll ("merk dir das", "speicher
das"). Schreibt die Datei nach {vault_root}/01 Inbox/<Titel>.md mit
sauberem Frontmatter (type: notiz, agent_ready: true, description, tags,
created, updated) und einem Body aus H1 plus einleitendem Absatz.

Design-Prinzipien:
- Deterministisches Verhalten: Pfad-Auflösung, Frontmatter-Erzeugung
  ohne Stochastik.
- Atomares Schreiben: Tempfile + os.replace, kein partieller Output.
- Schreib-Allowlist: nur unter {vault_root}/01 Inbox/. Versuche
  außerhalb führen zu non-zero exit (Stefan-Daten-Disziplin).
- Pfad-Sanitization: Title wird gegen ein striktes Whitelist-Regex
  gefiltert (Latein + deutsche Umlaute + Bindestrich/Underscore/Space).
- Stdout: nur der finale Pfad bei Erfolg. Stderr: Fehler.

Aufruf:
    python write_inbox_note.py --title "Lieferant X dienstags" \
        --content "Lieferant X liefert nur dienstags." \
        --vault-root /path/to/vault
    python write_inbox_note.py --title T --content C --tags inbox,wissen \
        --vault-root /path/to/vault --dry-run
"""
from __future__ import annotations

import argparse
import os
import re
import sys
import tempfile
from datetime import datetime
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

# ---------------------------------------------------------------------------
# Hilfsfunktionen
# ---------------------------------------------------------------------------

# Erlaubte Zeichen im Title: Latein-Buchstaben, Ziffern, Leerzeichen,
# deutsche Umlaute (ä ö ü Ä Ö Ü ß), Bindestrich, Underscore.
_TITLE_ALLOWED = re.compile(r"[^A-Za-z0-9 äöüÄÖÜß\-_]")


def sanitize_title(title: str) -> str:
    """Ersetzt nicht erlaubte Zeichen durch Bindestrich, normalisiert Spaces.

    Verhindert Path-Traversal, ungueltige Dateinamen, Windows-Sonderzeichen.
    """
    cleaned = _TITLE_ALLOWED.sub("-", title)
    # Mehrfach-Bindestriche zusammenfassen, vorne/hinten trimmen.
    cleaned = re.sub(r"-{2,}", "-", cleaned).strip(" -_")
    if not cleaned:
        raise ValueError("Title nach Sanitization leer.")
    return cleaned


def build_frontmatter(
    title: str,
    description: str,
    tags: list[str],
    today: str,
) -> str:
    """Baut den YAML-Frontmatter-Block.

    `description` wird IMMER in doppelten Anführungszeichen geschrieben —
    Pflicht laut Skill-Doku, damit ein ': ' im Beschreibungstext den
    YAML-Parser nicht zerlegt.
    """
    safe_desc = description.replace('"', '\\"').replace("\n", " ").strip()
    if len(safe_desc) > 300:
        safe_desc = safe_desc[:297] + "..."

    base_tags = ["inbox"]
    all_tags = base_tags + [t for t in tags if t and t not in base_tags]
    tags_yaml = "\n".join(f"  - {t}" for t in all_tags)

    lines = [
        "---",
        "type: notiz",
        "agent_ready: true",
        f'description: "{safe_desc}"',
        "tags:",
        tags_yaml,
        f"created: {today}",
        f"updated: {today}",
        "---",
        "",
    ]
    return "\n".join(lines) + "\n"


def build_body(title: str, content: str) -> str:
    """Baut Body aus H1 plus einleitendem Absatz."""
    body_text = content.strip() or f"Notiz zu {title}."
    return f"# {title}\n\n{body_text}\n"


def resolve_target_path(inbox_dir: Path, title: str, today: str) -> Path:
    """Bestimmt den Zielpfad. Bei Kollision Datums-Suffix anhängen.

    Schritte:
    1. <inbox>/<sanitized>.md
    2. Wenn existiert: <inbox>/<sanitized>_<YYYY-MM-DD>.md
    3. Wenn auch das existiert: <inbox>/<sanitized>_<YYYY-MM-DD>_<n>.md
    """
    safe = sanitize_title(title)
    candidate = inbox_dir / f"{safe}.md"
    if not candidate.exists():
        return candidate
    candidate = inbox_dir / f"{safe}_{today}.md"
    if not candidate.exists():
        return candidate
    counter = 2
    while True:
        candidate = inbox_dir / f"{safe}_{today}_{counter}.md"
        if not candidate.exists():
            return candidate
        counter += 1


def atomic_write(path: Path, text: str) -> None:
    """Schreibt Text atomar: Temp-Datei im selben Verzeichnis, dann os.replace."""
    path.parent.mkdir(parents=True, exist_ok=True)
    fd, tmp_path = tempfile.mkstemp(
        prefix=".memory-router-",
        suffix=".tmp",
        dir=str(path.parent),
    )
    try:
        with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as fh:
            fh.write(text)
        os.replace(tmp_path, path)
    except Exception:
        # Aufräumen, falls os.replace nicht stattgefunden hat.
        try:
            os.unlink(tmp_path)
        except OSError:
            pass
        raise


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Schreibt eine Inbox-Notiz mit Frontmatter in den Vault.",
    )
    parser.add_argument("--title", required=True, help="Titel der Notiz (Pflicht).")
    parser.add_argument(
        "--content",
        required=True,
        help="Markdown-Body der Notiz (Pflicht). Mehrzeilig erlaubt.",
    )
    parser.add_argument(
        "--tags",
        default="inbox",
        help="Komma-separierte Tag-Liste. Default: 'inbox'.",
    )
    parser.add_argument(
        "--vault-root",
        type=Path,
        required=True,
        help="Vault-Wurzel-Pfad. Inbox liegt unter <vault-root>/01 Inbox/ (per Default).",
    )
    parser.add_argument(
        "--bundle-root",
        type=Path,
        default=None,
        help="Optionaler Bundle-Root-Pfad (Default: Skill-Parent).",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Nur den Zielpfad ausgeben, nichts schreiben.",
    )
    args = parser.parse_args()

    try:
        vault_root = args.vault_root.resolve()
        if not vault_root.exists():
            print(f"vault-root existiert nicht: {vault_root}", file=sys.stderr)
            return 2

        # Config laden — der Loader loest {vault_root}/01 Inbox auf, wenn
        # die example-Config gesetzt ist. Skill-Default greift sonst.
        config = load_skill_config(
            skill_dir=_SKILL_DIR,
            vault_root=vault_root,
            bundle_root=args.bundle_root,
            default_paths={
                "inbox_dir": "{inbox}",
            },
        )

        # Inbox-Pfad aus Config holen. Pflicht: muss unter vault_root liegen
        # (Schreib-Allowlist gegen Stefan-Daten-Leak und Path-Traversal).
        inbox_dir = config.paths.get("inbox_dir") or (vault_root / "01 Inbox")
        inbox_dir = inbox_dir.resolve()

        # Allowlist-Prüfung: inbox_dir muss innerhalb vault_root liegen.
        try:
            inbox_dir.relative_to(vault_root)
        except ValueError:
            print(
                f"Schreib-Allowlist-Verletzung: inbox_dir {inbox_dir} liegt "
                f"nicht unter vault-root {vault_root}.",
                file=sys.stderr,
            )
            return 3

        # Tags parsen.
        tags = [t.strip() for t in (args.tags or "").split(",") if t.strip()]

        today = datetime.now().strftime("%Y-%m-%d")

        # Description: erste Zeile des Contents oder Title-Fallback.
        first_line = args.content.strip().splitlines()[0] if args.content.strip() else ""
        description = first_line if first_line else args.title

        target = resolve_target_path(inbox_dir, args.title, today)

        # Zweite Allowlist-Prüfung gegen den finalen Pfad — verhindert
        # symlink-Tricks oder Title-Pfad-Injection.
        target_resolved = target.resolve()
        # parent gegen inbox_dir prüfen — target selbst existiert noch nicht.
        try:
            target_resolved.parent.relative_to(inbox_dir)
        except ValueError:
            print(
                f"Schreib-Allowlist-Verletzung: Ziel {target_resolved} liegt "
                f"nicht unter inbox_dir {inbox_dir}.",
                file=sys.stderr,
            )
            return 4

        frontmatter = build_frontmatter(args.title, description, tags, today)
        body = build_body(args.title, args.content)

        if args.dry_run:
            print(str(target))
            return 0

        atomic_write(target, frontmatter + body)
        print(str(target))
        return 0
    except ValueError as exc:
        print(f"memory-router write_inbox_note.py error: {exc}", file=sys.stderr)
        return 5
    except Exception as exc:  # noqa: BLE001
        print(f"memory-router write_inbox_note.py error: {exc}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    raise SystemExit(main())
