"""``mgf-fed feedback`` — relay cross-repo feedback outboxes into target inboxes (FB-05).

A sync session is read-only on every other federation repo, so a finding destined elsewhere is
drafted into the source repo's ``pending-feedback/<target>.md`` *outbox*. This command is the other
half: the **supervisor** drains those outboxes into each target's ``FEEDBACK.md`` *inbox*, tracked +
idempotent. See ``mgf-standard/docs/recipes/feedback-relay.md``.

- ``mgf-fed feedback`` (default / ``--check``): scan every member's outbox, list un-relayed papers,
  exit non-zero if any exist — the no-silent-rot gate.
- ``mgf-fed feedback --relay [--target R] [--dry-run]``: assign the target's next ``PAPER-NN``,
  render the inbox entry, append it to the target ``FEEDBACK.md``, move the source paper to
  ``pending-feedback/relayed/``, and print the reviewed PR commands the supervisor lands.

Pure stdlib (pathlib + re); the cross-repo PRs stay a reviewed human step (printed, not
auto-pushed), per the "read-only on the federation except the supervisor" invariant.
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from pathlib import Path

from mgf.fed._roster import federation_root, members

_PAPER_SPLIT = re.compile(r"^### (?=PAPER\b)", re.MULTILINE)
_TITLE = re.compile(r"^### PAPER[^\n]*?\s[—-]\s+(?P<title>[^\n]+)", re.MULTILINE)
_SEVERITY = re.compile(r"\*\*Severity:\*\*\s*(?P<sev>[^\n]+)")
_ORIGIN = re.compile(r"\*\*(?:Origin|Filed by):\*\*\s*(?P<origin>[^\n]+)")
_DATE = re.compile(r"\b(\d{4}-\d{2}-\d{2})\b")
_PAPER_NO = re.compile(r"\bPAPER-(\d+)\b")


@dataclass(frozen=True, slots=True)
class OutboxPaper:
    """One un-relayed paper sitting in a source repo's ``pending-feedback/<target>.md``."""

    source: str  # the repo that drafted it (read-only on the target)
    target: str  # the repo it is destined for (== the outbox file stem)
    title: str
    severity: str
    origin: str  # the Origin/Filed-by line (becomes the inbox Submitter + cross-ref)
    block: str  # the verbatim ``### PAPER …`` markdown block
    file: Path  # the outbox file it lives in


def _parse_outbox(text: str, source: str, target: str, file: Path) -> list[OutboxPaper]:
    """Split a ``pending-feedback/<target>.md`` body into its ``### PAPER …`` blocks."""
    out: list[OutboxPaper] = []
    for chunk in _PAPER_SPLIT.split(text):
        if not chunk.startswith("PAPER"):
            continue
        block = "### " + chunk.rstrip() + "\n"
        title_m = _TITLE.search(block)
        sev_m = _SEVERITY.search(block)
        origin_m = _ORIGIN.search(block)
        out.append(
            OutboxPaper(
                source=source,
                target=target,
                title=(title_m.group("title").strip() if title_m else "(untitled)"),
                severity=(sev_m.group("sev").strip() if sev_m else ""),
                origin=(origin_m.group("origin").strip() if origin_m else source),
                block=block,
                file=file,
            )
        )
    return out


def scan(root: Path | None = None) -> list[OutboxPaper]:
    """Every un-relayed outbox paper across the fleet (``pending-feedback/*.md``)."""
    base = root or federation_root()
    found: list[OutboxPaper] = []
    for member in members(base):
        outbox = member.repo_dir / "pending-feedback"
        if not outbox.is_dir():
            continue
        for file in sorted(outbox.glob("*.md")):  # top-level only → skips relayed/ subdir
            found.extend(
                _parse_outbox(file.read_text(encoding="utf-8"), member.repo, file.stem, file)
            )
    return found


def _next_paper_number(feedback_text: str) -> int:
    """The target's next local ``PAPER-NN`` — max existing + 1 (FB-01: never reuse/renumber)."""
    nums = [int(n) for n in _PAPER_NO.findall(feedback_text)]
    return (max(nums) + 1) if nums else 1


def _render_inbox_entry(paper: OutboxPaper, number: int) -> str:
    """Render the FB-02 inbox entry for the target's ``FEEDBACK.md`` (+ the FB-01 cross-ref)."""
    date_m = _DATE.search(paper.origin)
    submitted = date_m.group(1) if date_m else "(see Origin)"
    sev = paper.severity or "—"
    xref = f"`{paper.source}/pending-feedback/{paper.target}.md`"
    # Strip the outbox title line; keep the body bullets verbatim.
    body = _TITLE.sub("", paper.block, count=1).strip()
    return (
        f"### PAPER-{number:02d} — {paper.title}\n\n"
        f"| Field | Value |\n|---|---|\n"
        f"| Status | OPEN |\n"
        f"| Submitter | {paper.origin} |\n"
        f"| Submitted | {submitted} |\n"
        f"| Severity | {sev} |\n"
        f"| Federation paper | relayed by the supervisor from {xref} (FB-05) |\n\n"
        f"{body}\n"
    )


def _pr_commands(repo: str, branch: str, paths: str, message: str) -> str:
    return (
        f"    cd {repo} && git checkout -b {branch} && git add {paths} \\\n"
        f"      && git commit -m {message!r} && git push -u origin {branch} \\\n"
        f"      && tea pr create --login codeberg --repo magogi-admin/{repo} "
        f"--base main --head {branch} --title {message!r}"
    )


def _relay_one(paper: OutboxPaper, number: int, root: Path, *, dry_run: bool) -> str:
    """Apply (or preview) one relay: append the target inbox entry + move the source to relayed/."""
    target_repo = root / paper.target
    feedback = target_repo / "FEEDBACK.md"
    entry = _render_inbox_entry(paper, number)
    head = f"{paper.source} → {paper.target}#PAPER-{number:02d}: {paper.title}"
    if dry_run:
        return f"  {head}\n" + "\n".join(f"      | {ln}" for ln in entry.splitlines())

    feedback.write_text(feedback.read_text(encoding="utf-8").rstrip() + "\n\n" + entry, "utf-8")
    src_text = paper.file.read_text(encoding="utf-8")
    paper.file.write_text(src_text.replace(paper.block, "").rstrip() + "\n", "utf-8")
    relayed_dir = paper.file.parent / "relayed"
    relayed_dir.mkdir(exist_ok=True)
    stamp = f"> **Relayed** to {paper.target}#PAPER-{number:02d} by the supervisor (FB-05).\n\n"
    (relayed_dir / f"{paper.target}-PAPER-{number:02d}.md").write_text(stamp + paper.block, "utf-8")
    return (
        f"  ✓ {head}\n"
        f"    + appended to {paper.target}/FEEDBACK.md\n"
        f"    + moved source paper → {paper.source}/pending-feedback/relayed/\n"
        f"    review the diff, then land both PRs (codeberg-pr.md):\n"
        + _pr_commands(
            paper.target,
            f"docs/feedback-paper-{number:02d}",
            "FEEDBACK.md",
            f"docs: relay PAPER-{number:02d} from {paper.source} (FB-05)",
        )
        + "\n"
        + _pr_commands(
            paper.source,
            "docs/feedback-relayed",
            "pending-feedback",
            f"docs: mark {paper.target}#PAPER-{number:02d} relayed (FB-05)",
        )
    )


def run_feedback(
    *,
    relay: bool = False,
    target: str | None = None,
    dry_run: bool = False,
    root: Path | None = None,
) -> int:
    """``mgf-fed feedback`` — scan the outboxes (gate) or relay them into target inboxes."""
    base = root or federation_root()
    papers = scan(base)
    if target:
        papers = [p for p in papers if p.target == target]

    if not relay:
        if not papers:
            print("No un-relayed feedback in any outbox ✓")
            return 0
        print(f"Un-relayed feedback ({len(papers)} paper(s) — FB-05 debt):")
        for p in papers:
            print(f"  {p.source} → {p.target}: {p.title}  {p.severity}".rstrip())
        print("\nRelay with `mgf-fed feedback --relay` (preview: add --dry-run).")
        return 1  # the gate: un-relayed feedback is a non-zero condition

    if not papers:
        print("Nothing to relay ✓" + (f" (target {target})" if target else ""))
        return 0
    # Assign target-local PAPER-NN sequentially, accounting for multiple papers per target.
    counters: dict[str, int] = {}
    print("Relay plan (dry-run — no files written):" if dry_run else "Relaying:")
    for p in papers:
        if p.target not in counters:
            fb = base / p.target / "FEEDBACK.md"
            counters[p.target] = _next_paper_number(
                fb.read_text(encoding="utf-8") if fb.is_file() else ""
            )
        number = counters[p.target]
        counters[p.target] += 1
        print(_relay_one(p, number, base, dry_run=dry_run))
    if not dry_run:
        print(
            "\nEdits applied locally + source papers moved to relayed/. "
            "Review + run the PR commands above."
        )
    return 0
