namespace QuickSetup {

    // Simple data class for WiFi access points
    public class WifiNetwork : Object {
        public string ssid;
        public uint8 strength;
        public uint32 flags;
        public uint32 wpa_flags;
        public uint32 rsn_flags;
        public ObjectPath ap_path;
        
        public bool is_secured {
            get {
                return wpa_flags != 0 || rsn_flags != 0 || flags != 0;
            }
        }
    }

    public class NetworkPage : QsPage {

        private Adw.StatusPage status_page;
        private GLib.NetworkMonitor monitor;
        private Adw.PreferencesPage wifi_list_page;
        private Adw.PreferencesGroup wifi_group;
        private Gtk.Spinner refresh_spinner;
        private DBusProxy nm_proxy;
        private ObjectPath? wireless_device_path;
        private Adw.Dialog? captive_portal_dialog;
        private bool has_advanced = false;

        public override string page_id { get { return "network"; } }
        public override bool can_go_back_to { get { return false; } }

        public NetworkPage(PageContext context) {
            base(context, "Network");

            // Create a scrollable container for WiFi list
            var scroll = new Gtk.ScrolledWindow();
            scroll.vexpand = true;
            scroll.hexpand = true;
            scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);

            // Create the preferences page for WiFi list
            this.wifi_list_page = new Adw.PreferencesPage();
            this.wifi_list_page.vexpand = true;
            this.wifi_list_page.hexpand = true;

            // Header group with title and refresh button
            var header_group = new Adw.PreferencesGroup();
            header_group.title = context.config.get_page_string(
                this.page_id, "custom_title", "Connect to WiFi to Continue Quick Setup."
            );
            this.wifi_list_page.add(header_group);

            // WiFi networks group
            this.wifi_group = new Adw.PreferencesGroup();
            this.wifi_group.title = tr("Available Networks");
            
            // Add refresh spinner
            this.refresh_spinner = new Gtk.Spinner();
            this.refresh_spinner.spinning = true;
            this.wifi_group.set_header_suffix(this.refresh_spinner);
            
            this.wifi_list_page.add(this.wifi_group);

            // Actions group with refresh and settings buttons
            var actions_group = new Adw.PreferencesGroup();
            
            var refresh_row = new Adw.ActionRow();
            refresh_row.title = tr("Refresh Networks");
            refresh_row.subtitle = tr("Scan for available WiFi networks");
            refresh_row.add_prefix(new Gtk.Image.from_icon_name("view-refresh-symbolic"));
            refresh_row.set_activatable(true);
            refresh_row.activated.connect(() => {
                scan_wifi_networks();
            });
            actions_group.add(refresh_row);

            var settings_row = new Adw.ActionRow();
            settings_row.title = tr("Open Network Settings");
            settings_row.subtitle = tr("Advanced network configuration");
            settings_row.add_prefix(new Gtk.Image.from_icon_name("preferences-system-symbolic"));
            settings_row.set_activatable(true);
            settings_row.activated.connect(() => {
                open_network_settings();
            });
            actions_group.add(settings_row);

            this.wifi_list_page.add(actions_group);

            scroll.set_child(this.wifi_list_page);
            this.add_content(scroll);

            // --- Network Monitoring ---
            this.monitor = GLib.NetworkMonitor.get_default();

            // Listen for connectivity changes
            monitor.network_changed.connect((available) => {
                print("Network changed, connectivity: %s\n", this.monitor.get_connectivity().to_string());
                check_and_advance();
            });

            // Initial check - deferred to after constructor completes and page is pushed.
            GLib.Idle.add(() => {
                check_and_advance();
                return false;  // Don't repeat
            });

            // Initialize NetworkManager proxy and scan for networks
            init_networkmanager.begin();
        }

        private async void init_networkmanager() {
            try {
                // Create proxy for NetworkManager
                this.nm_proxy = yield new DBusProxy.for_bus(
                    BusType.SYSTEM,
                    DBusProxyFlags.NONE,
                    null,
                    "org.freedesktop.NetworkManager",
                    "/org/freedesktop/NetworkManager",
                    "org.freedesktop.NetworkManager",
                    null
                );

                // Get wireless device
                yield find_wireless_device();
                
                // Scan for networks
                yield scan_wifi_networks_async();
                
            } catch (Error e) {
                warning("Failed to initialize NetworkManager: %s", e.message);
                show_error_message("Failed to initialize WiFi: " + e.message);
            }
        }

        private async void find_wireless_device() throws Error {
            // Get all devices
            var result = yield nm_proxy.call(
                "GetDevices",
                null,
                DBusCallFlags.NONE,
                -1,
                null
            );

            var devices = result.get_child_value(0);
            
            for (size_t i = 0; i < devices.n_children(); i++) {
                var device_path = (ObjectPath) devices.get_child_value(i).get_string();
                
                // Get device type
                var device_proxy = yield new DBusProxy.for_bus(
                    BusType.SYSTEM,
                    DBusProxyFlags.NONE,
                    null,
                    "org.freedesktop.NetworkManager",
                    device_path,
                    "org.freedesktop.DBus.Properties",
                    null
                );

                var type_result = yield device_proxy.call(
                    "Get",
                    new Variant("(ss)", "org.freedesktop.NetworkManager.Device", "DeviceType"),
                    DBusCallFlags.NONE,
                    -1,
                    null
                );

                var device_type = type_result.get_child_value(0).get_variant().get_uint32();
                
                // DeviceType 2 = WiFi
                if (device_type == 2) {
                    this.wireless_device_path = device_path;
                    print("Found wireless device: %s\n", device_path);
                    break;
                }
            }

            if (this.wireless_device_path == null) {
                // Only warn if not already connected
                if (this.monitor.get_connectivity() != GLib.NetworkConnectivity.FULL) {
                    warning("No wireless device found");
                }
            }
        }

        private void scan_wifi_networks() {
            scan_wifi_networks_async.begin();
        }

        private async void scan_wifi_networks_async() {
            if (this.wireless_device_path == null) {
                // Check if already connected via ethernet or other means
                if (this.monitor.get_connectivity() == GLib.NetworkConnectivity.FULL) {
                    // Already connected, no need for WiFi - just show a message
                    update_wifi_list_no_adapter_but_connected();
                } else {
                    // Not connected and no WiFi adapter - show error
                    show_error_message("No WiFi adapter found. Please connect via ethernet or USB.");
                }
                this.refresh_spinner.spinning = false;
                return;
            }

            this.refresh_spinner.spinning = true;

            try {
                // Request a scan
                var wireless_proxy = yield new DBusProxy.for_bus(
                    BusType.SYSTEM,
                    DBusProxyFlags.NONE,
                    null,
                    "org.freedesktop.NetworkManager",
                    this.wireless_device_path,
                    "org.freedesktop.NetworkManager.Device.Wireless",
                    null
                );

                // Request scan (fire and forget, may fail if already scanning)
                try {
                    yield wireless_proxy.call(
                        "RequestScan",
                        new Variant("(a{sv})", null),
                        DBusCallFlags.NONE,
                        -1,
                        null
                    );
                    // Wait a bit for scan to complete
                    yield async_sleep(2000);
                } catch (Error scan_error) {
                    print("Scan request note: %s\n", scan_error.message);
                }

                // Get access points
                var ap_result = yield wireless_proxy.call(
                    "GetAccessPoints",
                    null,
                    DBusCallFlags.NONE,
                    -1,
                    null
                );

                var access_points = ap_result.get_child_value(0);
                var networks = new List<WifiNetwork>();

                for (size_t i = 0; i < access_points.n_children(); i++) {
                    var ap_path = (ObjectPath) access_points.get_child_value(i).get_string();
                    
                    var network = yield get_access_point_info(ap_path);
                    if (network != null && network.ssid != null && network.ssid.length > 0) {
                        networks.append(network);
                    }
                }

                // Sort by signal strength (strongest first)
                networks.sort((a, b) => {
                    return (int) b.strength - (int) a.strength;
                });

                // Update UI
                update_wifi_list(networks);

            } catch (Error e) {
                warning("Failed to scan WiFi networks: %s", e.message);
                show_error_message("Failed to scan networks: " + e.message);
            }

            this.refresh_spinner.spinning = false;
        }

        private async WifiNetwork? get_access_point_info(ObjectPath ap_path) {
            try {
                var ap_proxy = yield new DBusProxy.for_bus(
                    BusType.SYSTEM,
                    DBusProxyFlags.NONE,
                    null,
                    "org.freedesktop.NetworkManager",
                    ap_path,
                    "org.freedesktop.DBus.Properties",
                    null
                );

                var network = new WifiNetwork();
                network.ap_path = ap_path;

                // Get SSID
                var ssid_result = yield ap_proxy.call(
                    "Get",
                    new Variant("(ss)", "org.freedesktop.NetworkManager.AccessPoint", "Ssid"),
                    DBusCallFlags.NONE,
                    -1,
                    null
                );
                var ssid_bytes = ssid_result.get_child_value(0).get_variant().get_data_as_bytes();
                network.ssid = sanitize_ssid((string) ssid_bytes.get_data());

                // Get strength
                var strength_result = yield ap_proxy.call(
                    "Get",
                    new Variant("(ss)", "org.freedesktop.NetworkManager.AccessPoint", "Strength"),
                    DBusCallFlags.NONE,
                    -1,
                    null
                );
                network.strength = strength_result.get_child_value(0).get_variant().get_byte();

                // Get flags
                var flags_result = yield ap_proxy.call(
                    "Get",
                    new Variant("(ss)", "org.freedesktop.NetworkManager.AccessPoint", "Flags"),
                    DBusCallFlags.NONE,
                    -1,
                    null
                );
                network.flags = flags_result.get_child_value(0).get_variant().get_uint32();

                // Get WPA flags
                var wpa_result = yield ap_proxy.call(
                    "Get",
                    new Variant("(ss)", "org.freedesktop.NetworkManager.AccessPoint", "WpaFlags"),
                    DBusCallFlags.NONE,
                    -1,
                    null
                );
                network.wpa_flags = wpa_result.get_child_value(0).get_variant().get_uint32();

                // Get RSN flags
                var rsn_result = yield ap_proxy.call(
                    "Get",
                    new Variant("(ss)", "org.freedesktop.NetworkManager.AccessPoint", "RsnFlags"),
                    DBusCallFlags.NONE,
                    -1,
                    null
                );
                network.rsn_flags = rsn_result.get_child_value(0).get_variant().get_uint32();

                return network;

            } catch (Error e) {
                warning("Failed to get AP info: %s", e.message);
                return null;
            }
        }

        private string sanitize_ssid(string ssid) {
            var sanitized = ssid.make_valid();

            if (sanitized.length == 0) {
                return "Hidden Network";
            }

            return sanitized;
        }

        private void update_wifi_list(List<WifiNetwork> networks) {
            // Remove existing rows
            var child = this.wifi_group.get_first_child();
            while (child != null) {
                var next = child.get_next_sibling();
                if (child is Adw.ActionRow) {
                    this.wifi_group.remove(child);
                }
                child = next;
            }

            if (networks.length() == 0) {
                var no_networks_row = new Adw.ActionRow();
                no_networks_row.title = tr("No networks found");
                no_networks_row.subtitle = tr("Try refreshing or check if WiFi is enabled");
                this.wifi_group.add(no_networks_row);
                return;
            }

            // Track seen SSIDs to avoid duplicates
            var seen_ssids = new GenericSet<string>(str_hash, str_equal);

            foreach (var network in networks) {
                // Skip duplicates
                if (network.ssid in seen_ssids) {
                    continue;
                }
                seen_ssids.add(network.ssid);

                var row = new Adw.ActionRow();
                row.title = network.ssid;

                // WiFi icon based on signal strength
                string icon_name;
                if (network.strength >= 75) {
                    icon_name = "network-wireless-signal-excellent-symbolic";
                } else if (network.strength >= 50) {
                    icon_name = "network-wireless-signal-good-symbolic";
                } else if (network.strength >= 25) {
                    icon_name = "network-wireless-signal-ok-symbolic";
                } else {
                    icon_name = "network-wireless-signal-weak-symbolic";
                }

                var icon = new Gtk.Image.from_icon_name(icon_name);
                row.add_prefix(icon);

                // Lock icon for secured networks
                if (network.is_secured) {
                    var lock_icon = new Gtk.Image.from_icon_name("channel-secure-symbolic");
                    lock_icon.add_css_class("dim-label");
                    row.add_suffix(lock_icon);
                }

                // Arrow for navigation
                var arrow = new Gtk.Image.from_icon_name("go-next-symbolic");
                arrow.add_css_class("dim-label");
                row.add_suffix(arrow);

                row.set_activatable(true);

                // Capture network for closure
                var net = network;
                row.activated.connect(() => {
                    on_network_clicked(net);
                });

                this.wifi_group.add(row);
            }
        }

        private void update_wifi_list_no_adapter_but_connected() {
            // Remove existing rows
            var child = this.wifi_group.get_first_child();
            while (child != null) {
                var next = child.get_next_sibling();
                if (child is Adw.ActionRow) {
                    this.wifi_group.remove(child);
                }
                child = next;
            }

            // Show success message - already connected
            var connected_row = new Adw.ActionRow();
            connected_row.title = tr("Already Connected");
            connected_row.subtitle = tr("You're connected to the internet via ethernet or another connection");
            
            var check_icon = new Gtk.Image.from_icon_name("emblem-ok-symbolic");
            check_icon.add_css_class("success");
            connected_row.add_prefix(check_icon);
            
            this.wifi_group.add(connected_row);
        }

        private void on_network_clicked(WifiNetwork network) {
            // Use nmcli to connect - it will trigger the system's password dialog if needed
            connect_with_system_dialog(network);
        }

        private void connect_with_system_dialog(WifiNetwork network) {
            print("Connecting to %s using system dialog...\n", network.ssid);
            
            // Use nmcli to initiate connection - NetworkManager's secret agent 
            // (gnome-shell or nm-applet) will show the system password dialog
            var subprocess = new SubprocessLauncher(SubprocessFlags.STDOUT_SILENCE | SubprocessFlags.STDERR_SILENCE);
            try {
                subprocess.spawnv({
                    "/usr/bin/nmcli",
                    "device",
                    "wifi",
                    "connect",
                    network.ssid
                });
            } catch (Error e) {
                warning("Failed to connect: %s", e.message);
                show_error_message("Failed to connect: " + e.message);
            }
        }

        private void show_error_message(string message) {
            var dialog = new Adw.AlertDialog("Error", message);
            dialog.add_response("ok", "OK");
            dialog.present(this.context.window);
        }

        private async void async_sleep(uint ms) {
            GLib.Timeout.add(ms, () => {
                async_sleep.callback();
                return false;
            });
            yield;
        }

        public void open_network_settings() {
            // Subprocess Launcher
            var subprocess = new SubprocessLauncher(SubprocessFlags.STDOUT_SILENCE);
            try {
                subprocess.spawnv({"/bin/gnome-control-center", "wifi"});
            } catch (Error e) {
                warning("Failed to open network settings: %s", e.message);
            }
        }

        public void check_and_advance() {
            var connectivity = this.monitor.get_connectivity();
            print("Status: %s\n", connectivity.to_string());
            
            // Only advance once to prevent skipping pages
            if (this.has_advanced) {
                return;
            }
            
            // Handle captive portal - open the portal login page
            if (connectivity == GLib.NetworkConnectivity.PORTAL) {
                print("Captive portal detected, opening login page...\n");
                open_captive_portal();
                return;
            }
            
            if (connectivity == GLib.NetworkConnectivity.FULL) {
                this.has_advanced = true;

                if (this.captive_portal_dialog != null) {
                    this.captive_portal_dialog.close();
                    this.captive_portal_dialog = null;
                }
                
                // Hardcode: set window index to network's position + 1, then push
                // This ensures we always go to the page right after network
                int network_index = 0;
                for (int i = 0; i < (int) base.context.window.page_names.length(); i++) {
                    if (base.context.window.page_names.nth_data(i) == "network") {
                        network_index = i;
                        break;
                    }
                }
                base.context.window.current_page_index = network_index + 1;
                base.context.window.push_next_page();
            }
        }

        private void open_captive_portal() {
            if (this.captive_portal_dialog != null) {
                return;
            }

            var dialog = new Adw.Dialog();
            this.captive_portal_dialog = dialog;
            dialog.set_title(tr("Sign In to WiFi"));
            dialog.set_content_width(1100);
            dialog.set_content_height(800);
            dialog.set_can_close(true);

            var toolbar_view = new Adw.ToolbarView();
            toolbar_view.vexpand = true;

            var header_bar = new Adw.HeaderBar();
            header_bar.set_title_widget(new Adw.WindowTitle(tr("Sign In to WiFi"), ""));

            var web_view = new WebKit.WebView();
            var back_button = new Gtk.Button();
            back_button.set_icon_name("go-previous-symbolic");
            back_button.add_css_class("flat");
            back_button.tooltip_text = tr("Back");
            back_button.sensitive = false;
            back_button.clicked.connect(() => {
                if (web_view.can_go_back()) {
                    web_view.go_back();
                }
            });
            header_bar.pack_start(back_button);
            toolbar_view.add_top_bar(header_bar);

            web_view.load_changed.connect(() => {
                back_button.sensitive = web_view.can_go_back();
            });
            web_view.load_uri("http://nmcheck.gnome.org/check_network_status.txt");
            toolbar_view.set_content(web_view);

            dialog.closed.connect(() => {
                if (this.captive_portal_dialog == dialog) {
                    this.captive_portal_dialog = null;
                }
            });
            dialog.set_child(toolbar_view);
            dialog.present(this.context.window);
        }
    }
}
