#!/usr/bin/env python3
# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Build Familiar Installer Executables

Creates single-file executables for Windows, macOS, and Linux.

Usage:
    python build_installer.py [--platform PLATFORM]

Platforms: windows, macos, linux, all (default)

Requirements:
    pip install pyinstaller pillow

Output:
    dist/
    ├── windows/
    │   └── Familiar-Setup.exe        (~25 MB)
    ├── macos/
    │   └── Familiar-Setup.app        (~20 MB)
    └── linux/
        └── familiar-setup             (~22 MB)
"""

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

# Configuration
APP_NAME = "Familiar"
APP_VERSION = "1.10.0"
ICON_SIZES = [16, 32, 48, 64, 128, 256, 512]

# Paths
SCRIPT_DIR = Path(__file__).parent
INSTALLER_SCRIPT = SCRIPT_DIR / "wizard.py"
DIST_DIR = SCRIPT_DIR.parent.parent / "dist"
ASSETS_DIR = SCRIPT_DIR / "assets"


def create_icon_if_missing():
    """Create a simple icon if none exists."""
    ASSETS_DIR.mkdir(exist_ok=True)

    ico_path = ASSETS_DIR / "familiar.ico"
    icns_path = ASSETS_DIR / "familiar.icns"
    png_path = ASSETS_DIR / "familiar.png"

    if ico_path.exists() and icns_path.exists():
        return

    print("Creating placeholder icons...")

    try:
        from PIL import Image, ImageDraw, ImageFont

        # Create a simple icon with the familiar emoji/text
        for size in ICON_SIZES:
            img = Image.new("RGBA", (size, size), (26, 26, 46, 255))  # Dark background
            draw = ImageDraw.Draw(img)

            # Draw a circle
            margin = size // 8
            draw.ellipse(
                [margin, margin, size - margin, size - margin],
                fill=(123, 104, 238, 255),  # Purple
            )

            # Draw "E" in center
            try:
                font = ImageFont.truetype("arial.ttf", size // 2)
            except Exception:
                font = ImageFont.load_default()

            text = "E"
            bbox = draw.textbbox((0, 0), text, font=font)
            text_width = bbox[2] - bbox[0]
            text_height = bbox[3] - bbox[1]

            x = (size - text_width) // 2
            y = (size - text_height) // 2 - bbox[1]
            draw.text((x, y), text, fill=(255, 255, 255, 255), font=font)

            img.save(ASSETS_DIR / f"icon_{size}.png")

        # Create .ico (Windows)
        images = [Image.open(ASSETS_DIR / f"icon_{s}.png") for s in ICON_SIZES if s <= 256]
        images[0].save(ico_path, format="ICO", sizes=[(s, s) for s in ICON_SIZES if s <= 256])

        # Create .icns (macOS) - just use PNG, macOS can handle it
        shutil.copy(ASSETS_DIR / "icon_512.png", icns_path)

        # Keep one PNG for Linux
        shutil.copy(ASSETS_DIR / "icon_256.png", png_path)

        print("  ✓ Icons created")

    except ImportError:
        print("  ⚠ Pillow not installed, using no icon")
        # Create empty files so build doesn't fail
        ico_path.touch()
        icns_path.touch()


def build_windows():
    """Build Windows executable."""
    print("\n🪟 Building Windows installer...")

    output_dir = DIST_DIR / "windows"
    output_dir.mkdir(parents=True, exist_ok=True)

    icon_path = ASSETS_DIR / "familiar.ico"

    cmd = [
        sys.executable,
        "-m",
        "PyInstaller",
        "--onefile",
        "--windowed",
        "--name",
        f"{APP_NAME}-Setup",
        "--distpath",
        str(output_dir),
        "--workpath",
        str(DIST_DIR / "build" / "windows"),
        "--specpath",
        str(DIST_DIR / "build"),
        "--clean",
    ]

    if icon_path.exists() and icon_path.stat().st_size > 0:
        cmd.extend(["--icon", str(icon_path)])

    # Add version info for Windows
    version_file = create_windows_version_file()
    if version_file:
        cmd.extend(["--version-file", str(version_file)])

    cmd.append(str(INSTALLER_SCRIPT))

    result = subprocess.run(cmd, capture_output=True, text=True)

    if result.returncode != 0:
        print(f"  ✗ Build failed:\n{result.stderr}")
        return False

    exe_path = output_dir / f"{APP_NAME}-Setup.exe"
    if exe_path.exists():
        size_mb = exe_path.stat().st_size / (1024 * 1024)
        print(f"  ✓ Created: {exe_path} ({size_mb:.1f} MB)")
        return True
    else:
        print("  ✗ Executable not found")
        return False


def build_macos():
    """Build macOS application."""
    print("\n🍎 Building macOS installer...")

    output_dir = DIST_DIR / "macos"
    output_dir.mkdir(parents=True, exist_ok=True)

    icon_path = ASSETS_DIR / "familiar.icns"

    cmd = [
        sys.executable,
        "-m",
        "PyInstaller",
        "--onefile",
        "--windowed",
        "--name",
        f"{APP_NAME}-Setup",
        "--distpath",
        str(output_dir),
        "--workpath",
        str(DIST_DIR / "build" / "macos"),
        "--specpath",
        str(DIST_DIR / "build"),
        "--clean",
        "--osx-bundle-identifier",
        "ai.familiar.setup",
    ]

    if icon_path.exists() and icon_path.stat().st_size > 0:
        cmd.extend(["--icon", str(icon_path)])

    cmd.append(str(INSTALLER_SCRIPT))

    result = subprocess.run(cmd, capture_output=True, text=True)

    if result.returncode != 0:
        print(f"  ✗ Build failed:\n{result.stderr}")
        return False

    app_path = output_dir / f"{APP_NAME}-Setup.app"
    if app_path.exists():
        # Get size of .app bundle
        size_mb = sum(f.stat().st_size for f in app_path.rglob("*") if f.is_file()) / (1024 * 1024)
        print(f"  ✓ Created: {app_path} ({size_mb:.1f} MB)")
        return True

    # PyInstaller on macOS might create just executable
    exe_path = output_dir / f"{APP_NAME}-Setup"
    if exe_path.exists():
        size_mb = exe_path.stat().st_size / (1024 * 1024)
        print(f"  ✓ Created: {exe_path} ({size_mb:.1f} MB)")
        return True

    print("  ✗ Application not found")
    return False


def build_linux():
    """Build Linux executable."""
    print("\n🐧 Building Linux installer...")

    output_dir = DIST_DIR / "linux"
    output_dir.mkdir(parents=True, exist_ok=True)

    cmd = [
        sys.executable,
        "-m",
        "PyInstaller",
        "--onefile",
        "--name",
        "familiar-setup",
        "--distpath",
        str(output_dir),
        "--workpath",
        str(DIST_DIR / "build" / "linux"),
        "--specpath",
        str(DIST_DIR / "build"),
        "--clean",
    ]

    cmd.append(str(INSTALLER_SCRIPT))

    result = subprocess.run(cmd, capture_output=True, text=True)

    if result.returncode != 0:
        print(f"  ✗ Build failed:\n{result.stderr}")
        return False

    exe_path = output_dir / "familiar-setup"
    if exe_path.exists():
        size_mb = exe_path.stat().st_size / (1024 * 1024)
        print(f"  ✓ Created: {exe_path} ({size_mb:.1f} MB)")

        # Make executable
        os.chmod(exe_path, 0o755)

        return True
    else:
        print("  ✗ Executable not found")
        return False


def create_windows_version_file():
    """Create Windows version info file."""
    version_parts = APP_VERSION.split(".")
    while len(version_parts) < 4:
        version_parts.append("0")

    version_tuple = ", ".join(version_parts[:4])

    content = f"""# UTF-8
VSVersionInfo(
  ffi=FixedFileInfo(
    filevers=({version_tuple}),
    prodvers=({version_tuple}),
    mask=0x3f,
    flags=0x0,
    OS=0x40004,
    fileType=0x1,
    subtype=0x0,
    date=(0, 0)
  ),
  kids=[
    StringFileInfo(
      [
        StringTable(
          u'040904B0',
          [
            StringStruct(u'CompanyName', u'Familiar Contributors'),
            StringStruct(u'FileDescription', u'{APP_NAME} Setup Wizard'),
            StringStruct(u'FileVersion', u'{APP_VERSION}'),
            StringStruct(u'InternalName', u'familiar-setup'),
            StringStruct(u'LegalCopyright', u'MIT License'),
            StringStruct(u'OriginalFilename', u'{APP_NAME}-Setup.exe'),
            StringStruct(u'ProductName', u'{APP_NAME}'),
            StringStruct(u'ProductVersion', u'{APP_VERSION}')
          ]
        )
      ]
    ),
    VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
  ]
)
"""

    version_file = DIST_DIR / "build" / "version_info.txt"
    version_file.parent.mkdir(parents=True, exist_ok=True)
    version_file.write_text(content)

    return version_file


def create_checksums():
    """Create SHA256 checksums for all builds."""
    print("\n📋 Creating checksums...")

    import hashlib

    checksums = []

    for platform_dir in ["windows", "macos", "linux"]:
        dir_path = DIST_DIR / platform_dir
        if not dir_path.exists():
            continue

        for file_path in dir_path.iterdir():
            if file_path.is_file() and not file_path.name.startswith("."):
                sha256 = hashlib.sha256()
                with open(file_path, "rb") as f:
                    for chunk in iter(lambda: f.read(8192), b""):
                        sha256.update(chunk)

                checksums.append(f"{sha256.hexdigest()}  {platform_dir}/{file_path.name}")

    checksum_file = DIST_DIR / "SHA256SUMS.txt"
    checksum_file.write_text("\n".join(checksums) + "\n")

    print(f"  ✓ Created: {checksum_file}")


def main():
    parser = argparse.ArgumentParser(description="Build Familiar installer executables")
    parser.add_argument(
        "--platform",
        choices=["windows", "macos", "linux", "all"],
        default="all",
        help="Platform to build for",
    )
    args = parser.parse_args()

    print(f"🦔 Familiar Installer Builder v{APP_VERSION}")
    print("=" * 50)

    # Check PyInstaller
    try:
        import PyInstaller

        print(f"PyInstaller version: {PyInstaller.__version__}")
    except ImportError:
        print("❌ PyInstaller not found. Install with: pip install pyinstaller")
        sys.exit(1)

    # Create icons
    create_icon_if_missing()

    # Build for selected platforms
    current_platform = platform.system().lower()
    if current_platform == "darwin":
        current_platform = "macos"

    results = {}

    if args.platform == "all":
        # Only build for current platform (cross-compilation not supported)
        print(f"\nBuilding for current platform: {current_platform}")

        if current_platform == "windows":
            results["windows"] = build_windows()
        elif current_platform == "macos":
            results["macos"] = build_macos()
        else:
            results["linux"] = build_linux()
    else:
        if args.platform != current_platform:
            print("\n⚠ Cross-compilation not supported.")
            print(f"  Current platform: {current_platform}")
            print(f"  Requested platform: {args.platform}")
            print("  Use GitHub Actions for multi-platform builds.")
            sys.exit(1)

        if args.platform == "windows":
            results["windows"] = build_windows()
        elif args.platform == "macos":
            results["macos"] = build_macos()
        else:
            results["linux"] = build_linux()

    # Create checksums
    create_checksums()

    # Summary
    print("\n" + "=" * 50)
    print("Build Summary:")
    print("=" * 50)

    for plat, success in results.items():
        status = "✓ SUCCESS" if success else "✗ FAILED"
        print(f"  {plat}: {status}")

    print(f"\nOutput directory: {DIST_DIR}")

    if all(results.values()):
        print("\n✅ All builds completed successfully!")
        return 0
    else:
        print("\n⚠ Some builds failed")
        return 1


if __name__ == "__main__":
    sys.exit(main())
