import argparse
import json
import subprocess
import sys
from pathlib import Path


EXCLUDED_DIR_NAMES = {".git", ".mypy_cache", ".pytest_cache", ".ruff_cache", ".venv", "__pycache__", "node_modules"}
EXCLUDED_RELATIVE_PREFIXES = (".prd_plugin/local/",)


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


def _relative_posix(root, path):
    return path.relative_to(root).as_posix()


def _is_excluded(root, path):
    relative = _relative_posix(root, path)
    if any(relative.startswith(prefix) for prefix in EXCLUDED_RELATIVE_PREFIXES):
        return True
    return any(part in EXCLUDED_DIR_NAMES for part in path.parts)


def _discover_files(root, suffix):
    root = Path(root)
    return sorted(
        path
        for path in root.rglob(f"*{suffix}")
        if path.is_file() and not _is_excluded(root, path)
    )


def discover_json_files(root=None):
    return _discover_files(Path(root or repo_root()), ".json")


def discover_jsonl_files(root=None):
    return _discover_files(Path(root or repo_root()), ".jsonl")


def validate_structured_files(root=None):
    root = Path(root or repo_root())
    errors = []

    for path in discover_json_files(root):
        try:
            json.loads(path.read_text(encoding="utf-8-sig"))
        except json.JSONDecodeError as exc:
            errors.append(f"{_relative_posix(root, path)}: JSON parse error at line {exc.lineno}, column {exc.colno}: {exc.msg}")

    for path in discover_jsonl_files(root):
        for line_number, line in enumerate(path.read_text(encoding="utf-8-sig").splitlines(), start=1):
            if not line.strip():
                continue
            try:
                json.loads(line)
            except json.JSONDecodeError as exc:
                errors.append(
                    f"{_relative_posix(root, path)}:{line_number}: JSONL parse error at column {exc.colno}: {exc.msg}"
                )

    return errors


def load_plugin_version(root=None):
    root = Path(root or repo_root())
    for manifest_path in (
        ".codex-plugin/plugin.json",
        ".opencode/plugin.json",
        ".claude-plugin/plugin.json",
    ):
        candidate = root / manifest_path
        if candidate.is_file():
            manifest = json.loads(candidate.read_text(encoding="utf-8-sig"))
            if manifest.get("version"):
                return str(manifest["version"])
    raise FileNotFoundError(
        "No plugin manifest found in .codex-plugin/, .opencode/, or .claude-plugin/"
    )


def build_workflow_commands(plugin_version=None, python_executable=None, include_system_tests=False):
    python_executable = python_executable or sys.executable
    plugin_version = plugin_version or load_plugin_version(repo_root())
    commands = [
        [python_executable, "-m", "unittest", "discover", "-s", "tests", "-v"],
        [
            python_executable,
            "scripts/feature_skill_audit.py",
            "--repo-root",
            ".",
            "--format",
            "json",
        ],
        [python_executable, "scripts/gap_audit.py", "--target-version", plugin_version],
        [
            python_executable,
            "scripts/version_advice.py",
            "--installed-repo",
            ".",
            "--format",
            "json",
            "--output",
            "request-report/version-advice.json",
        ],
        [
            python_executable,
            "scripts/release_check.py",
            "--output",
            "request-report/release-hygiene.md",
        ],
        [
            python_executable,
            "scripts/prd_doctor.py",
            "--repo-root",
            ".",
            "--format",
            "json",
            "--output",
            "request-report/prd-doctor.json",
        ],
        [
            python_executable,
            "scripts/state_consistency_check.py",
            "--repo-root",
            ".",
            "--format",
            "json",
            "--output",
            "request-report/state-consistency.json",
        ],
        [
            python_executable,
            "scripts/prd_install_skills.py",
            "--repo-root",
            ".",
            "--dry-run",
        ],
        [
            python_executable,
            "scripts/request_report.py",
            "--config",
            ".prd_plugin/config.json",
            "--format",
            "markdown",
            "--output",
            "request-report/request-report.md",
        ],
        [
            python_executable,
            "scripts/request_report.py",
            "--config",
            ".prd_plugin/config.json",
            "--format",
            "json",
            "--output",
            "request-report/request-report.json",
        ],
        [
            python_executable,
            "scripts/message_check.py",
            "--config",
            ".prd_plugin/config.json",
            "--format",
            "json",
            "--output",
            "request-report/message-check.json",
        ],
    ]
    if include_system_tests:
        commands.append([python_executable, "-m", "pytest", "system_tests", "-v"])
    return commands


def _format_command(command):
    display = list(command)
    if display and Path(display[0]).name.lower().startswith("python"):
        display[0] = "python"
    return subprocess.list2cmdline(display)


def _with_no_fail_release(commands):
    report_only_commands = []
    for command in commands:
        if "scripts/release_check.py" in command and "--no-fail" not in command:
            report_only_commands.append([*command, "--no-fail"])
        else:
            report_only_commands.append(command)
    return report_only_commands


def run_commands(commands, root):
    failures = []
    for command in commands:
        print(f"$ {_format_command(command)}", flush=True)
        result = subprocess.run(command, cwd=root)
        if result.returncode:
            failures.append({"command": command, "returncode": result.returncode})
    return failures


def main(argv=None):
    parser = argparse.ArgumentParser(description="Run the local PRD Plugin workflow checks without requiring a remote.")
    parser.add_argument("--repo-root", default=str(repo_root()), help="Repository root to check.")
    parser.add_argument("--target-version", help="Version for gap audit. Defaults to the plugin manifest version.")
    parser.add_argument("--skip-tests", action="store_true", help="Skip unittest discovery.")
    parser.add_argument("--skip-structured-validation", action="store_true", help="Skip JSON and JSONL parse validation.")
    parser.add_argument("--no-fail-release", action="store_true", help="Report release hygiene findings without failing.")
    parser.add_argument("--include-system-tests", action="store_true", help="Run mock-driven system tests after unit tests.")
    args = parser.parse_args(argv)

    root = Path(args.repo_root).resolve()
    if not root.exists():
        print(f"Repository root does not exist: {root}", file=sys.stderr)
        return 1

    if not args.skip_structured_validation:
        errors = validate_structured_files(root)
        if errors:
            print("Structured file validation failed:", file=sys.stderr)
            for error in errors:
                print(f"- {error}", file=sys.stderr)
            return 1
        print("Structured file validation passed.")

    plugin_version = args.target_version or load_plugin_version(root)
    commands = build_workflow_commands(plugin_version, include_system_tests=args.include_system_tests)
    if args.skip_tests:
        commands = [command for command in commands if "-m" not in command or ("unittest" not in command and "pytest" not in command)]
    if args.no_fail_release:
        commands = _with_no_fail_release(commands)

    failures = run_commands(commands, root)
    if failures:
        print("Local workflow check failed:", file=sys.stderr)
        for failure in failures:
            print(
                f"- exit {failure['returncode']}: {_format_command(failure['command'])}",
                file=sys.stderr,
            )
        return 1

    print("Local workflow check completed.")
    return 0


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