#!/usr/bin/env python3
"""
OpenAkita Python Backend Build Script

Usage:
  python build/build_backend.py --mode core    # Core package (~100-150MB)
  python build/build_backend.py --mode full    # Full package (~600-800MB)
"""

import argparse
import os
import shutil
import subprocess
import sys
from pathlib import Path

PROJECT_ROOT = Path(__file__).resolve().parent.parent
SPEC_FILE = PROJECT_ROOT / "build" / "openakita.spec"
DIST_DIR = PROJECT_ROOT / "dist"
OUTPUT_DIR = DIST_DIR / "openakita-server"


def run_cmd(cmd: list[str], env: dict | None = None, **kwargs) -> subprocess.CompletedProcess:
    """Run command and print output"""
    print(f"  $ {' '.join(cmd)}")
    merged_env = {**os.environ, **(env or {})}
    result = subprocess.run(cmd, env=merged_env, **kwargs)
    if result.returncode != 0:
        print(f"  [ERROR] Command failed (exit {result.returncode})")
        sys.exit(1)
    return result


def check_pyinstaller():
    """Check if PyInstaller is installed"""
    try:
        import PyInstaller  # noqa: F401
        print(f"  [OK] PyInstaller {PyInstaller.__version__} installed")
    except ImportError:
        print("  [WARN] PyInstaller not installed, installing...")
        run_cmd([sys.executable, "-m", "pip", "install", "pyinstaller"])


def clean_dist():
    """Clean previous build output"""
    # Clean dist output directory
    if OUTPUT_DIR.exists():
        print(f"  Cleaning old build output: {OUTPUT_DIR}")
        shutil.rmtree(OUTPUT_DIR)

    # Clean entire dist directory to avoid symlink conflicts on macOS
    if DIST_DIR.exists():
        print(f"  Cleaning dist directory: {DIST_DIR}")
        shutil.rmtree(DIST_DIR)

    # Clean build temp directory
    build_tmp = PROJECT_ROOT / "build" / "openakita-server"
    if build_tmp.exists():
        shutil.rmtree(build_tmp)

    # Clean PyInstaller work directory (fixes macOS symlink FileExistsError)
    pyinstaller_work = PROJECT_ROOT / "build" / "pyinstaller_work"
    if pyinstaller_work.exists():
        print(f"  Cleaning PyInstaller work directory: {pyinstaller_work}")
        shutil.rmtree(pyinstaller_work)


def ensure_playwright_chromium():
    """Ensure Playwright Chromium is installed for bundling"""
    try:
        import playwright
        pw_dir = Path(playwright.__file__).parent
        local_browsers = pw_dir / ".local-browsers"
        if local_browsers.exists():
            print("  [OK] Playwright Chromium already installed (local-browsers)")
            return

        # Check default system path
        if sys.platform == "win32":
            pw_cache = Path.home() / "AppData" / "Local" / "ms-playwright"
        elif sys.platform == "darwin":
            pw_cache = Path.home() / "Library" / "Caches" / "ms-playwright"
        else:
            pw_cache = Path.home() / ".cache" / "ms-playwright"

        chromium_found = False
        if pw_cache.exists():
            for d in pw_cache.iterdir():
                if d.is_dir() and "chromium" in d.name.lower():
                    chromium_found = True
                    break

        if chromium_found:
            print(f"  [OK] Playwright Chromium found at {pw_cache}")
        else:
            print("  [INFO] Installing Playwright Chromium for bundling...")
            run_cmd([sys.executable, "-m", "playwright", "install", "chromium"])
    except ImportError:
        print("  [WARN] playwright not installed, installing...")
        run_cmd([sys.executable, "-m", "pip", "install", "playwright", "browser-use", "langchain-openai"])
        print("  [INFO] Installing Playwright Chromium...")
        run_cmd([sys.executable, "-m", "playwright", "install", "chromium"])


def build_backend(mode: str):
    """Execute PyInstaller packaging"""
    print(f"\n{'='*60}")
    print(f"  OpenAkita Backend Build - Mode: {mode.upper()}")
    print(f"{'='*60}\n")

    print("[1/5] Checking dependencies...")
    check_pyinstaller()

    print("\n[2/5] Ensuring Playwright Chromium for bundling...")
    ensure_playwright_chromium()

    print("\n[3/5] Cleaning old build...")
    clean_dist()

    print("\n[4/5] Running PyInstaller...")
    env = {"OPENAKITA_BUILD_MODE": mode}
    
    run_cmd(
        [
            sys.executable, "-m", "PyInstaller",
            str(SPEC_FILE),
            "--distpath", str(DIST_DIR),
            "--workpath", str(PROJECT_ROOT / "build" / "pyinstaller_work"),
            "--noconfirm",
            "--clean",  # Force clean build to avoid symlink conflicts on macOS
        ],
        env=env,
    )

    print("\n[5/5] Verifying build output...")
    
    # On macOS with onefile mode, PyInstaller outputs directly to dist/openakita-server (file)
    # We need to move it to dist/openakita-server/openakita-server (expected directory structure)
    if sys.platform == "darwin":
        onefile_exe = DIST_DIR / "openakita-server"
        if onefile_exe.is_file():
            print(f"  [macOS] Moving onefile executable to directory structure...")
            # First, rename the file to a temp name
            temp_exe = DIST_DIR / "openakita-server.tmp"
            shutil.move(str(onefile_exe), str(temp_exe))
            # Now create the expected directory
            OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
            # Move the executable into the directory with original name
            target_exe = OUTPUT_DIR / "openakita-server"
            shutil.move(str(temp_exe), str(target_exe))
            print(f"  [macOS] Moved to: {target_exe}")
    
    if sys.platform == "win32":
        exe_path = OUTPUT_DIR / "openakita-server.exe"
    else:
        exe_path = OUTPUT_DIR / "openakita-server"

    if not exe_path.exists():
        print(f"  [ERROR] Executable not found: {exe_path}")
        sys.exit(1)

    # Test executable
    try:
        result = subprocess.run(
            [str(exe_path), "--help"],
            capture_output=True,
            text=True,
            timeout=30,
        )
        if result.returncode == 0:
            print(f"  [OK] Executable verified: {exe_path}")
        else:
            print(f"  [WARN] Executable returned non-zero exit code: {result.returncode}")
            print(f"    stderr: {result.stderr[:500]}")
    except subprocess.TimeoutExpired:
        print("  [WARN] Executable timed out (may be normal, continuing)")
    except Exception as e:
        print(f"  [WARN] Exception during verification: {e}")

    # Calculate size
    total_size = sum(f.stat().st_size for f in OUTPUT_DIR.rglob("*") if f.is_file())
    size_mb = total_size / (1024 * 1024)
    print(f"\n  Build completed!")
    print(f"  Output directory: {OUTPUT_DIR}")
    print(f"  Total size: {size_mb:.1f} MB")
    print(f"  Mode: {mode.upper()}")


def main():
    parser = argparse.ArgumentParser(description="OpenAkita backend build script")
    parser.add_argument(
        "--mode",
        choices=["core", "full"],
        default="core",
        help="Build mode: core=minimal(exclude heavy deps), full=complete(all deps)",
    )
    args = parser.parse_args()
    build_backend(args.mode)


if __name__ == "__main__":
    main()
