"""The shared federation applier — files received intents into the vault as DATA.

Extracted verbatim from `engine/federation_inbox.py` (V4 P2, docs/V4-FEDERATION.md
§5.1) so both transports use ONE applier: V1's authenticated-email path imports
these names back, and V2's relay/receive path (P5) calls the same functions on a
decrypted envelope's intents. The extraction is behavior-preserving — the V1
characterization tests (`tests/test_federation_apply.py`) pass unchanged and
assert the import-back identity.

SECURITY MODEL — the whole point, read before editing:

- **DATA, NEVER INSTRUCTIONS.** Intents are parsed into a FIXED schema
  (`todo | note | knowledge | event`). The applier ONLY files content: no
  sending mail, no reading or exfiltrating other notes, no shell, no tools. It
  contains no eval/exec/network/tool calls by construction. This boundary is
  what makes accepting outside content safe, on either transport.
- **PER-PEER SCOPE.** A peer may write only the intent types + areas its
  `permissions` allow. `permissions.intents` is the master allowlist (a type not
  listed is never written); `permissions.areas` narrows *area-carrying* intents
  (`todo`/`note`) to the listed areas but does not fence area-less intents
  (`knowledge`/`event`) — those are governed by `intents` alone. There is no
  separate CONFIDENTIAL bit (`areas[].confidential` was removed in 2.68.0); the
  allowlists ARE the write-protection model. An out-of-scope or incomplete
  intent is PARKED in a review note for the owner, never applied silently.
"""
import os
from pathlib import Path

# The fixed intent schema — the only shapes the applier will ever file.
ALLOWED_INTENTS = {"todo", "note", "knowledge", "event"}


def _safe_component(value):
    """A peer-supplied `area`/`subject` reduced to ONE safe path component, or None
    if it isn't a plain name. Peers control these strings and the applier builds file
    paths from them, so anything that could traverse — a separator, `..`, an absolute
    path, a leading dot, or a reserved name — is refused here (the intent then parks
    for the owner). This is the primary guard; `_within_vault` is the backstop."""
    s = (value or "").strip()
    if not s or len(s) > 128:
        return None
    if "/" in s or "\\" in s or "\x00" in s or ".." in s:
        return None
    if s.startswith(".") or os.path.isabs(s) or s in (".", ".."):
        return None
    return s


def _within_vault(vault, path):
    """Backstop: True only if `path` resolves INSIDE the vault (defense in depth —
    a component that slipped past _safe_component still can't escape the sandbox)."""
    try:
        vroot = Path(vault).resolve()
        return Path(path).resolve() == vroot or vroot in Path(path).resolve().parents
    except (OSError, ValueError, RuntimeError):
        return False


def validate_intents(data):
    """Return the validated intent list from a parsed envelope body.

    ``data`` is a dict carrying an ``intents`` list (V1's fenced JSON block, or
    V2's decrypted ``{"intents": [...]}`` body). Keeps only known intent types,
    preserving each intent dict verbatim; a malformed/absent list yields ``[]``.
    Type matching is case/space tolerant, matching the V1 behavior exactly.
    """
    out = []
    for it in (data.get("intents") or []):
        t = (it.get("type") or "").strip().lower()
        if t in ALLOWED_INTENTS:
            out.append(it)
    return out


def _permits(peer, intent_type, area):
    perms = peer.get("permissions") or {}
    intents = perms.get("intents")
    if intents is not None and intent_type not in intents:
        return False
    if area is not None:
        allowed = perms.get("areas")
        if allowed is not None and area not in allowed:
            return False
    return True


def _append(path, text):
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("a", encoding="utf-8") as f:
        if path.exists() and path.stat().st_size:
            f.write("\n")
        f.write(text.rstrip() + "\n")


def _via(peer):
    """Provenance stamp. When pairing resolved this peer to a real People note (F17),
    link it — `*(via [[Full Name]]'s vault)*` resolves. Otherwise PLAIN TEXT (F7): the
    peer's label rarely matches a People note exactly, so an unresolved `[[name]]` would
    dangle on arrival and vault-integrity would flag every stamp."""
    note = peer.get("people_note")
    if note:
        return f"*(via [[{note}]]'s vault)*"
    name = peer.get("name") or peer.get("owner") or "a peer"
    return f"*(via {name}'s vault)*"


# A parked intent's plan carries a mid-token (why it parked); map it to a human reason
# for the review queue (F8). Empty mid = the permission allowlist refused it.
_PARK_REASON = {" unsafe area": "unsafe area", "": "outside this peer's grant",
                " unsafe subject": "unsafe subject", " unsafe title": "unsafe title",
                " incomplete": "incomplete"}


def _reason(mid):
    return _PARK_REASON.get(mid, mid.strip())


def _plan_intent(peer, it, today, via, *, enforce_permits=True):
    """PURE planner: return ``(rel_path, text, label)`` to WRITE, or
    ``(None, mid, label)`` to PARK. No writes. Shared by ``apply_intents``
    (enforce_permits=True) and the owner-approve path (``file_intent`` with
    enforce_permits=False — files regardless of the peer's grant; the path
    sanitization below still applies). The success ``label`` and the park ``mid``
    tokens are preserved from the original inline logic so receipts read the same."""
    t = it["type"].strip().lower()
    area_raw = (it.get("area") or "").strip() or None
    area = _safe_component(area_raw) if area_raw else None
    if area_raw and area is None:                        # a peer sent a traversal area
        return None, " unsafe area", f"{t} -> review (unsafe area)"
    if enforce_permits and not _permits(peer, t, area):
        return None, "", f"{t} -> review"
    if t == "todo" and area:
        text = (f"- [ ] {it.get('text','').strip()} {via}\n"
                f"    - added {today}" + (f", due {it['due']}" if it.get("due") else ""))
        return Path("Areas") / area / f"{area} TODO.md", text, f"todo -> {area}"
    if t == "note" and area:
        title_raw = (it.get("title") or "").strip() or None
        if title_raw:
            # A PACKAGED note (`push --file … --as note`): lands as its OWN note in the
            # area, never as a bullet in the area note — a multi-section briefing is a
            # document, not a line. The title is peer-supplied, so it is sanitized like
            # an area; a repeat of the same title appends a second dated block.
            title = _safe_component(title_raw)
            if title is None:
                return None, " unsafe title", "note -> review (unsafe title)"
            if title.lower().endswith(".md"):
                title = title[:-3] or title
            body = it.get("text", "").strip()
            return (Path("Areas") / area / f"{title}.md",
                    f"> Received {today} {via}\n\n{body}", f"note -> {area}/{title}")
        return (Path("Areas") / area / f"{area}.md",
                f"- {today}: {it.get('text','').strip()} {via}", f"note -> {area}")
    if t == "knowledge" and it.get("subject"):
        subj = _safe_component(it["subject"])
        if subj is None:
            return None, " unsafe subject", "knowledge -> review (unsafe subject)"
        return (Path("Knowledge") / f"{subj}.md",
                f"## Our notes\n- {it.get('body','').strip()} {via} *({today})*",
                f"knowledge -> {subj}")
    if t == "event":
        return (Path("Events") / f"{today[:4]} Events.md",
                f"- **{it.get('date', today)}** — {it.get('text','').strip()} {via}", "event")
    return None, " incomplete", f"{t} -> review (incomplete)"


def file_intent(vault, peer, it, cfg, today, *, enforce_permits=True):
    """File ONE intent and return ``{status: applied|parked, ...}``. The owner-approve
    path (``review.approve``) calls it with ``enforce_permits=False`` — owner override
    of the peer's grant, with path sanitization still enforced. Never writes the review
    queue: the caller decides what to do with a parked result."""
    rel, text_or_mid, label = _plan_intent(peer, it, today, _via(peer),
                                            enforce_permits=enforce_permits)
    if rel is None:
        return {"status": "parked", "reason": _reason(text_or_mid)}
    if not _within_vault(vault, vault / rel):
        return {"status": "parked", "reason": "unsafe path"}
    _append(vault / rel, text_or_mid)
    return {"status": "applied", "label": label, "path": str(rel)}


def apply_intents(vault, peer, intents, cfg, today):
    """File each permitted intent into the vault as content, stamped with peer
    provenance. Out-of-scope/forbidden/unsafe intents are PARKED (as JSON objects in the
    review queue, review_store), never applied. Returns (applied, parked) lists of short
    descriptions. This function only writes notes/queue; it contains no
    eval/exec/network/tool calls, and every peer-derived path component is sanitized +
    jailed to the vault."""
    from alexandria_vault.federation import review_store
    via = _via(peer)
    applied, parked = [], []
    for it in intents:
        rel, text_or_mid, label = _plan_intent(peer, it, today, via)   # enforce_permits
        if rel is None:
            review_store.park(vault, peer, it, _reason(text_or_mid), today)
            parked.append(label)
            continue
        if not _within_vault(vault, vault / rel):        # backstop behind _safe_component
            review_store.park(vault, peer, it, "unsafe path", today)
            parked.append(f"{label} (unsafe path)")
            continue
        _append(vault / rel, text_or_mid)
        applied.append(label)
    return applied, parked
