#!/usr/bin/env python3
"""
Build script to compile paid stablestack checkers to .so/.pyd bundles.

Reads the paid checker manifest (tier_manifest.py), Cython-compiles each
source file, and packages the results into a platform-specific .tar.gz
bundle with a manifest.json that the runtime loader expects.

Usage:
    pip install cython setuptools
    python build_compiled.py                         # auto-detect platform
    python build_compiled.py --platform linux --arch x86_64
"""

import argparse
import json
import os
import platform as plat
import shutil
import subprocess
import sys
import tarfile
from pathlib import Path


# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

CHECKERS_SRC = Path("src/stablestack/checkers")


def _get_version() -> str:
    """Read the stablestack version from __init__.py."""
    init = Path("src/stablestack/__init__.py")
    for line in init.read_text().splitlines():
        if line.startswith("__version__"):
            return line.split("=")[1].strip().strip('"').strip("'")
    return "0.0.0"


def _load_manifest() -> list[tuple[str, str, str]]:
    """Import PAID_CHECKERS from the tier manifest."""
    # Add src/ to path so we can import the manifest
    sys.path.insert(0, str(Path("src").resolve()))
    from stablestack.checkers.tier_manifest import PAID_CHECKERS
    return PAID_CHECKERS


def _source_path(module_path: str) -> Path:
    """Convert a manifest module_path to the actual .py file path."""
    return CHECKERS_SRC / f"{module_path}.py"


# ---------------------------------------------------------------------------
# Build steps
# ---------------------------------------------------------------------------

def compile_checkers(paid_checkers: list, build_dir: Path) -> list[Path]:
    """Cython-compile each paid checker .py into a standalone .so/.pyd."""
    import tempfile

    compiled: list[Path] = []
    build_dir.mkdir(parents=True, exist_ok=True)
    stablestack_root = Path.cwd().resolve()

    for rule_id, module_path, class_name in paid_checkers:
        src = _source_path(module_path).resolve()
        if not src.exists():
            print(f"  SKIP {rule_id}: {src} not found")
            continue

        # Cython compile to .so/.pyd in the build dir
        module_name = module_path.replace("/", "_")
        print(f"  Compiling {rule_id} ({src.name})...")

        # Use a temp directory as cwd to avoid picking up pyproject.toml
        with tempfile.TemporaryDirectory() as tmpdir:
            abs_build_dir = build_dir.resolve()
            # Use forward slashes to avoid Windows backslash escape issues
            # in the inline Python source string
            src_posix = src.as_posix()
            build_posix = abs_build_dir.as_posix()
            result = subprocess.run(
                [
                    sys.executable, "-c",
                    f"""
import sys, os
from Cython.Build import cythonize
from setuptools import setup, Extension

ext = Extension(
    "{module_name}",
    sources=["{src_posix}"],
)
setup(
    name="{module_name}",
    ext_modules=cythonize([ext], compiler_directives={{"language_level": "3"}}),
    script_args=["build_ext", "--inplace", "--build-lib", "{build_posix}"],
)
""",
                ],
                capture_output=True,
                text=True,
                cwd=tmpdir,
            )

            if result.returncode != 0:
                print(f"  FAIL {rule_id}: {result.stderr[-200:]}")
                continue

            # Find the compiled output (may be in tmpdir or build_dir)
            tmp_path = Path(tmpdir)
            found = list(tmp_path.glob(f"{module_name}*.so")) + \
                    list(tmp_path.glob(f"{module_name}*.pyd")) + \
                    list(abs_build_dir.glob(f"{module_name}*.so")) + \
                    list(abs_build_dir.glob(f"{module_name}*.pyd"))

            if found:
                so_file = found[0]
                # Normalize name: strip cpython tag, keep just module_name + extension
                ext = ".pyd" if so_file.suffix == ".pyd" else ".so"
                dest = abs_build_dir / f"{module_name}{ext}"
                if so_file != dest:
                    shutil.move(str(so_file), str(dest))
                compiled.append(dest)
                print(f"  OK   {rule_id} -> {dest.name}")
            else:
                print(f"  FAIL {rule_id}: compiled output not found")

    return compiled


def create_manifest_json(
    paid_checkers: list,
    compiled: list[Path],
    version: str,
    build_dir: Path,
) -> Path:
    """Write manifest.json that the runtime loader reads."""
    compiled_names = {p.stem for p in compiled}

    entries = []
    for rule_id, module_path, class_name in paid_checkers:
        module_name = module_path.replace("/", "_")
        if module_name in compiled_names:
            entries.append({
                "rule_id": rule_id,
                "module": module_name,
                "class_name": class_name,
            })

    manifest = {
        "stablestack_version": version,
        "checkers": entries,
    }

    manifest_path = build_dir / "manifest.json"
    manifest_path.write_text(json.dumps(manifest, indent=2))
    return manifest_path


def _get_python_tag() -> str:
    """Return the CPython ABI tag, e.g. 'cp311', 'cp312'."""
    vi = sys.version_info
    return f"cp{vi.major}{vi.minor}"


def package_bundle(
    build_dir: Path,
    version: str,
    platform_name: str,
    arch: str,
    output_dir: Path,
    python_tag: str = "",
) -> Path:
    """Create a .tar.gz bundle ready for upload."""
    output_dir.mkdir(parents=True, exist_ok=True)
    if not python_tag:
        python_tag = _get_python_tag()
    bundle_name = (
        f"stablestack-checkers-{version}-{python_tag}"
        f"-{platform_name}-{arch}.tar.gz"
    )
    bundle_path = output_dir / bundle_name

    with tarfile.open(str(bundle_path), "w:gz") as tar:
        for f in build_dir.iterdir():
            tar.add(str(f), arcname=f.name)

    return bundle_path


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

def main():
    parser = argparse.ArgumentParser(
        description="Build compiled paid checker bundles"
    )
    parser.add_argument(
        "--platform",
        default=sys.platform,
        help="Target platform (e.g. linux, darwin, win32)",
    )
    parser.add_argument(
        "--arch",
        default=plat.machine(),
        help="Target architecture (e.g. x86_64, arm64, amd64)",
    )
    parser.add_argument(
        "--python-tag",
        default="",
        help="Python ABI tag (e.g. cp311, cp312). Auto-detected if omitted.",
    )
    parser.add_argument(
        "--output-dir",
        default="dist_bundles",
        help="Where to write the .tar.gz bundle",
    )
    args = parser.parse_args()

    os.chdir(Path(__file__).parent)

    print("=" * 60)
    print("STABLESTACK PAID CHECKER BUILD")
    print("=" * 60)

    # Check for Cython
    try:
        import Cython
        print(f"Cython {Cython.__version__}")
    except ImportError:
        print("ERROR: Cython not found. Install with: pip install cython")
        sys.exit(1)

    version = _get_version()
    python_tag = args.python_tag or _get_python_tag()
    print(f"Version: {version}")
    print(f"Platform: {args.platform}, Arch: {args.arch}, Python: {python_tag}")

    # Load manifest
    paid_checkers = _load_manifest()
    print(f"\nPaid checkers in manifest: {len(paid_checkers)}")

    # Compile
    build_dir = Path("build/paid_checkers")
    if build_dir.exists():
        shutil.rmtree(build_dir)
    build_dir.mkdir(parents=True)

    print("\nCompiling...")
    compiled = compile_checkers(paid_checkers, build_dir)
    print(f"\nCompiled {len(compiled)} of {len(paid_checkers)} checkers")

    if not compiled:
        print("ERROR: No checkers compiled successfully")
        sys.exit(1)

    # Create manifest.json
    manifest_path = create_manifest_json(
        paid_checkers, compiled, version, build_dir
    )
    print(f"Manifest: {manifest_path}")

    # Package
    bundle = package_bundle(
        build_dir, version, args.platform, args.arch, Path(args.output_dir),
        python_tag=python_tag,
    )
    print(f"\nBundle: {bundle} ({bundle.stat().st_size / 1024:.0f} KB)")

    print("\n" + "=" * 60)
    print("BUILD COMPLETE")
    print("=" * 60)
    print(f"""
Upload this bundle to S3:
  aws s3 cp {bundle} s3://stablestack-bundles/{version}/

The license API will generate pre-signed URLs pointing to this path.
""")


if __name__ == "__main__":
    main()
