#!/usr/bin/env python3
"""
Build script for creating cross-platform executables of MbetterClient using PyInstaller
"""

import os
import sys
import shutil
import platform
import subprocess
from pathlib import Path
from typing import List, Dict, Any

# Build configuration
BUILD_CONFIG = {
    'app_name': 'MbetterClient',
    'app_version': '1.2.11',
    'description': 'Cross-platform multimedia client application',
    'author': 'MBetter Team',
    'entry_point': 'main.py',
    'icon_file': 'assets/icon.ico' if platform.system() == 'Windows' else 'assets/icon.png',
    'console': False,  # Set to True for debugging
    'one_file': True,  # Create single executable
    'clean_build': True,
    'upx': False,  # UPX compression (set to True if UPX is available)
}

# Platform-specific configurations
PLATFORM_CONFIG = {
    'Windows': {
        'executable_name': BUILD_CONFIG['app_name'] + '.exe',
        'icon_ext': '.ico',
        'additional_paths': [],
        'exclude_modules': ['tkinter', 'unittest', 'doctest', 'pdb', 'PyQt5', 'PyQt5.QtCore', 'PyQt5.QtGui', 'PyQt5.QtWidgets', 'PyQt5.QtMultimedia', 'PyQt5.QtMultimediaWidgets'],
    },
    'Darwin': {  # macOS
        'executable_name': BUILD_CONFIG['app_name'],
        'icon_ext': '.icns',
        'additional_paths': [],
        'exclude_modules': ['tkinter', 'unittest', 'doctest', 'pdb', 'PyQt5', 'PyQt5.QtCore', 'PyQt5.QtGui', 'PyQt5.QtWidgets', 'PyQt5.QtMultimedia', 'PyQt5.QtMultimediaWidgets'],
    },
    'Linux': {
        'executable_name': BUILD_CONFIG['app_name'],
        'icon_ext': '.png',
        'additional_paths': [],
        'exclude_modules': ['tkinter', 'unittest', 'doctest', 'pdb', 'PyQt5', 'PyQt5.QtCore', 'PyQt5.QtGui', 'PyQt5.QtWidgets', 'PyQt5.QtMultimedia', 'PyQt5.QtMultimediaWidgets'],
    }
}


def get_project_root() -> Path:
    """Get the project root directory"""
    return Path(__file__).parent


def get_build_dir() -> Path:
    """Get the build directory"""
    return get_project_root() / 'build'


def get_dist_dir() -> Path:
    """Get the distribution directory"""
    return get_project_root() / 'dist'


def clean_build_directories():
    """Clean previous build artifacts"""
    print("🧹 Cleaning build directories...")
    
    dirs_to_clean = [get_build_dir(), get_dist_dir()]
    
    for dir_path in dirs_to_clean:
        if dir_path.exists():
            shutil.rmtree(dir_path)
            print(f"   Removed: {dir_path}")
    
    # Remove spec file if it exists
    spec_file = get_project_root() / f"{BUILD_CONFIG['app_name']}.spec"
    if spec_file.exists():
        spec_file.unlink()
        print(f"   Removed: {spec_file}")


def collect_data_files() -> List[tuple]:
    """Collect data files that need to be included in the build"""
    project_root = get_project_root()
    data_files = []
    
    # Include assets directory
    assets_dir = project_root / 'assets'
    if assets_dir.exists():
        for file_path in assets_dir.rglob('*'):
            if file_path.is_file():
                relative_path = file_path.relative_to(project_root)
                data_files.append((str(file_path), str(relative_path.parent)))
    
    # Include web dashboard templates and static files
    web_templates = project_root / 'mbetterclient' / 'web_dashboard' / 'templates'
    if web_templates.exists():
        for file_path in web_templates.rglob('*'):
            if file_path.is_file():
                relative_path = file_path.relative_to(project_root)
                data_files.append((str(file_path), str(relative_path.parent)))
    
    web_static = project_root / 'mbetterclient' / 'web_dashboard' / 'static'
    if web_static.exists():
        for file_path in web_static.rglob('*'):
            if file_path.is_file():
                relative_path = file_path.relative_to(project_root)
                data_files.append((str(file_path), str(relative_path.parent)))
    
    # Include Qt player overlay HTML and resources
    qt_player_dir = project_root / 'mbetterclient' / 'qt_player'
    if qt_player_dir.exists():
        for file_path in qt_player_dir.rglob('*.html'):
            if file_path.is_file():
                relative_path = file_path.relative_to(project_root)
                data_files.append((str(file_path), str(relative_path.parent)))
        
        # Include any CSS, JS, or other web resources
        for ext in ['*.css', '*.js', '*.json']:
            for file_path in qt_player_dir.rglob(ext):
                if file_path.is_file():
                    relative_path = file_path.relative_to(project_root)
                    data_files.append((str(file_path), str(relative_path.parent)))
    
    # Include Qt player templates directory and all assets
    templates_dir = project_root / 'mbetterclient' / 'qt_player' / 'templates'
    if templates_dir.exists():
        for file_path in templates_dir.rglob('*'):
            if file_path.is_file():
                relative_path = file_path.relative_to(project_root)
                data_files.append((str(file_path), str(relative_path.parent)))
        print(f"   📁 Including templates directory: {templates_dir}")
        
        # Specifically ensure JavaScript files in templates are included
        js_files = list(templates_dir.rglob('*.js'))
        css_files = list(templates_dir.rglob('*.css'))
        print(f"   📄 Found {len(js_files)} JavaScript files in templates")
        print(f"   🎨 Found {len(css_files)} CSS files in templates")
    
    # Include any external JavaScript/CSS assets that templates might reference
    # Look for common asset directories that templates might use
    asset_patterns = ['assets', 'js', 'css', 'fonts', 'images']
    for pattern in asset_patterns:
        asset_dir = project_root / 'mbetterclient' / 'qt_player' / pattern
        if asset_dir.exists():
            for file_path in asset_dir.rglob('*'):
                if file_path.is_file():
                    relative_path = file_path.relative_to(project_root)
                    data_files.append((str(file_path), str(relative_path.parent)))
            print(f"   📁 Including asset directory: {asset_dir}")
    
    return data_files


def collect_hidden_imports() -> List[str]:
    """Collect hidden imports that PyInstaller might miss"""
    return [
        # PyQt6 modules
        'PyQt6.QtCore',
        'PyQt6.QtGui',
        'PyQt6.QtWidgets',
        'PyQt6.QtMultimedia',
        'PyQt6.QtMultimediaWidgets',
        'PyQt6.QtWebEngineWidgets',
        'PyQt6.QtWebChannel',
        
        # Flask and web dependencies
        'flask',
        'flask_login',
        'flask_jwt_extended',
        'werkzeug.security',
        'jinja2',
        
        # SQLAlchemy
        'sqlalchemy',
        'sqlalchemy.sql.default_comparator',
        
        # Requests and HTTP
        'requests',
        'urllib3',
        
        # Logging
        'loguru',
        
        # File watching for template system
        'watchdog',
        'watchdog.observers',
        'watchdog.events',
        
        # Other dependencies
        'packaging',
        'pkg_resources',
    ]


def get_platform_config() -> Dict[str, Any]:
    """Get platform-specific configuration"""
    system = platform.system()
    return PLATFORM_CONFIG.get(system, PLATFORM_CONFIG['Linux'])


def create_icon_file():
    """Create or copy icon file for the platform"""
    project_root = get_project_root()
    platform_config = get_platform_config()
    
    icon_source = project_root / 'assets' / 'icon.png'  # Default source
    icon_target = project_root / 'assets' / f"icon{platform_config['icon_ext']}"
    
    # Create assets directory if it doesn't exist
    (project_root / 'assets').mkdir(exist_ok=True)
    
    # Create a simple icon if none exists
    if not icon_source.exists() and not icon_target.exists():
        print("📦 Creating default icon...")
        try:
            # Try to create a simple icon using PIL if available
            from PIL import Image, ImageDraw
            
            # Create a simple 256x256 icon
            img = Image.new('RGBA', (256, 256), (70, 130, 180, 255))  # Steel blue
            draw = ImageDraw.Draw(img)
            
            # Draw a simple play button
            points = [(80, 60), (180, 128), (80, 196)]
            draw.polygon(points, fill=(255, 255, 255, 255))
            
            # Save as PNG
            img.save(icon_source)
            print(f"   Created: {icon_source}")
            
        except ImportError:
            # Create a simple text-based icon file (fallback)
            with open(icon_source, 'wb') as f:
                # This is just a placeholder - in a real scenario you'd have a proper icon
                f.write(b'')
            print(f"   Created placeholder: {icon_source}")
    
    # Copy to target format if needed
    if icon_source.exists() and not icon_target.exists() and icon_source != icon_target:
        shutil.copy2(icon_source, icon_target)
    
    return icon_target if icon_target.exists() else None


def generate_spec_file():
    """Generate PyInstaller spec file"""
    project_root = get_project_root()
    platform_config = get_platform_config()
    
    # Collect files and imports
    data_files = collect_data_files()
    hidden_imports = collect_hidden_imports()
    icon_file = create_icon_file()
    
    # Build pathex (additional paths for imports)
    pathex = [str(project_root)] + platform_config.get('additional_paths', [])
    
    spec_content = f'''# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(
    ['{BUILD_CONFIG['entry_point']}'],
    pathex={pathex!r},
    binaries=[],
    datas={data_files!r},
    hiddenimports={hidden_imports!r},
    hookspath=[],
    hooksconfig={{}},
    runtime_hooks=[],
    excludes={platform_config.get('exclude_modules', [])!r},
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='{platform_config['executable_name']}',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx={BUILD_CONFIG['upx']},
    upx_exclude=[],
    runtime_tmpdir=None,
    console={BUILD_CONFIG['console']},
    disable_windowed_traceback=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    version='{BUILD_CONFIG['app_version']}',
    description='{BUILD_CONFIG['description']}',
    icon={repr(str(icon_file)) if icon_file else None},
)
'''
    
    # Add macOS app bundle configuration if on macOS
    if platform.system() == 'Darwin':
        spec_content += f'''
app = BUNDLE(
    exe,
    name='{BUILD_CONFIG['app_name']}.app',
    icon={repr(str(icon_file)) if icon_file else None},
    bundle_identifier='net.nexlab.mbetterclient',
    version='{BUILD_CONFIG['app_version']}',
    info_plist={{
        'CFBundleShortVersionString': '{BUILD_CONFIG['app_version']}',
        'CFBundleVersion': '{BUILD_CONFIG['app_version']}',
        'NSHighResolutionCapable': 'True',
        'NSRequiresAquaSystemAppearance': 'False',
    }},
)
'''
    
    spec_file_path = project_root / f"{BUILD_CONFIG['app_name']}.spec"
    with open(spec_file_path, 'w', encoding='utf-8') as f:
        f.write(spec_content)
    
    print(f"📝 Generated spec file: {spec_file_path}")
    return spec_file_path


def check_dependencies():
    """Check if all required dependencies are available"""
    print("🔍 Checking dependencies...")
    
    # Map package names to their import names
    required_packages = {
        'PyInstaller': 'PyInstaller',
        'PyQt6': 'PyQt6'
    }
    missing_packages = []
    
    for package_name, import_name in required_packages.items():
        try:
            __import__(import_name)
            print(f"   ✓ {package_name}")
        except ImportError:
            missing_packages.append(package_name)
            print(f"   ✗ {package_name}")
    
    if missing_packages:
        print(f"\n❌ Missing dependencies: {', '.join(missing_packages)}")
        print("Please install them using:")
        print(f"   pip install {' '.join(missing_packages)}")
        return False
    
    return True


def run_pyinstaller(spec_file_path: Path):
    """Run PyInstaller with the generated spec file"""
    print("🔨 Running PyInstaller...")
    
    cmd = [
        sys.executable, '-m', 'PyInstaller',
        '--clean' if BUILD_CONFIG['clean_build'] else '--noconfirm',
        str(spec_file_path)
    ]
    
    print(f"Command: {' '.join(cmd)}")
    
    try:
        result = subprocess.run(cmd, check=True, capture_output=False)
        print("✅ Build completed successfully!")
        return True
    except subprocess.CalledProcessError as e:
        print(f"❌ Build failed with exit code {e.returncode}")
        return False


def post_build_tasks():
    """Perform post-build tasks"""
    print("📦 Performing post-build tasks...")
    
    dist_dir = get_dist_dir()
    platform_config = get_platform_config()
    
    if not dist_dir.exists():
        print("   No dist directory found")
        return
    
    # Find the created executable
    executable_path = None
    for item in dist_dir.iterdir():
        if item.is_file() and item.name == platform_config['executable_name']:
            executable_path = item
            break
        elif item.is_dir() and item.name.endswith('.app'):  # macOS app bundle
            executable_path = item
            break
    
    if executable_path:
        size_mb = get_file_size_mb(executable_path)
        print(f"   📱 Created: {executable_path} ({size_mb:.1f} MB)")
        
        # Make executable on Unix systems
        if platform.system() in ['Linux', 'Darwin'] and executable_path.is_file():
            os.chmod(executable_path, 0o755)
            print("   🔧 Made executable")
    
    # Create distribution package
    create_distribution_package(dist_dir)


def get_file_size_mb(path: Path) -> float:
    """Get file or directory size in MB"""
    if path.is_file():
        return path.stat().st_size / (1024 * 1024)
    elif path.is_dir():
        total_size = sum(f.stat().st_size for f in path.rglob('*') if f.is_file())
        return total_size / (1024 * 1024)
    return 0.0


def create_distribution_package(dist_dir: Path):
    """Create a distribution package"""
    print("📦 Creating distribution package...")
    
    project_root = get_project_root()
    platform_name = platform.system().lower()
    arch = platform.machine().lower()
    
    package_name = f"{BUILD_CONFIG['app_name']}-{BUILD_CONFIG['app_version']}-{platform_name}-{arch}"
    package_dir = project_root / 'packages'
    package_dir.mkdir(exist_ok=True)
    
    # Copy executable and create package
    if platform.system() == 'Windows':
        # Create ZIP package for Windows
        package_file = package_dir / f"{package_name}.zip"
        shutil.make_archive(str(package_file.with_suffix('')), 'zip', str(dist_dir))
        print(f"   📦 Created: {package_file}")
    else:
        # Create tar.gz package for Unix systems
        package_file = package_dir / f"{package_name}.tar.gz"
        shutil.make_archive(str(package_file.with_suffix('').with_suffix('')), 'gztar', str(dist_dir))
        print(f"   📦 Created: {package_file}")
    
    # Create README for distribution
    readme_content = f"""# {BUILD_CONFIG['app_name']} v{BUILD_CONFIG['app_version']}

{BUILD_CONFIG['description']}

## Installation

1. Extract this package to your desired location
2. Run the executable file
3. The application will create necessary configuration files on first run

## System Requirements

- **Operating System**: {platform.system()} {platform.release()}
- **Architecture**: {platform.machine()}
- **Memory**: 512 MB RAM minimum, 1 GB recommended
- **Disk Space**: 100 MB free space

## Configuration

The application stores its configuration and database in:
- **Windows**: `%APPDATA%\\{BUILD_CONFIG['app_name']}`
- **macOS**: `~/Library/Application Support/{BUILD_CONFIG['app_name']}`
- **Linux**: `~/.config/{BUILD_CONFIG['app_name']}`

## Web Interface

By default, the web interface is available at: http://localhost:5001

Default login credentials:
- Username: admin
- Password: admin

**Please change the default password after first login.**

## Support

For support and documentation, please visit: https://git.nexlab.net/mbetter/mbetterc

## Version Information

- Version: {BUILD_CONFIG['app_version']}
- Build Date: {platform.node()}
- Platform: {platform.platform()}
"""
    
    readme_file = package_dir / f"{package_name}-README.txt"
    with open(readme_file, 'w', encoding='utf-8') as f:
        f.write(readme_content)
    
    print(f"   📄 Created: {readme_file}")


def main():
    """Main build process"""
    print(f"🚀 Building {BUILD_CONFIG['app_name']} v{BUILD_CONFIG['app_version']}")
    print(f"Platform: {platform.system()} {platform.release()} ({platform.machine()})")
    print()
    
    # Check dependencies
    if not check_dependencies():
        sys.exit(1)
    
    # Clean build directories
    if BUILD_CONFIG['clean_build']:
        clean_build_directories()
    
    # Generate spec file
    spec_file_path = generate_spec_file()
    
    # Run PyInstaller
    if not run_pyinstaller(spec_file_path):
        sys.exit(1)
    
    # Post-build tasks
    post_build_tasks()
    
    print()
    print("🎉 Build process completed successfully!")
    print(f"📁 Distribution files are in: {get_dist_dir()}")
    print(f"📦 Packages are in: {get_project_root() / 'packages'}")


if __name__ == '__main__':
    main()