import argparse
import json
import re
from pathlib import Path


REVIEW_RISKS = {"medium", "high", "critical"}
REVIEW_RECOMMENDATIONS = {"review_first", "blocked"}
PRERELEASE_ORDER = {"alpha": 0, "beta": 1, "rc": 2}


def script_repo_root():
    return Path(__file__).resolve().parents[1]


def default_releases_path():
    return script_repo_root() / ".prd_plugin" / "state" / "releases.json"


def parse_version(version):
    match = re.match(
        r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:[-.]([A-Za-z]+)(?:[.-]?(\d+))?)?",
        str(version),
    )
    if not match:
        return (0, 0, 0, 3, 0)

    major, minor, patch, prerelease_label, prerelease_number = match.groups()
    prerelease_rank = 3
    if prerelease_label:
        prerelease_rank = PRERELEASE_ORDER.get(prerelease_label.lower(), -1)

    return (
        int(major or 0),
        int(minor or 0),
        int(patch or 0),
        prerelease_rank,
        int(prerelease_number or 0),
    )


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


def detect_installed_version(installed_repo):
    repo = Path(installed_repo)

    config_path = repo / ".prd_plugin" / "config.json"
    if config_path.exists():
        config = _read_json(config_path)
        plugin = config.get("plugin", {})
        installed_version = plugin.get("installed_version") or plugin.get("version")
        if installed_version:
            return str(installed_version)

    plugin_manifest = repo / ".codex-plugin" / "plugin.json"
    if plugin_manifest.exists():
        manifest = _read_json(plugin_manifest)
        if manifest.get("version"):
            return str(manifest["version"])

    opencode_manifest = repo / ".opencode" / "plugin.json"
    if opencode_manifest.exists():
        manifest = _read_json(opencode_manifest)
        if manifest.get("version"):
            return str(manifest["version"])

    claude_manifest = repo / ".claude-plugin" / "plugin.json"
    if claude_manifest.exists():
        manifest = _read_json(claude_manifest)
        if manifest.get("version"):
            return str(manifest["version"])

    state_project = repo / ".prd_plugin" / "state" / "project.json"
    if state_project.exists():
        project = _read_json(state_project)
        plugin = project.get("plugin", {})
        installed_version = plugin.get("installed_version") or plugin.get("version")
        if installed_version:
            return str(installed_version)

    return None


def detect_latest_version(hub_repo=None):
    hub_repo = script_repo_root() if hub_repo is None else Path(hub_repo)
    for manifest_path in (
        ".codex-plugin/plugin.json",
        ".opencode/plugin.json",
        ".claude-plugin/plugin.json",
    ):
        manifest = Path(hub_repo) / manifest_path
        if not manifest.exists():
            continue
        data = _read_json(manifest)
        if data.get("version"):
            return str(data["version"])
    return None


def load_releases(releases_path):
    path = Path(releases_path)
    if not path.exists():
        raise FileNotFoundError(f"Release metadata not found at {path}. Pass --releases with the PRD Plugin hub releases.json path.")
    data = _read_json(path)
    releases = data.get("releases", [])
    if not isinstance(releases, list):
        raise ValueError(f"{releases_path} must contain a 'releases' array")
    return sorted(releases, key=lambda release: parse_version(release.get("version", "0.0.0")))


def _recommendation_for_releases(newer_releases):
    if not newer_releases:
        return "up_to_date"

    for release in newer_releases:
        risk = str(release.get("risk", "low")).lower()
        recommendation = str(release.get("recommendation", "safe_to_update")).lower()
        if risk in REVIEW_RISKS or recommendation in REVIEW_RECOMMENDATIONS:
            return "review_first" if recommendation != "blocked" else "blocked"

    return "safe_to_update"


def build_version_advice(installed_repo, latest_version, releases_path, installed_version=None):
    installed = installed_version or detect_installed_version(installed_repo)
    releases = load_releases(releases_path)

    if installed:
        newer_releases = [
            release
            for release in releases
            if parse_version(installed) < parse_version(release.get("version", "0.0.0")) <= parse_version(latest_version)
        ]
    else:
        newer_releases = [
            release
            for release in releases
            if parse_version(release.get("version", "0.0.0")) <= parse_version(latest_version)
        ]

    recommendation = _recommendation_for_releases(newer_releases)

    return {
        "status": "ok" if installed else "installed_version_unknown",
        "installed_repo": str(Path(installed_repo)),
        "installed_version": installed,
        "latest_version": latest_version,
        "newer_releases": newer_releases,
        "recommendation": recommendation,
        "request_type": "version_change" if recommendation in {"review_first", "blocked"} else None,
    }


def _next_request_id(requests):
    max_id = 0
    for request in requests:
        request_id = str(request.get("id", ""))
        match = re.match(r"REQ-(\d+)$", request_id)
        if match:
            max_id = max(max_id, int(match.group(1)))
    return f"REQ-{max_id + 1:03d}"


def append_version_change_request(
    advice,
    requests_path,
    requested_by_agent="AGENT-001",
    requested_from_session="SES-001",
):
    path = Path(requests_path)
    if path.exists():
        data = _read_json(path)
    else:
        data = {"schema_version": "0.1", "requests": []}

    requests = data.setdefault("requests", [])
    if not isinstance(requests, list):
        raise ValueError(f"{path} must contain a 'requests' array")

    latest_version = advice.get("latest_version")
    installed_version = advice.get("installed_version")
    target_repo = advice.get("installed_repo", "")
    migration_notes = []
    for release in advice.get("newer_releases", []):
        migration_notes.extend(release.get("migration_notes", []))

    request = {
        "id": _next_request_id(requests),
        "status": "proposed",
        "request_type": "version_change",
        "scope": "local",
        "origin_repo": target_repo,
        "visibility": "repo",
        "upstream_submission": False,
        "upstream_request_id": None,
        "source_request_id": None,
        "summary": f"Review PRD Plugin update from {installed_version or 'unknown'} to {latest_version}.",
        "rationale": f"Version advice recommended `{advice.get('recommendation')}` before updating.",
        "severity": "medium" if advice.get("recommendation") in {"review_first", "blocked"} else "low",
        "requested_by_agent": requested_by_agent,
        "requested_from_session": requested_from_session,
        "affected_versions": [installed_version, latest_version] if installed_version else [latest_version],
        "current_plugin_version": installed_version,
        "requested_plugin_version": latest_version,
        "target_repo": target_repo,
        "update_window": None,
        "breaking_change_concerns": [
            release.get("summary", "")
            for release in advice.get("newer_releases", [])
            if release.get("risk") in {"medium", "high", "critical"}
        ],
        "migration_blockers": migration_notes,
        "affected_environments": [],
        "compatibility_scope": [],
        "affected_areas": [".prd_plugin/config.json", ".prd_plugin/state/releases.json"],
        "linked_ids": [],
        "linked_health_findings": [],
        "source_refs": [],
        "proposed_actions": ["Review release notes before bumping plugin.installed_version."],
        "reproduction_steps": [],
        "expected_behavior": "",
        "actual_behavior": "",
        "workaround": None,
        "regression": False,
        "risk": "medium" if advice.get("recommendation") in {"review_first", "blocked"} else "low",
        "created_at": "",
        "reviewed_at": None,
        "reviewed_by_agent": None,
        "decision": None,
        "decision_rationale": None,
        "thread": {"status": "open", "participants": [], "messages": []},
        "graduated_to": [],
    }
    requests.append(request)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
    return request


def format_markdown(advice):
    lines = [
        "# PRD Plugin Version Advice",
        "",
        f"Status: `{advice['status']}`",
        f"Installed repo: `{advice['installed_repo']}`",
        f"Installed version: `{advice['installed_version'] or 'unknown'}`",
        f"Latest version: `{advice['latest_version']}`",
        f"Recommendation: `{advice['recommendation']}`",
        "",
        "## Newer Releases",
        "",
    ]

    if not advice["newer_releases"]:
        lines.append("- None")
    for release in advice["newer_releases"]:
        lines.extend(
            [
                f"### {release.get('version', 'unknown')}",
                "",
                release.get("summary", ""),
                "",
                f"- Risk: `{release.get('risk', 'unspecified')}`",
                f"- Recommendation: `{release.get('recommendation', 'unspecified')}`",
            ]
        )
        for change in release.get("changes", []):
            lines.append(f"- Change: {change}")
        for note in release.get("migration_notes", []):
            lines.append(f"- Migration: {note}")
        lines.append("")

    if advice["request_type"]:
        lines.extend(
            [
                "## Suggested Request",
                "",
                "`request_type: version_change` is recommended before updating.",
            ]
        )

    return "\n".join(lines).rstrip() + "\n"


def main():
    parser = argparse.ArgumentParser(description="Show PRD Plugin changes before a repo upgrades.")
    parser.add_argument("--installed-repo", default=".", help="Repo using PRD Plugin.")
    parser.add_argument("--latest-version", help="Latest hub version. Defaults to .codex-plugin/plugin.json.")
    parser.add_argument("--releases", help="Path to PRD Plugin hub releases.json. Defaults to the hub repo beside this script.")
    parser.add_argument("--installed-version", help="Override detected installed version.")
    parser.add_argument("--write-request", action="store_true", help="Append a local version_change request when review is advised.")
    parser.add_argument("--requests", default=".prd_plugin/state/requests.json", help="Requests file used with --write-request.")
    parser.add_argument("--requested-by-agent", default="AGENT-001")
    parser.add_argument("--requested-from-session", default="SES-001")
    parser.add_argument("--format", choices=("json", "markdown"), default="markdown")
    parser.add_argument("--output")
    args = parser.parse_args()

    releases_path = Path(args.releases) if args.releases else default_releases_path()
    latest_version = args.latest_version or detect_latest_version()
    if not latest_version:
        raise SystemExit("Could not determine latest version; pass --latest-version.")

    try:
        advice = build_version_advice(
            args.installed_repo,
            latest_version,
            releases_path,
            installed_version=args.installed_version,
        )
    except FileNotFoundError as exc:
        raise SystemExit(str(exc)) from exc
    if args.write_request and advice.get("request_type") == "version_change":
        advice["written_request"] = append_version_change_request(
            advice,
            args.requests,
            requested_by_agent=args.requested_by_agent,
            requested_from_session=args.requested_from_session,
        )
    content = json.dumps(advice, indent=2) + "\n" if args.format == "json" else format_markdown(advice)

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


if __name__ == "__main__":
    main()
