#!/usr/bin/env python3
"""
CARL Control Panel Desktop Launcher
- Splash screen during startup
- System tray icon with menu
- Flask runs in-process (thread)
- Cross-platform (Windows/Mac/Linux)
- PyInstaller-friendly (single exe)

Double-click to run, or: python launcher.pyw
"""
import sys
import os
import time
import threading

# CRITICAL: Prevent infinite spawn when bundled with PyInstaller
if getattr(sys, 'frozen', False):
    from multiprocessing import freeze_support
    freeze_support()

    # Prevent re-entry - check if we're already running
    import tempfile
    lock_file = os.path.join(tempfile.gettempdir(), 'carl_control_panel.lock')
    if os.path.exists(lock_file):
        try:
            if time.time() - os.path.getmtime(lock_file) < 60:
                sys.exit(0)  # Already running, exit silently
        except:
            pass
    try:
        with open(lock_file, 'w') as f:
            f.write(str(os.getpid()))
    except:
        pass

# Change to script directory (or exe directory if frozen)
if getattr(sys, 'frozen', False):
    SCRIPT_DIR = os.path.dirname(sys.executable)
else:
    SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
os.chdir(SCRIPT_DIR)

# Add script dir to path for imports
if SCRIPT_DIR not in sys.path:
    sys.path.insert(0, SCRIPT_DIR)

# Platform detection
IS_WINDOWS = sys.platform == 'win32'
IS_MAC = sys.platform == 'darwin'

# Server config
PORT = 5050
SERVER_URL = f"http://localhost:{PORT}/"

# Global state
flask_thread = None
tray_icon = None
webview_window = None


def log_to_file(msg):
    """Log message to server.log (no console output for .pyw)"""
    log_file = os.path.join(SCRIPT_DIR, 'server.log')
    try:
        with open(log_file, 'a') as f:
            f.write(f"[LAUNCHER] {time.strftime('%Y-%m-%d %H:%M:%S')} {msg}\n")
    except:
        pass


def install_dependencies(splash=None):
    """Auto-install missing packages on first run"""
    import subprocess
    packages = [
        ('flask', 'Flask'),
        ('webview', 'pywebview'),
        ('pystray', 'pystray'),
        ('PIL', 'Pillow'),
    ]
    for import_name, pip_name in packages:
        try:
            __import__(import_name)
            log_to_file(f"[OK] {pip_name} already installed")
            update_splash_status(splash, f"Checking {pip_name}...")
        except ImportError:
            update_splash_status(splash, f"Installing {pip_name}...")
            log_to_file(f"Installing {pip_name}...")
            try:
                result = subprocess.run(
                    [sys.executable, '-m', 'pip', 'install', pip_name.lower(), '-q', '--user'],
                    capture_output=True,
                    timeout=120
                )
                if result.returncode != 0:
                    log_to_file(f"[WARN] Failed to install {pip_name}: {result.stderr.decode()}")
            except subprocess.TimeoutExpired:
                log_to_file(f"[WARN] Timeout installing {pip_name}, continuing...")
            except Exception as e:
                log_to_file(f"[WARN] Error installing {pip_name}: {e}")


def create_icon_image():
    """Create a warm amber/gold icon for system tray"""
    from PIL import Image, ImageDraw

    # Create 64x64 icon with warm amber/gold theme
    size = 64
    img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
    draw = ImageDraw.Draw(img)

    # Deep charcoal background (#0d0d0f)
    draw.ellipse([2, 2, size-2, size-2], fill=(13, 13, 15, 255))

    # Warm amber "C" shape (#d4a373)
    amber = (212, 163, 115, 255)
    draw.arc([12, 12, size-12, size-12], start=45, end=315, fill=amber, width=8)

    return img


def show_splash():
    """Show splash screen during startup - warm amber aesthetic"""
    try:
        import tkinter as tk
        from tkinter import Canvas
    except ImportError:
        log_to_file("[SPLASH] tkinter not available, skipping splash")
        return None

    splash = tk.Tk()
    splash.title("CARL Control Panel")
    splash.overrideredirect(True)  # No window decorations

    # Window size and centering
    width, height = 420, 280
    screen_w = splash.winfo_screenwidth()
    screen_h = splash.winfo_screenheight()
    x = (screen_w - width) // 2
    y = (screen_h - height) // 2
    splash.geometry(f"{width}x{height}+{x}+{y}")

    # Color palette - warm amber theme
    bg_deep = '#0a0a0b'      # Deeper black for gradient
    bg_color = '#0d0d0f'     # Deep charcoal
    accent = '#d4a373'       # Warm amber
    accent_glow = '#5a4832'  # Dim amber for glow
    accent_bright = '#e9c088'
    text_secondary = '#9a958d'
    text_muted = '#6b6660'
    border_color = '#2a2520'

    # Outer border frame
    border_frame = tk.Frame(splash, bg=border_color, padx=1, pady=1)
    border_frame.pack(fill='both', expand=True)

    # Canvas for gradient background
    canvas = Canvas(border_frame, width=width-2, height=height-2,
                   bg=bg_color, highlightthickness=0)
    canvas.pack(fill='both', expand=True)

    # Draw subtle radial gradient (concentric rectangles fading from center)
    cx, cy = (width-2)//2, (height-2)//2
    for i in range(15, 0, -1):
        # Interpolate from bg_deep at edges to slightly lighter at center
        factor = i / 15
        r = int(13 + (20 - 13) * (1 - factor))
        g = int(13 + (18 - 13) * (1 - factor))
        b = int(15 + (20 - 15) * (1 - factor))
        color = f'#{r:02x}{g:02x}{b:02x}'
        size = int(max(width, height) * factor)
        canvas.create_oval(cx - size, cy - size, cx + size, cy + size,
                          fill=color, outline='')

    # Title glow layers (simulated text shadow)
    title_y = 70
    for offset, alpha in [(3, accent_glow), (2, accent_glow), (1, accent_glow)]:
        canvas.create_text(cx + offset, title_y + offset, text="C A R L",
                          font=('Consolas', 42, 'bold'), fill=alpha)
        canvas.create_text(cx - offset, title_y + offset, text="C A R L",
                          font=('Consolas', 42, 'bold'), fill=alpha)

    # Main title
    canvas.create_text(cx, title_y, text="C A R L",
                      font=('Consolas', 42, 'bold'), fill=accent)

    # Subtitle with letter spacing
    canvas.create_text(cx, title_y + 50, text="C o n t r o l   P a n e l",
                      font=('Consolas', 11), fill=text_secondary)

    # Thin decorative line
    line_y = title_y + 75
    canvas.create_line(cx - 60, line_y, cx + 60, line_y,
                      fill=border_color, width=1)

    # Status text
    status_var = tk.StringVar(value="Starting")
    status_id = canvas.create_text(cx, height - 70, text="Starting",
                                   font=('Consolas', 10), fill=text_muted)

    # Pulsing amber dot (loading indicator)
    dot_radius = 4
    dot_id = canvas.create_oval(cx - dot_radius, height - 45 - dot_radius,
                                cx + dot_radius, height - 45 + dot_radius,
                                fill=accent, outline='')

    # Store references for updates
    splash.canvas = canvas
    splash.status_id = status_id
    splash.dot_id = dot_id
    splash.status_var = status_var
    splash.dot_pulse_state = 0
    splash.cx = cx
    splash.dot_radius = dot_radius
    splash.accent = accent
    splash.accent_glow = accent_glow

    return splash


def animate_splash(splash, stop_event):
    """Animate the splash screen - pulsing dot effect"""
    if not splash:
        return

    pulse_colors = ['#d4a373', '#c49466', '#b48559', '#a4764c', '#94673f',
                    '#a4764c', '#b48559', '#c49466', '#d4a373']
    i = 0
    while not stop_event.is_set():
        try:
            # Pulse the loading dot color
            color = pulse_colors[i % len(pulse_colors)]
            splash.canvas.itemconfig(splash.dot_id, fill=color)
            splash.update()
            i += 1
            time.sleep(0.12)
        except:
            break


def update_splash_status(splash, message):
    """Update splash screen status message"""
    if splash:
        try:
            splash.canvas.itemconfig(splash.status_id, text=message)
            splash.update()
        except:
            pass


def close_splash(splash):
    """Close the splash screen"""
    if splash:
        try:
            splash.destroy()
        except:
            pass


def start_flask_server():
    """Start the Flask server in the current thread"""
    try:
        # Force fresh import of app module (no caching)
        import importlib
        import app as app_module
        importlib.reload(app_module)
        app = app_module.app
        load_active_profile = app_module.load_active_profile

        from api.database import init_database

        # Initialize
        init_database()
        load_active_profile()

        # Run Flask (this blocks)
        log_to_file("Starting Flask server...")
        app.run(host='127.0.0.1', port=PORT, debug=False, use_reloader=False, threaded=True)
    except Exception as e:
        log_to_file(f"Flask server error: {e}")


def start_server():
    """Start the Flask server in a background thread"""
    global flask_thread

    flask_thread = threading.Thread(target=start_flask_server, daemon=True)
    flask_thread.start()

    return flask_thread


def wait_for_server(timeout=30):
    """Wait for server to be ready"""
    import urllib.request
    import urllib.error

    start = time.time()
    while time.time() - start < timeout:
        try:
            url = f"http://localhost:{PORT}/"
            urllib.request.urlopen(url, timeout=1)
            return True
        except (urllib.error.URLError, Exception):
            time.sleep(0.5)
    return False


def open_browser():
    """Open the app in a native window using PyWebView"""
    global webview_window

    # If window already exists, try to bring it to focus
    if webview_window:
        try:
            webview_window.show()
            webview_window.restore()
            return
        except:
            pass

    try:
        import webview

        webview_window = webview.create_window(
            'CARL Control Panel',
            SERVER_URL,
            width=650,
            height=700,
            min_size=(500, 400),
            text_select=True,
            easy_drag=False,
            zoomable=True
        )

    except Exception as e:
        log_to_file(f"[WEBVIEW] Failed to create window: {e}")


def set_window_icon():
    """Set window icon using Windows API (runs after window created)"""
    if not IS_WINDOWS:
        return
    try:
        import ctypes

        # Load icon from file
        icon_path = os.path.join(SCRIPT_DIR, 'assets', 'icon.ico')
        if not os.path.exists(icon_path):
            return

        WM_SETICON = 0x0080
        ICON_SMALL = 0
        ICON_BIG = 1
        IMAGE_ICON = 1
        LR_LOADFROMFILE = 0x0010

        user32 = ctypes.windll.user32

        hwnd = user32.FindWindowW(None, "CARL Control Panel")
        if hwnd:
            hicon_big = user32.LoadImageW(0, icon_path, IMAGE_ICON, 48, 48, LR_LOADFROMFILE)
            hicon_small = user32.LoadImageW(0, icon_path, IMAGE_ICON, 16, 16, LR_LOADFROMFILE)

            if hicon_big:
                user32.SendMessageW(hwnd, WM_SETICON, ICON_BIG, hicon_big)
            if hicon_small:
                user32.SendMessageW(hwnd, WM_SETICON, ICON_SMALL, hicon_small)
    except Exception as e:
        log_to_file(f"[ICON] Failed to set icon: {e}")


def start_webview():
    """Start the webview (blocking call)"""
    global webview_window
    if webview_window:
        try:
            import webview

            log_to_file(f"PyWebView version: {webview.__version__ if hasattr(webview, '__version__') else 'unknown'}")

            # Set icon after window is shown
            def on_shown():
                time.sleep(0.5)
                set_window_icon()

            icon_thread = threading.Thread(target=on_shown, daemon=True)
            icon_thread.start()

            # Use EdgeChromium backend on Windows (WebView2)
            if IS_WINDOWS:
                try:
                    log_to_file("Starting with edgechromium backend...")
                    webview.start(gui='edgechromium')
                except Exception as e:
                    log_to_file(f"edgechromium failed: {e}, using default...")
                    webview.start()
            else:
                webview.start()
        except Exception as e:
            log_to_file(f"Failed to start webview: {e}")


def view_logs(icon=None, item=None):
    """Open the server log file"""
    import subprocess
    log_file = os.path.join(SCRIPT_DIR, 'server.log')
    if IS_WINDOWS:
        subprocess.Popen(['notepad', log_file])
    elif IS_MAC:
        subprocess.Popen(['open', '-a', 'TextEdit', log_file])
    else:
        subprocess.Popen(['xdg-open', log_file])


def restart_app(icon=None, item=None):
    """Restart the application"""
    import subprocess
    global tray_icon, webview_window

    # Close PyWebView window first
    if webview_window:
        try:
            webview_window.destroy()
        except:
            pass

    if tray_icon:
        tray_icon.stop()

    # Relaunch the launcher
    launcher_path = os.path.join(SCRIPT_DIR, 'launcher.pyw')
    subprocess.Popen([sys.executable, launcher_path], cwd=SCRIPT_DIR)
    sys.exit(0)


def check_updates_background():
    """Check for updates in background after startup"""
    try:
        # Wait a bit for server to be fully ready
        time.sleep(5)

        # Import updater and check
        from api.updater import check_for_updates_async
        check_for_updates_async()
        log_to_file("[UPDATE] Background update check initiated")
    except Exception as e:
        log_to_file(f"[UPDATE] Background check failed: {e}")


def fetch_updates(icon=None, item=None):
    """Check for and apply updates via GitHub releases"""
    try:
        from api.updater import check_for_updates, download_update, apply_update

        log_to_file("[UPDATE] Manual update check...")

        # Check for updates
        status = check_for_updates(force=True)

        if status.get('error'):
            log_to_file(f"[UPDATE] Check failed: {status['error']}")
            return

        if not status.get('available'):
            log_to_file("[UPDATE] Already up to date")
            return

        log_to_file(f"[UPDATE] Update available: v{status.get('latest_version')}")

        # Download update
        download_path = download_update()
        if not download_path:
            log_to_file("[UPDATE] Download failed")
            return

        log_to_file(f"[UPDATE] Downloaded to: {download_path}")

        # Apply update (will restart app)
        result = apply_update()
        if result.get('success'):
            log_to_file("[UPDATE] Update applied, restarting...")
            # The apply_update function handles the restart
            quit_app(icon, item)
        else:
            log_to_file(f"[UPDATE] Apply failed: {result.get('error')}")

    except Exception as e:
        log_to_file(f"[UPDATE] Update failed: {e}")


def quit_app(icon=None, item=None):
    """Quit the application"""
    global tray_icon, webview_window

    if webview_window:
        try:
            webview_window.destroy()
        except:
            pass

    if tray_icon:
        tray_icon.stop()

    # Clean up lock file
    try:
        import tempfile
        lock_file = os.path.join(tempfile.gettempdir(), 'carl_control_panel.lock')
        if os.path.exists(lock_file):
            os.remove(lock_file)
    except:
        pass
    sys.exit(0)


def focus_window(icon=None, item=None):
    """Bring PyWebView window to focus and maximize (tray action)"""
    global webview_window
    if webview_window:
        try:
            webview_window.show()
            webview_window.restore()
            webview_window.on_top = True
            webview_window.on_top = False
        except Exception as e:
            log_to_file(f"[TRAY] Failed to focus window: {e}")


def setup_tray():
    """Setup system tray icon"""
    global tray_icon

    try:
        import pystray
        from pystray import MenuItem as item
    except ImportError:
        log_to_file("[TRAY] pystray not available")
        return None

    icon_image = create_icon_image()

    menu = pystray.Menu(
        item('Open CARL', focus_window, default=True),
        item('Server Running', lambda: None, enabled=False),
        pystray.Menu.SEPARATOR,
        item('Restart', restart_app),
        item('Fetch Updates', fetch_updates),
        item('View Logs', view_logs),
        pystray.Menu.SEPARATOR,
        item('Quit', quit_app)
    )

    tray_icon = pystray.Icon(
        'carl-control-panel',
        icon_image,
        'CARL Control Panel',
        menu
    )

    return tray_icon


def main():
    """Main launcher function"""
    global tray_icon

    # Show splash screen IMMEDIATELY (tkinter is built-in)
    splash = show_splash()

    # Start animation in background
    stop_animation = threading.Event()
    if splash:
        anim_thread = threading.Thread(target=animate_splash, args=(splash, stop_animation))
        anim_thread.daemon = True
        anim_thread.start()

    # Install/check dependencies (pystray, Pillow, pywebview, flask)
    update_splash_status(splash, "Checking dependencies...")
    install_dependencies(splash)

    # Start server (in-process thread)
    update_splash_status(splash, "Starting server...")
    start_server()

    # Wait for server
    update_splash_status(splash, "Waiting for server...")
    if wait_for_server(timeout=30):
        update_splash_status(splash, "Server ready!")
        time.sleep(0.5)
    else:
        update_splash_status(splash, "Server may have failed to start")
        time.sleep(2)

    # Stop animation and close splash
    stop_animation.set()
    close_splash(splash)

    # Setup tray in background thread
    tray_icon = setup_tray()
    if tray_icon:
        tray_thread = threading.Thread(target=tray_icon.run, daemon=True)
        tray_thread.start()

    # Start background update check
    update_thread = threading.Thread(target=check_updates_background, daemon=True)
    update_thread.start()

    # Open window
    open_browser()
    start_webview()
    quit_app()


if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        # Log crash to file since pythonw.exe hides console
        import traceback
        crash_log = os.path.join(SCRIPT_DIR, 'launcher_crash.log')
        with open(crash_log, 'w') as f:
            f.write(f"Launcher crashed at {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"Error: {e}\n\n")
            f.write(traceback.format_exc())
        raise
