#!/usr/bin/env python3
"""
VictoryClaw Activation Wizard v1.1
Python+GTK desktop app for WUV OS.

Launches on first boot if /opt/victoryclaw/config/sim_key.txt does not exist.
If already activated, exits immediately without showing any UI.
Checks WiFi connectivity first, then opens browser to dreamguide.com/activate,
waits for localhost:18790 redirect, then shows "Victory is ready" and
minimizes to system tray.

Build: python3 setup.py build
Package: included in victoryclaw-desktop.deb
"""

# Route legacy activation shortcuts before loading any GUI dependencies.
import os
import sys
from pathlib import Path

def activation_complete():
    import json
    state = json.loads(Path('/opt/victoryclaw/state/activation.json').read_text()).get('state')
    if state not in ('activated', 'unactivated', 'revoked'):
        raise OSError('Local activation status unavailable; repair through Victory Setup')
    return state == 'activated'

def launcher_state():
    try:
        return activation_complete()
    except (OSError, ValueError):
        import subprocess
        subprocess.run(['/usr/bin/python3', '/usr/lib/victoryclaw/victory-update-check.py', '--startup-error'])
        raise SystemExit(1)

if __name__ == '__main__' and launcher_state():
    if '--autostart' in sys.argv:
        raise SystemExit(0)
    os.execv('/usr/bin/victory-open', ['/usr/bin/victory-open'] +
             (['--skip-update-check'] if '--skip-update-check' in sys.argv else []))

import gi
gi.require_version('Gtk', '3.0')
try:
    gi.require_version('AyatanaAppIndicator3', '0.1')
    from gi.repository import AyatanaAppIndicator3 as AppIndicator3
except (ValueError, ImportError):
    gi.require_version('AppIndicator3', '0.1')
    from gi.repository import AppIndicator3
from gi.repository import Gtk, GdkPixbuf, GLib, Gdk
import os
import sys
import time
import threading
import urllib.request
import subprocess
import shutil
import json
import re

# -- Config --
SIM_KEY_FILE = "/opt/victoryclaw/config/sim_key.txt"
ACTIVATION_URL = "https://dreamguide.com/activate"
LOCAL_ACTIVATION_PORT = 18790
GATEWAY_PORT = 18789
HEARTBEAT_INTERVAL = 2
ACTIVATION_TIMEOUT = 600
HEART_ICON = "/usr/share/wuv/heart.png"
CONNECTIVITY_TEST_URL = "http://connectivity-check.ubuntu.com"
CONNECTIVITY_TIMEOUT = 5


def check_internet():
    """Returns True if we have internet connectivity."""
    try:
        req = urllib.request.Request(CONNECTIVITY_TEST_URL, method="HEAD")
        with urllib.request.urlopen(req, timeout=CONNECTIVITY_TIMEOUT) as resp:
            return resp.status == 204 or resp.status == 200
    except:
        try:
            req = urllib.request.Request("https://dreamguide.com", method="HEAD")
            with urllib.request.urlopen(req, timeout=CONNECTIVITY_TIMEOUT) as resp:
                return resp.status < 400
        except:
            return False


def get_wifi_networks():
    """Scan available WiFi networks using nmcli. Returns list of (ssid, signal) tuples."""
    try:
        result = subprocess.run(
            ['nmcli', '-t', '-f', 'SSID,SIGNAL,SECURITY', 'device', 'wifi', 'list'],
            capture_output=True, text=True, timeout=15
        )
        networks = []
        seen = set()
        for line in result.stdout.strip().split('\n'):
            if not line:
                continue
            parts = line.split(':')
            ssid = parts[0] if len(parts) > 0 else ""
            signal = parts[1] if len(parts) > 1 else "0"
            security = parts[2] if len(parts) > 2 else ""
            if ssid and ssid not in seen:
                seen.add(ssid)
                networks.append({"ssid": ssid, "signal": int(signal) if signal.isdigit() else 0, "security": security})
        networks.sort(key=lambda n: n["signal"], reverse=True)
        return networks
    except Exception as e:
        print(f"[wizard] WiFi scan error: {e}", flush=True)
        return []


def connect_wifi(ssid, password):
    """Connect to a WiFi network. Returns True on success."""
    try:
        cmd = ['nmcli', 'device', 'wifi', 'connect', ssid]
        if password:
            cmd.extend(['password', password])
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
        if result.returncode == 0:
            time.sleep(3)
            return check_internet()
        return False
    except:
        return False



def open_browser(url):
    """Launch a real browser window for the sign-in flow. xdg-open on some
    desktops resolves https:// to a text viewer instead of a browser, which
    silently breaks activation (the page renders as unclickable raw HTML)."""
    for browser in ("firefox", "google-chrome", "chromium-browser", "chromium"):
        path = shutil.which(browser)
        if path:
            subprocess.Popen([path, url])
            return browser
    subprocess.Popen(["xdg-open", url])
    return "xdg-open"


class ActivationWizard:
    def __init__(self):
        self.window = Gtk.Window(title="VictoryClaw Activation")
        self.window.set_default_size(520, 400)
        self.window.set_resizable(False)
        self.window.set_position(Gtk.WindowPosition.CENTER)
        
        try:
            self.window.set_icon_from_file(HEART_ICON)
        except:
            pass
        
        self.window.connect("destroy", self.on_window_close)
        
        # State
        self.activated = launcher_state()
        self.revoked = json.loads(Path('/opt/victoryclaw/state/activation.json').read_text()).get('state') == 'revoked'
        self.poll_thread = None
        self.poll_running = False
        
        # Build UI
        self.stack = Gtk.Stack()
        self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
        self.stack.set_transition_duration(300)
        
        self.build_wifi_page()
        self.build_welcome_page()
        self.build_activating_page()
        self.build_success_page()
        self.build_error_page()
        
        self.window.add(self.stack)
        
        if self.activated:
            # Already activated — exit immediately, don't show UI or tray
            print("[wizard] Already activated — exiting", flush=True)
            sys.exit(0)
        else:
            # Start WiFi check
            self.stack.set_visible_child_name("wifi")
            GLib.idle_add(self.check_connectivity)
    
    # ── WiFi Page ──────────────────────────────────────────────
    
    def build_wifi_page(self):
        page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=15)
        page.set_valign(Gtk.Align.CENTER)
        page.set_halign(Gtk.Align.CENTER)
        page.set_margin_left(40)
        page.set_margin_right(40)
        
        try:
            try:
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(HEART_ICON, 72, 72, True)
                img = Gtk.Image.new_from_pixbuf(pixbuf)
            except Exception:
                img = Gtk.Image.new_from_file(HEART_ICON)
                img.set_pixel_size(72)
            page.pack_start(img, False, False, 0)
        except:
            pass
        
        self.wifi_title = Gtk.Label(label="Checking connection...")
        self.wifi_title.get_style_context().add_class("title")
        self.wifi_title.set_margin_top(10)
        page.pack_start(self.wifi_title, False, False, 0)
        
        self.wifi_spinner = Gtk.Spinner()
        self.wifi_spinner.start()
        self.wifi_spinner.set_size_request(48, 48)
        page.pack_start(self.wifi_spinner, False, False, 0)
        
        # Network list (hidden until scan completes)
        self.wifi_scrolled = Gtk.ScrolledWindow()
        self.wifi_scrolled.set_min_content_height(200)
        self.wifi_scrolled.set_propagate_natural_height(True)
        
        self.wifi_listbox = Gtk.ListBox()
        self.wifi_listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
        self.wifi_listbox.connect("row-activated", self.on_wifi_selected)
        self.wifi_scrolled.add(self.wifi_listbox)
        
        self.wifi_scrolled.set_no_show_all(True)
        page.pack_start(self.wifi_scrolled, False, False, 0)
        
        # Password entry (hidden until network selected)
        self.wifi_password_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        self.wifi_password_entry = Gtk.Entry()
        self.wifi_password_entry.set_placeholder_text("WiFi password")
        self.wifi_password_entry.set_visibility(False)
        self.wifi_password_box.pack_start(self.wifi_password_entry, True, True, 0)
        
        self.wifi_connect_btn = Gtk.Button(label="Connect")
        self.wifi_connect_btn.get_style_context().add_class("suggested-action")
        self.wifi_connect_btn.connect("clicked", self.on_wifi_connect)
        self.wifi_password_box.pack_start(self.wifi_connect_btn, False, False, 0)
        
        self.wifi_password_box.set_no_show_all(True)
        page.pack_start(self.wifi_password_box, False, False, 0)
        
        # Skip button (if already connected)
        self.wifi_skip_btn = Gtk.Button(label="Continue")
        self.wifi_skip_btn.set_margin_top(10)
        self.wifi_skip_btn.connect("clicked", self.on_wifi_skip)
        page.pack_start(self.wifi_skip_btn, False, False, 0)
        
        self.stack.add_named(page, "wifi")
    
    def check_connectivity(self):
        """Check internet connectivity. If connected, skip to welcome."""
        def do_check():
            if check_internet():
                GLib.idle_add(self.on_wifi_connected)
            else:
                GLib.idle_add(self.show_wifi_networks)
        
        threading.Thread(target=do_check, daemon=True).start()
    
    def on_wifi_connected(self):
        self.stack.set_visible_child_name("welcome")
    
    def show_wifi_networks(self):
        """Show WiFi network picker."""
        self.wifi_spinner.stop()
        self.wifi_spinner.hide()
        self.wifi_title.set_text("Connect to WiFi")
        
        # Clear existing rows
        for child in self.wifi_listbox.get_children():
            self.wifi_listbox.remove(child)
        
        networks = get_wifi_networks()
        if not networks:
            label = Gtk.Label(label="No WiFi networks found.\nMake sure your WiFi switch is on.")
            label.set_margin_top(20)
            self.wifi_listbox.add(label)
            self.wifi_listbox.show_all()
            return
        
        for net in networks:
            row = Gtk.ListBoxRow()
            box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
            box.set_margin_top(8)
            box.set_margin_bottom(8)
            box.set_margin_left(12)
            box.set_margin_right(12)
            
            ssid_label = Gtk.Label(label=net["ssid"])
            ssid_label.set_halign(Gtk.Align.START)
            box.pack_start(ssid_label, True, True, 0)
            
            signal_label = Gtk.Label(label=f"{net['signal']}%")
            signal_label.get_style_context().add_class("dim-label")
            box.pack_start(signal_label, False, False, 0)
            
            if net["security"]:
                lock = Gtk.Label(label="🔒")
                box.pack_start(lock, False, False, 0)
            
            row.add(box)
            row.ssid = net["ssid"]
            row.security = net["security"]
            self.wifi_listbox.add(row)
        
        self.wifi_scrolled.set_no_show_all(False)
        self.wifi_scrolled.show_all()
        self.wifi_skip_btn.hide()
    
    def on_wifi_selected(self, listbox, row):
        """When a WiFi network is selected, show password entry if secured."""
        self.selected_ssid = row.ssid
        if row.security:
            self.wifi_password_box.set_no_show_all(False)
            self.wifi_password_box.show_all()
            self.wifi_password_entry.grab_focus()
        else:
            self.wifi_password_box.hide()
            self.wifi_connect_btn.emit("clicked")
    
    def on_wifi_connect(self, widget):
        """Connect to selected WiFi network."""
        ssid = getattr(self, 'selected_ssid', None)
        if not ssid:
            return
        
        password = self.wifi_password_entry.get_text()
        self.wifi_connect_btn.set_sensitive(False)
        self.wifi_connect_btn.set_label("Connecting...")
        
        def do_connect():
            success = connect_wifi(ssid, password)
            GLib.idle_add(lambda: self.on_wifi_connect_result(success))
        
        threading.Thread(target=do_connect, daemon=True).start()
    
    def on_wifi_connect_result(self, success):
        if success:
            self.stack.set_visible_child_name("welcome")
        else:
            self.wifi_connect_btn.set_sensitive(True)
            self.wifi_connect_btn.set_label("Connect")
            self.wifi_title.set_text("Connection failed — try again")
    
    def on_wifi_skip(self, widget):
        self.stack.set_visible_child_name("welcome")
    
    # ── Welcome Page ───────────────────────────────────────────
    
    def build_welcome_page(self):
        page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
        page.set_valign(Gtk.Align.CENTER)
        page.set_halign(Gtk.Align.CENTER)
        page.set_margin_left(40)
        page.set_margin_right(40)
        
        try:
            try:
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(HEART_ICON, 96, 96, True)
                img = Gtk.Image.new_from_pixbuf(pixbuf)
            except Exception:
                img = Gtk.Image.new_from_file(HEART_ICON)
                img.set_pixel_size(96)
            page.pack_start(img, False, False, 0)
        except:
            pass
        
        title = Gtk.Label(label="Re-activate Victory" if self.revoked else "Victory is waiting")
        title.get_style_context().add_class("title")
        title.set_margin_top(10)
        page.pack_start(title, False, False, 0)
        
        subtitle = Gtk.Label(label="This device was deactivated.\nSign in again to re-activate Victory on this device." if self.revoked else "Click below to activate Victory on this device.\nYou'll sign in to your DreamGuide account — that's it.")
        subtitle.set_line_wrap(True)
        subtitle.set_max_width_chars(40)
        subtitle.set_margin_top(10)
        page.pack_start(subtitle, False, False, 0)
        
        btn = Gtk.Button(label="Activate Victory")
        btn.get_style_context().add_class("suggested-action")
        btn.set_margin_top(20)
        btn.set_size_request(200, 44)
        btn.connect("clicked", self.on_activate_clicked)
        page.pack_start(btn, False, False, 0)
        
        self.stack.add_named(page, "welcome")
    
    # ── Activating Page ────────────────────────────────────────
    
    def build_activating_page(self):
        page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
        page.set_valign(Gtk.Align.CENTER)
        page.set_halign(Gtk.Align.CENTER)
        page.set_margin_left(40)
        page.set_margin_right(40)
        
        spinner = Gtk.Spinner()
        spinner.start()
        spinner.set_size_request(64, 64)
        page.pack_start(spinner, False, False, 0)
        
        self.activating_label = Gtk.Label(label="Opening your browser...\nSign in to DreamGuide to activate Victory on this device.")
        self.activating_label.set_line_wrap(True)
        self.activating_label.set_max_width_chars(40)
        self.activating_label.set_margin_top(10)
        page.pack_start(self.activating_label, False, False, 0)
        
        self.status_label = Gtk.Label(label="")
        self.status_label.set_margin_top(10)
        self.status_label.get_style_context().add_class("dim-label")
        page.pack_start(self.status_label, False, False, 0)
        
        self.stack.add_named(page, "activating")
    
    # ── Success Page ───────────────────────────────────────────
    
    def build_success_page(self):
        page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
        page.set_valign(Gtk.Align.CENTER)
        page.set_halign(Gtk.Align.CENTER)
        page.set_margin_left(40)
        page.set_margin_right(40)
        
        try:
            try:
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(HEART_ICON, 96, 96, True)
                img = Gtk.Image.new_from_pixbuf(pixbuf)
            except Exception:
                img = Gtk.Image.new_from_file(HEART_ICON)
                img.set_pixel_size(96)
            page.pack_start(img, False, False, 0)
        except:
            pass
        
        title = Gtk.Label(label="Victory is ready")
        title.get_style_context().add_class("title")
        title.set_margin_top(10)
        page.pack_start(title, False, False, 0)
        
        subtitle = Gtk.Label(label="Victory is running on this device.\nIt will start automatically on every login.")
        subtitle.set_line_wrap(True)
        subtitle.set_max_width_chars(40)
        subtitle.set_margin_top(10)
        page.pack_start(subtitle, False, False, 0)
        
        btn = Gtk.Button(label="Open Victory")
        btn.get_style_context().add_class("suggested-action")
        btn.set_margin_top(20)
        btn.set_size_request(200, 44)
        btn.connect("clicked", self.on_open_victory)
        page.pack_start(btn, False, False, 0)
        
        self.stack.add_named(page, "success")
    
    # ── Error Page ────────────────────────────────────────────
    
    def build_error_page(self):
        page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
        page.set_valign(Gtk.Align.CENTER)
        page.set_halign(Gtk.Align.CENTER)
        page.set_margin_left(40)
        page.set_margin_right(40)
        
        title = Gtk.Label(label="Activation failed")
        title.get_style_context().add_class("title")
        page.pack_start(title, False, False, 0)
        
        self.error_detail = Gtk.Label(label="")
        self.error_detail.set_line_wrap(True)
        self.error_detail.set_max_width_chars(40)
        self.error_detail.set_margin_top(10)
        page.pack_start(self.error_detail, False, False, 0)
        
        btn = Gtk.Button(label="Try Again")
        btn.get_style_context().add_class("suggested-action")
        btn.set_margin_top(20)
        btn.set_size_request(200, 44)
        btn.connect("clicked", self.on_retry)
        page.pack_start(btn, False, False, 0)
        
        self.stack.add_named(page, "error")
    
    # ── Activation Flow ────────────────────────────────────────
    
    def on_activate_clicked(self, widget):
        # Revocation is cleared by the server after authenticated redemption.
        self.stack.set_visible_child_name("activating")
        self.status_label.set_text("Opening browser...")
        
        launched_with = open_browser(ACTIVATION_URL)
        print(f"[wizard] Opened sign-in page with: {launched_with}", flush=True)
        
        self.poll_running = True
        self.poll_thread = threading.Thread(target=self.poll_activation, daemon=True)
        self.poll_thread.start()
    
    def poll_activation(self):
        start = time.time()
        
        while self.poll_running and (time.time() - start) < ACTIVATION_TIMEOUT:
            if launcher_state():
                GLib.idle_add(self.on_activation_success)
                return
            
            try:
                req = urllib.request.Request(f"http://localhost:{LOCAL_ACTIVATION_PORT}/status")
                with urllib.request.urlopen(req, timeout=3) as resp:
                    data = json.loads(resp.read())
                    status = data.get("status")
                    if status == "activated":
                        GLib.idle_add(self.on_activation_success)
                        return
                    elif status == "error":
                        message = data.get("message") or "Activation failed. Please try again."
                        GLib.idle_add(lambda m=message: self.on_activation_error(m))
                        return
                    elif status == "activating":
                        message = data.get("message") or "Redeeming activation..."
                        GLib.idle_add(lambda m=message: self.status_label.set_text(m))
                    elif status == "waiting":
                        elapsed = int(time.time() - start)
                        GLib.idle_add(
                            lambda e=elapsed: self.status_label.set_text(
                                f"Waiting for sign-in... ({e}s)"
                            )
                        )
            except:
                elapsed = int(time.time() - start)
                if elapsed > 5:
                    GLib.idle_add(
                        lambda e=elapsed: self.status_label.set_text(
                            f"Waiting for activation server... ({e}s)"
                        )
                    )
            
            time.sleep(HEARTBEAT_INTERVAL)
        
        GLib.idle_add(self.on_activation_timeout)
    
    def on_activation_success(self):
        self.poll_running = False
        self.activated = True
        self.stack.set_visible_child_name("success")
        self.setup_tray()
        GLib.timeout_add(1000, lambda: self.on_open_victory(None))
    
    def on_activation_error(self, message):
        self.poll_running = False
        self.error_detail.set_text(message)
        self.stack.set_visible_child_name("error")

    def on_activation_timeout(self):
        self.poll_running = False
        self.error_detail.set_text(
            "Activation timed out after 10 minutes.\n"
            "Make sure Victory is installed and the activation server is running."
        )
        self.stack.set_visible_child_name("error")
    
    def on_open_victory(self, widget=None):
        if getattr(self, "opening_victory", False): return False
        self.opening_victory = True
        # Self-destruct: remove autostart entry and delete wizard
        try:
            import os as _os, glob as _glob
            # Remove autostart entries
            for pattern in ["/etc/xdg/autostart/victory*-wizard*",
                          "/home/*/.config/autostart/victory*-wizard*",
                          "/etc/xdg/autostart/victoryclaw*activation*",
                          "/home/*/.config/autostart/victoryclaw*activation*"]:
                for f in _glob.glob(pattern):
                    try: _os.remove(f)
                    except: pass
            # Remove the wizard binary itself
            wizard_paths = ["/usr/bin/victory-activate", "/usr/bin/victoryclaw-activate"]
            for wp in wizard_paths:
                if _os.path.exists(wp):
                    try: _os.remove(wp)
                    except: pass
            # Remove this script if it's in /tmp
            script_path = _os.path.abspath(__file__)
            if "/tmp/" in script_path:
                try: _os.unlink(script_path)
                except: pass
            print("[wizard] Self-cleaned up after successful activation", flush=True)
        except Exception as e:
            print(f"[wizard] Cleanup failed: {e}", flush=True)

        # v1.4.2 activation server exits after redemption; the container
        # re-execs into activated mode itself. No redundant password prompt.
        # victory-open waits for the UI and offers recovery only if needed.

        # Open Victory
        subprocess.Popen(['/usr/bin/victory-open'])
        self.window.hide()
        Gtk.main_quit()
        return False
    
    def on_retry(self, widget):
        self.stack.set_visible_child_name("welcome")
    
    # ── Tray Icon ──────────────────────────────────────────────
    
    def setup_tray(self):
        try:
            indicator = AppIndicator3.Indicator.new(
                "victory",
                HEART_ICON if os.path.exists(HEART_ICON) else "applications-system",
                AppIndicator3.IndicatorCategory.APPLICATION_STATUS
            )
            indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
            
            menu = Gtk.Menu()
            
            item_open = Gtk.MenuItem(label="Open Victory")
            item_open.connect("activate", lambda w: subprocess.Popen(['/usr/bin/victory-open']))
            menu.append(item_open)
            
            item_quit = Gtk.MenuItem(label="Quit")
            item_quit.connect("activate", self.on_quit)
            menu.append(item_quit)
            
            menu.show_all()
            indicator.set_menu(menu)
            self.indicator = indicator
        except Exception as e:
            print(f"[wizard] Tray setup failed: {e}", flush=True)
    
    def on_window_close(self, widget):
        if self.activated:
            self.window.hide()
            return True  # Don't destroy, just hide
        else:
            return False  # Allow close only if activated
    
    def on_quit(self, widget):
        if self.activated:
            # If activated and user quits, also self-delete
            try:
                import os as _os, glob as _glob
                for pattern in ["/etc/xdg/autostart/victory*-wizard*",
                              "/home/*/.config/autostart/victory*-wizard*",
                              "/etc/xdg/autostart/victoryclaw*activation*",
                              "/home/*/.config/autostart/victoryclaw*activation*"]:
                    for f in _glob.glob(pattern):
                        try: _os.remove(f)
                        except: pass
                # Keep activation entry points for legacy pinned shortcuts.
            except: pass
        Gtk.main_quit()
    
    def run(self):
        self.window.show_all()
        
        # Hide conditional elements
        self.wifi_scrolled.hide()
        self.wifi_password_box.hide()
        
        Gtk.main()


if __name__ == "__main__":
    if '--skip-update-check' not in sys.argv:
        check = '/usr/lib/victoryclaw/victory-update-check.py'
        if os.path.isfile(check):
            if subprocess.run(['/usr/bin/python3', check]).returncode == 20:
                sys.exit(0)
    app = ActivationWizard()
    app.run()
