use super::*;
use anyhow::{Context, Result};
use tokio::time::Duration;

pub struct NetworksScreen {
    list_box: gtk::ListBox,
    network_status: gtk::Label,
    wireguard_text: gtk::TextView,
    tailnet_email_entry: gtk::Entry,
    tailnet_authority_entry: gtk::Entry,
    tailnet_account_entry: gtk::Entry,
    tailnet_identity_entry: gtk::Entry,
    tailnet_hostname_entry: gtk::Entry,
    tailnet_name_entry: gtk::Entry,
    custom_authority_switch: gtk::Switch,
    tailnet_status: gtk::Label,
    tailnet_session_id: Option<String>,
    tailnet_running: bool,
}

#[derive(Debug)]
pub enum NetworksScreenMsg {
    Refresh,
    AddWireGuard,
    DiscoverTailnet,
    ProbeTailnet,
    StartTailnetLogin,
    PollTailnetLogin,
    CancelTailnetLogin,
    AddTailnet,
}

#[relm4::component(pub, async)]
impl AsyncComponent for NetworksScreen {
    type Init = ();
    type Input = NetworksScreenMsg;
    type Output = ();
    type CommandOutput = ();

    view! {
        gtk::ScrolledWindow {
            set_vexpand: true,

            adw::Clamp {
                gtk::Box {
                    set_orientation: gtk::Orientation::Vertical,
                    set_spacing: 12,
                    set_margin_all: 12,

            adw::PreferencesGroup {
                set_title: "Stored Networks",
                set_description: Some("Networks saved in the Burrow daemon"),

                #[name(network_status)]
                gtk::Label {
                    set_xalign: 0.0,
                    set_wrap: true,
                    set_label: "Loading networks",
                },

                #[name(list_box)]
                gtk::ListBox {
                    add_css_class: "boxed-list",
                },

                gtk::Button {
                    set_label: "Refresh",
                    connect_clicked => NetworksScreenMsg::Refresh
                },
            },

            adw::PreferencesGroup {
                set_title: "Add WireGuard",
                set_description: Some("Import a WireGuard configuration into the daemon"),

                gtk::ScrolledWindow {
                    set_min_content_height: 150,
                    set_vexpand: false,

                    #[name(wireguard_text)]
                    gtk::TextView {
                        set_monospace: true,
                        set_wrap_mode: gtk::WrapMode::WordChar,
                    }
                },

                gtk::Button {
                    set_label: "Add WireGuard Network",
                    connect_clicked => NetworksScreenMsg::AddWireGuard
                },
            },

            adw::PreferencesGroup {
                set_title: "Tailnet",
                set_description: Some("Discover, probe, sign in, and save Tailnet authorities through the daemon"),

                #[name(tailnet_email_entry)]
                gtk::Entry {
                    set_placeholder_text: Some("Email address"),
                },

                #[name(custom_authority_switch)]
                gtk::Switch {
                    set_halign: Align::Start,
                    set_active: false,
                },

                #[name(tailnet_authority_entry)]
                gtk::Entry {
                    set_placeholder_text: Some("Tailnet server URL"),
                },

                #[name(tailnet_account_entry)]
                gtk::Entry {
                    set_placeholder_text: Some("Account name"),
                },

                #[name(tailnet_identity_entry)]
                gtk::Entry {
                    set_placeholder_text: Some("Identity name"),
                },

                #[name(tailnet_hostname_entry)]
                gtk::Entry {
                    set_placeholder_text: Some("Device hostname"),
                },

                #[name(tailnet_name_entry)]
                gtk::Entry {
                    set_placeholder_text: Some("Tailnet name"),
                },

                #[name(tailnet_status)]
                gtk::Label {
                    set_xalign: 0.0,
                    set_wrap: true,
                    set_selectable: true,
                    set_label: "Use discovery for the managed Tailscale authority or enter a custom Tailnet server.",
                },

                gtk::Box {
                    set_orientation: gtk::Orientation::Horizontal,
                    set_spacing: 8,

                    gtk::Button {
                        set_label: "Discover",
                        connect_clicked => NetworksScreenMsg::DiscoverTailnet
                    },

                    gtk::Button {
                        set_label: "Probe",
                        connect_clicked => NetworksScreenMsg::ProbeTailnet
                    },

                    gtk::Button {
                        set_label: "Sign In",
                        connect_clicked => NetworksScreenMsg::StartTailnetLogin
                    },

                    gtk::Button {
                        set_label: "Cancel",
                        connect_clicked => NetworksScreenMsg::CancelTailnetLogin
                    },
                },

                gtk::Button {
                    set_label: "Save Tailnet Network",
                    connect_clicked => NetworksScreenMsg::AddTailnet
                },
            },
                }
            }
        }
    }

    async fn init(
        _: Self::Init,
        root: Self::Root,
        sender: AsyncComponentSender<Self>,
    ) -> AsyncComponentParts<Self> {
        let widgets = view_output!();
        widgets
            .tailnet_authority_entry
            .set_text(daemon_api::default_tailnet_authority());
        widgets.tailnet_account_entry.set_text("default");
        widgets.tailnet_identity_entry.set_text("linux");

        let mut model = NetworksScreen {
            list_box: widgets.list_box.clone(),
            network_status: widgets.network_status.clone(),
            wireguard_text: widgets.wireguard_text.clone(),
            tailnet_email_entry: widgets.tailnet_email_entry.clone(),
            tailnet_authority_entry: widgets.tailnet_authority_entry.clone(),
            tailnet_account_entry: widgets.tailnet_account_entry.clone(),
            tailnet_identity_entry: widgets.tailnet_identity_entry.clone(),
            tailnet_hostname_entry: widgets.tailnet_hostname_entry.clone(),
            tailnet_name_entry: widgets.tailnet_name_entry.clone(),
            custom_authority_switch: widgets.custom_authority_switch.clone(),
            tailnet_status: widgets.tailnet_status.clone(),
            tailnet_session_id: None,
            tailnet_running: false,
        };

        model.refresh_networks().await;

        AsyncComponentParts { model, widgets }
    }

    async fn update(
        &mut self,
        msg: Self::Input,
        sender: AsyncComponentSender<Self>,
        _root: &Self::Root,
    ) {
        match msg {
            NetworksScreenMsg::Refresh => self.refresh_networks().await,
            NetworksScreenMsg::AddWireGuard => self.add_wireguard().await,
            NetworksScreenMsg::DiscoverTailnet => self.discover_tailnet().await,
            NetworksScreenMsg::ProbeTailnet => self.probe_tailnet().await,
            NetworksScreenMsg::StartTailnetLogin => self.start_tailnet_login(sender).await,
            NetworksScreenMsg::PollTailnetLogin => self.poll_tailnet_login(sender).await,
            NetworksScreenMsg::CancelTailnetLogin => self.cancel_tailnet_login().await,
            NetworksScreenMsg::AddTailnet => self.add_tailnet().await,
        }
    }
}

impl NetworksScreen {
    async fn refresh_networks(&mut self) {
        match daemon_api::list_networks().await {
            Ok(networks) => {
                self.render_networks(&networks);
                self.network_status.set_label(if networks.is_empty() {
                    "No daemon networks saved yet"
                } else {
                    "Daemon networks are up to date"
                });
            }
            Err(error) => {
                self.clear_networks();
                self.network_status
                    .set_label(&format!("Unable to read daemon networks: {error}"));
            }
        }
    }

    async fn add_wireguard(&mut self) {
        let config = text_view_text(&self.wireguard_text);
        if config.trim().is_empty() {
            self.network_status
                .set_label("Paste a WireGuard configuration before adding a network");
            return;
        }

        match daemon_api::add_wireguard(config).await {
            Ok(id) => {
                self.network_status
                    .set_label(&format!("Added WireGuard network #{id}"));
                self.wireguard_text.buffer().set_text("");
                self.refresh_networks().await;
            }
            Err(error) => self
                .network_status
                .set_label(&format!("Unable to add WireGuard network: {error}")),
        }
    }

    async fn discover_tailnet(&mut self) {
        let email = self.tailnet_email_entry.text().to_string();
        let Ok(email) = daemon_api::require_value(&email, "Email address") else {
            self.tailnet_status
                .set_label("Enter an email address before discovery");
            return;
        };

        self.tailnet_status.set_label("Discovering Tailnet server");
        match daemon_api::discover_tailnet(email).await {
            Ok(discovery) => {
                self.tailnet_authority_entry.set_text(&discovery.authority);
                self.custom_authority_switch.set_active(!discovery.managed);
                let issuer = discovery
                    .oidc_issuer
                    .map(|issuer| format!(" OIDC issuer: {issuer}."))
                    .unwrap_or_default();
                let kind = if discovery.managed {
                    "managed authority"
                } else {
                    "custom authority"
                };
                self.tailnet_status.set_label(&format!(
                    "Discovered {kind}: {}.{issuer}",
                    discovery.authority
                ));
            }
            Err(error) => self
                .tailnet_status
                .set_label(&format!("Tailnet discovery failed: {error}")),
        }
    }

    async fn probe_tailnet(&mut self) {
        let authority = self.tailnet_authority();
        let Ok(authority) = daemon_api::require_value(&authority, "Tailnet server URL") else {
            self.tailnet_status
                .set_label("Enter a Tailnet server URL before probing");
            return;
        };

        self.tailnet_status.set_label("Checking Tailnet server");
        match daemon_api::probe_tailnet(authority).await {
            Ok(probe) => {
                let detail = probe
                    .detail
                    .unwrap_or_else(|| format!("HTTP {}", probe.status_code));
                self.tailnet_status
                    .set_label(&format!("{}: {detail}", probe.summary));
            }
            Err(error) => self
                .tailnet_status
                .set_label(&format!("Tailnet probe failed: {error}")),
        }
    }

    async fn start_tailnet_login(&mut self, sender: AsyncComponentSender<Self>) {
        let authority = self.tailnet_authority();
        let Ok(authority) = daemon_api::require_value(&authority, "Tailnet server URL") else {
            self.tailnet_status
                .set_label("Enter a Tailnet server URL before sign-in");
            return;
        };
        let account = daemon_api::normalized(&self.tailnet_account_entry.text(), "default");
        let identity = daemon_api::normalized(&self.tailnet_identity_entry.text(), "linux");
        let hostname = daemon_api::normalized_optional(&self.tailnet_hostname_entry.text());

        self.tailnet_status.set_label("Starting Tailnet sign-in");
        match daemon_api::start_tailnet_login(authority, account, identity, hostname).await {
            Ok(status) => {
                self.apply_login_status(&status);
                if let Some(auth_url) = status.auth_url.as_deref() {
                    if let Err(error) = open_auth_url(auth_url) {
                        self.tailnet_status.set_label(&format!(
                            "{} Open this URL manually: {auth_url}. Browser launch failed: {error}",
                            self.tailnet_status.text()
                        ));
                    }
                }
                if !status.running {
                    sender.input(NetworksScreenMsg::PollTailnetLogin);
                }
            }
            Err(error) => self
                .tailnet_status
                .set_label(&format!("Tailnet sign-in failed: {error}")),
        }
    }

    async fn poll_tailnet_login(&mut self, sender: AsyncComponentSender<Self>) {
        let Some(session_id) = self.tailnet_session_id.clone() else {
            return;
        };
        if self.tailnet_running {
            return;
        }

        tokio::time::sleep(Duration::from_secs(1)).await;
        match daemon_api::tailnet_login_status(session_id).await {
            Ok(status) => {
                self.apply_login_status(&status);
                if !status.running {
                    sender.input(NetworksScreenMsg::PollTailnetLogin);
                }
            }
            Err(error) => {
                self.tailnet_status
                    .set_label(&format!("Tailnet sign-in status failed: {error}"));
                self.tailnet_session_id = None;
            }
        }
    }

    async fn cancel_tailnet_login(&mut self) {
        let Some(session_id) = self.tailnet_session_id.clone() else {
            self.tailnet_status
                .set_label("No Tailnet sign-in is active");
            return;
        };

        match daemon_api::cancel_tailnet_login(session_id).await {
            Ok(()) => {
                self.tailnet_session_id = None;
                self.tailnet_running = false;
                self.tailnet_status.set_label("Tailnet sign-in cancelled");
            }
            Err(error) => self
                .tailnet_status
                .set_label(&format!("Unable to cancel Tailnet sign-in: {error}")),
        }
    }

    async fn add_tailnet(&mut self) {
        let authority = self.tailnet_authority();
        let Ok(authority) = daemon_api::require_value(&authority, "Tailnet server URL") else {
            self.tailnet_status
                .set_label("Enter a Tailnet server URL before saving");
            return;
        };
        if self.tailnet_session_id.is_some() && !self.tailnet_running {
            self.tailnet_status
                .set_label("Finish browser sign-in before saving this Tailnet network");
            return;
        }

        let account = daemon_api::normalized(&self.tailnet_account_entry.text(), "default");
        let identity = daemon_api::normalized(&self.tailnet_identity_entry.text(), "linux");
        let hostname = daemon_api::normalized_optional(&self.tailnet_hostname_entry.text());
        let tailnet = daemon_api::normalized_optional(&self.tailnet_name_entry.text());

        match daemon_api::add_tailnet(authority, account, identity, hostname, tailnet).await {
            Ok(id) => {
                self.tailnet_status
                    .set_label(&format!("Saved Tailnet network #{id}"));
                self.refresh_networks().await;
            }
            Err(error) => self
                .tailnet_status
                .set_label(&format!("Unable to save Tailnet network: {error}")),
        }
    }

    fn apply_login_status(&mut self, status: &daemon_api::TailnetLoginStatus) {
        self.tailnet_session_id = Some(status.session_id.clone());
        self.tailnet_running = status.running;

        let mut parts = Vec::new();
        if status.running {
            parts.push("Signed in".to_owned());
        } else if status.needs_login {
            parts.push("Browser sign-in required".to_owned());
        } else {
            parts.push("Checking sign-in".to_owned());
        }
        if !status.backend_state.is_empty() {
            parts.push(format!("State: {}", status.backend_state));
        }
        if let Some(tailnet_name) = &status.tailnet_name {
            parts.push(format!("Tailnet: {tailnet_name}"));
        }
        if let Some(self_dns_name) = &status.self_dns_name {
            parts.push(self_dns_name.clone());
        }
        if !status.tailnet_ips.is_empty() {
            parts.push(status.tailnet_ips.join(", "));
        }
        if !status.health.is_empty() {
            parts.push(status.health.join(" / "));
        }
        if let Some(auth_url) = &status.auth_url {
            parts.push(format!("Auth URL: {auth_url}"));
        }

        self.tailnet_status.set_label(&parts.join("\n"));
    }

    fn tailnet_authority(&self) -> String {
        if self.custom_authority_switch.is_active() {
            self.tailnet_authority_entry.text().to_string()
        } else {
            let authority = self.tailnet_authority_entry.text();
            if authority.trim().is_empty() {
                daemon_api::default_tailnet_authority().to_owned()
            } else {
                authority.to_string()
            }
        }
    }

    fn clear_networks(&self) {
        while let Some(child) = self.list_box.first_child() {
            self.list_box.remove(&child);
        }
    }

    fn render_networks(&self, networks: &[daemon_api::NetworkSummary]) {
        self.clear_networks();
        if networks.is_empty() {
            let row = adw::ActionRow::builder()
                .title("No networks")
                .subtitle("Save a WireGuard or Tailnet network to use it here.")
                .build();
            self.list_box.append(&row);
            return;
        }

        for network in networks {
            let title = format!("{} (#{})", network.title, network.id);
            let row = adw::ActionRow::builder()
                .title(&title)
                .subtitle(&network.detail)
                .build();
            self.list_box.append(&row);
        }
    }
}

fn text_view_text(text_view: &gtk::TextView) -> String {
    let buffer = text_view.buffer();
    buffer
        .text(&buffer.start_iter(), &buffer.end_iter(), true)
        .to_string()
}

fn open_auth_url(url: &str) -> Result<()> {
    gtk::gio::AppInfo::launch_default_for_uri(url, None::<&gtk::gio::AppLaunchContext>)
        .with_context(|| format!("failed to open {url}"))
}
