import argparse
import json
from pathlib import Path


def _read_json(path):
    return json.loads(Path(path).read_text(encoding="utf-8-sig"))


def _load_config(config_path):
    path = Path(config_path)
    if not path.exists():
        return {}
    return _read_json(path)


def _resolve_relative(root, value, default):
    raw = Path(value or default)
    if raw.is_absolute():
        return raw
    return Path(root) / raw


def resolve_message_paths(repo_root=".", config_path=".prd_plugin/config.json"):
    root = Path(repo_root).resolve()
    config = _load_config(config_path)
    paths = config.get("paths", {}) if isinstance(config, dict) else {}
    return {
        "requests": _resolve_relative(root, paths.get("requests_file"), ".prd_plugin/state/requests.json"),
        "inbox": _resolve_relative(root, paths.get("inbox_dir"), ".prd_plugin/inbox"),
        "outbox": _resolve_relative(root, paths.get("outbox_dir"), ".prd_plugin/outbox"),
        "mailboxes": _resolve_relative(root, paths.get("mailboxes_dir"), ".prd_plugin/mailboxes"),
    }


def _repo_relative(root, path):
    try:
        return Path(path).resolve().relative_to(Path(root).resolve()).as_posix()
    except ValueError:
        return str(path)


def _load_requests(path):
    if not Path(path).exists():
        return []
    data = _read_json(path)
    if isinstance(data, dict) and isinstance(data.get("requests"), list):
        return data["requests"]
    if isinstance(data, dict) and isinstance(data.get("id"), str):
        return [data]
    return []


def _package_identity(package, fallback_origin=None):
    if not isinstance(package, dict):
        return {"origin_repo": fallback_origin or "unknown", "source_request_id": None}
    return {
        "origin_repo": package.get("origin_repo") or fallback_origin or "unknown",
        "source_request_id": package.get("source_request_id") or package.get("id"),
    }


def _import_matches(package, requests):
    identity = _package_identity(package)
    matches = []
    for request in requests:
        if not isinstance(request, dict):
            continue
        if (
            request.get("origin_repo") == identity["origin_repo"]
            and request.get("source_request_id") == identity["source_request_id"]
        ):
            matches.append(request.get("id", "<missing-id>"))
    return matches


def _discover_json_files(directory):
    root = Path(directory)
    if not root.exists():
        return []
    return sorted(path for path in root.rglob("*.json") if path.is_file())


def _inbox_packages(repo_root, inbox_dir, requests):
    packages = []
    for path in _discover_json_files(inbox_dir):
        package = _read_json(path)
        fallback_origin = path.parent.parent.name if path.parent.name == "incoming" else path.parent.name
        identity = _package_identity(package, fallback_origin)
        matches = _import_matches({**package, **identity}, requests)
        packages.append(
            {
                "path": _repo_relative(repo_root, path),
                "origin_repo": identity["origin_repo"],
                "source_request_id": identity["source_request_id"],
                "summary": package.get("summary", ""),
                "imported_as": matches,
                "status": "imported" if matches else "new",
            }
        )
    return packages


def _simple_transport_files(repo_root, directory):
    files = []
    for path in _discover_json_files(directory):
        try:
            data = _read_json(path)
        except json.JSONDecodeError:
            data = {}
        files.append(
            {
                "path": _repo_relative(repo_root, path),
                "requests": len(data.get("requests", [])) if isinstance(data, dict) else 0,
            }
        )
    return files


def _thread_messages(request):
    thread = request.get("thread", {}) if isinstance(request, dict) else {}
    messages = thread.get("messages", []) if isinstance(thread, dict) else []
    return messages if isinstance(messages, list) else []


def _thread_status(request):
    thread = request.get("thread", {}) if isinstance(request, dict) else {}
    if not isinstance(thread, dict):
        return ""
    return str(thread.get("status", "")).strip().lower()


def _unresolved_threads(requests):
    unresolved = []
    for request in requests:
        messages = _thread_messages(request)
        if messages and _thread_status(request) not in {"resolved", "closed"}:
            unresolved.append(
                {
                    "id": request.get("id", "<missing-id>"),
                    "status": request.get("status", "proposed"),
                    "thread_status": _thread_status(request) or "unknown",
                    "messages": len(messages),
                    "latest_message_id": messages[-1].get("id", "<missing-message-id>")
                    if isinstance(messages[-1], dict)
                    else "<invalid-message>",
                }
            )
    return unresolved


TERMINAL_STATUSES = {"implemented", "rejected"}
# Visibility is DIRECTIONAL: `repo`/`public` travel to the origin repo, while
# `upstream` is hub-only and the mailbox correctly refuses to forward it. A
# reply the origin repo can never receive is not an answer (REQ-130).
DELIVERABLE_VISIBILITIES = {"repo", "public"}


def _unanswered_resolutions(requests):
    """Imported requests at terminal status with no upstream-visibility reply
    (REQ-124): the origin repo cannot learn the outcome until a reply is
    recorded and the scoped mailbox republished — its tooling warns forever."""
    rows = []
    for request in requests:
        if not isinstance(request, dict):
            continue
        if request.get("status") not in TERMINAL_STATUSES:
            continue
        if not request.get("origin_repo") or not request.get("source_request_id"):
            continue
        messages = _thread_messages(request)
        deliverable = [m for m in messages if isinstance(m, dict)
                       and str(m.get("visibility", "repo")).strip().lower()
                       in DELIVERABLE_VISIBILITIES]
        if deliverable:
            continue
        hub_only = [m for m in messages if isinstance(m, dict)
                    and str(m.get("visibility", "")).strip().lower() == "upstream"]
        rows.append({
            "id": request.get("id", "<missing-id>"),
            "status": request.get("status"),
            "origin_repo": request.get("origin_repo"),
            "source_request_id": request.get("source_request_id"),
            "reason": ("undeliverable: the only replies are hub-only "
                       "(visibility 'upstream'); reply with visibility 'repo' so "
                       "the origin repo receives the body"
                       if hub_only else "no reply recorded"),
        })
    return rows


def _pending_outbound(requests):
    """Replies written but never sent (REQ-133). Delivery state lives on the
    message, so an unsent reply is visible rather than inferred."""
    rows = []
    for request in requests:
        if not isinstance(request, dict):
            continue
        for message in _thread_messages(request):
            if not isinstance(message, dict):
                continue
            if (message.get("delivery") or {}).get("state") == "pending":
                rows.append({"id": request.get("id", "<missing-id>"),
                             "message_id": message.get("id"),
                             "origin_repo": request.get("origin_repo")})
    return rows


def build_message_check(repo_root=".", config_path=".prd_plugin/config.json"):
    root = Path(repo_root).resolve()
    paths = resolve_message_paths(root, config_path)
    requests = _load_requests(paths["requests"])
    inbox_packages = _inbox_packages(root, paths["inbox"], requests)
    outbox_packages = _simple_transport_files(root, paths["outbox"])
    mailbox_files = _simple_transport_files(root, paths["mailboxes"])
    unresolved = _unresolved_threads(requests)
    unanswered = _unanswered_resolutions(requests)
    pending_outbound = _pending_outbound(requests)
    new_inbox = [package for package in inbox_packages if package["status"] == "new"]
    imported_inbox = [package for package in inbox_packages if package["status"] == "imported"]

    report = {
        "status": "attention" if new_inbox or outbox_packages or mailbox_files or unresolved or unanswered or pending_outbound else "clear",
        "paths": {key: _repo_relative(root, value) for key, value in paths.items()},
        "totals": {
            "canonical_requests": len(requests),
            "inbox_packages": len(inbox_packages),
            "new_inbox_packages": len(new_inbox),
            "imported_inbox_packages": len(imported_inbox),
            "outbox_packages": len(outbox_packages),
            "mailbox_files": len(mailbox_files),
            "unresolved_threads": len(unresolved),
            "unanswered_resolutions": len(unanswered),
            "pending_outbound_replies": len(pending_outbound),
        },
        "new_inbox_packages": new_inbox,
        "imported_inbox_packages": imported_inbox,
        "outbox_packages": outbox_packages,
        "mailbox_files": mailbox_files,
        "unresolved_threads": unresolved,
        "unanswered_resolutions": unanswered,
        "pending_outbound_replies": pending_outbound,
    }
    return report


def _lines_for_items(items, formatter):
    if not items:
        return ["- None"]
    return [formatter(item) for item in items]


def format_markdown(report):
    totals = report["totals"]
    lines = [
        "# PRD Plugin Message Check",
        "",
        f"Status: `{report['status']}`",
        "",
        "| Metric | Count |",
        "| --- | ---: |",
        f"| Canonical requests | {totals['canonical_requests']} |",
        f"| Inbox packages | {totals['inbox_packages']} |",
        f"| New inbox packages | {totals['new_inbox_packages']} |",
        f"| Imported inbox packages | {totals['imported_inbox_packages']} |",
        f"| Outbox packages | {totals['outbox_packages']} |",
        f"| Mailbox files | {totals['mailbox_files']} |",
        f"| Unresolved threads | {totals['unresolved_threads']} |",
        f"| Unanswered resolutions | {totals.get('unanswered_resolutions', 0)} |",
        f"| Replies pending delivery | {totals.get('pending_outbound_replies', 0)} |",
        "",
        "## New Inbox Packages",
        "",
    ]
    lines.extend(
        _lines_for_items(
            report["new_inbox_packages"],
            lambda item: f"- `{item['path']}` from `{item['origin_repo']}` source `{item['source_request_id']}`",
        )
    )
    lines.extend(["", "## Imported Inbox Packages", ""])
    lines.extend(
        _lines_for_items(
            report["imported_inbox_packages"],
            lambda item: f"- `{item['path']}` imported as {', '.join(f'`{rid}`' for rid in item['imported_as'])}",
        )
    )
    lines.extend(["", "## Outbox Packages", ""])
    lines.extend(_lines_for_items(report["outbox_packages"], lambda item: f"- `{item['path']}`"))
    lines.extend(["", "## Mailbox Files", ""])
    lines.extend(_lines_for_items(report["mailbox_files"], lambda item: f"- `{item['path']}`"))
    lines.extend(["", "## Unresolved Request Threads", ""])
    lines.extend(
        _lines_for_items(
            report["unresolved_threads"],
            lambda item: f"- `{item['id']}`: `{item['thread_status']}`, {item['messages']} message(s), latest `{item['latest_message_id']}`",
        )
    )
    return "\n".join(lines) + "\n"


def main(argv=None):
    parser = argparse.ArgumentParser(description="Check PRD Plugin inbox, outbox, mailboxes, and request threads.")
    parser.add_argument("--repo-root", default=".", help="Repository root to inspect.")
    parser.add_argument("--config", default=".prd_plugin/config.json", help="Path to PRD Plugin config.json.")
    parser.add_argument("--format", choices=("json", "markdown"), default="markdown")
    parser.add_argument("--output", help="Optional output file.")
    args = parser.parse_args(argv)

    root = Path(args.repo_root).resolve()
    config = Path(args.config)
    if not config.is_absolute():
        config = root / config
    report = build_message_check(root, config)
    content = json.dumps(report, indent=2) + "\n" if args.format == "json" else format_markdown(report)

    if args.output:
        output = Path(args.output)
        if not output.is_absolute():
            output = root / output
        output.parent.mkdir(parents=True, exist_ok=True)
        output.write_text(content, encoding="utf-8")
    else:
        print(content, end="")


if __name__ == "__main__":
    main()
