// Fragments - add_connection_dialog.rs
// Copyright (C) 2022-2023  Felix Häcker <haeckerfelix@gnome.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use std::cell::{Cell, RefCell};

use adw::prelude::*;
use adw::subclass::prelude::*;
use glib::{clone, subclass};
use gtk::{glib, CompositeTemplate};
use transmission_gobject::{ClientError, TrClient};

use crate::app::FrgApplication;
use crate::backend::{FrgConnection, FrgConnectionManager};
use crate::i18n::i18n_f;

mod imp {
    use super::*;

    #[derive(Debug, Default, CompositeTemplate)]
    #[template(resource = "/de/haeckerfelix/Fragments/gtk/add_connection_dialog.ui")]
    pub struct FrgAddConnectionDialog {
        #[template_child]
        stack: TemplateChild<gtk::Stack>,
        #[template_child]
        title_row: TemplateChild<adw::EntryRow>,
        #[template_child]
        host_row: TemplateChild<adw::EntryRow>,
        #[template_child]
        port_spinbutton: TemplateChild<adw::SpinRow>,
        #[template_child]
        path_row: TemplateChild<adw::EntryRow>,
        #[template_child]
        ssl_switch: TemplateChild<adw::SwitchRow>,
        #[template_child]
        connect_button: TemplateChild<gtk::Button>,
        #[template_child]
        error_label: TemplateChild<gtk::Label>,
        #[template_child]
        spinner: TemplateChild<gtk::Spinner>,

        title_ok: Cell<bool>,
        address_ok: Cell<bool>,
        address: RefCell<String>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for FrgAddConnectionDialog {
        const NAME: &'static str = "FrgAddConnectionDialog";
        type ParentType = adw::Dialog;
        type Type = super::FrgAddConnectionDialog;

        fn class_init(klass: &mut Self::Class) {
            klass.bind_template();
            klass.bind_template_callbacks();
        }

        fn instance_init(obj: &subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }

    impl ObjectImpl for FrgAddConnectionDialog {}

    impl WidgetImpl for FrgAddConnectionDialog {}

    impl AdwDialogImpl for FrgAddConnectionDialog {}

    #[gtk::template_callbacks]
    impl FrgAddConnectionDialog {
        fn add_connection(&self, connection: FrgConnection) {
            let cm = FrgConnectionManager::default();
            let app = FrgApplication::default();

            cm.connections().append(&connection);
            app.activate_action("set-connection", Some(&connection.uuid().to_variant()));
            self.obj().close();
        }

        #[template_callback]
        fn connect_button_clicked(&self) {
            let fut = clone!(@weak self as this => async move {
                let title = this.title_row.text();
                let address = this.address.borrow().clone();

                let connection = FrgConnection::new(&title, &address);

                this.stack.set_visible_child_name("loading");
                this.spinner.set_spinning(true);
                this.connect_button.set_sensitive(false);

                let res = TrClient::test_connectivity(connection.address(), None).await;
                match res {
                    Ok(_) => this.add_connection(connection),
                    Err(ref err) => {
                        // Skip "Unauthorized" errors since we can handle those
                        if matches!(err, ClientError::TransmissionUnauthorized) {
                            this.add_connection(connection);
                        } else {
                            let msg = i18n_f("Could not connect with “{}”:\n{}", &[&this.address.borrow(), &err.to_string()]);

                            this.error_label.set_visible(true);
                            this.error_label.set_text(&msg);

                            this.stack.set_visible_child_name("input");
                            this.spinner.set_spinning(false);
                            this.connect_button.set_sensitive(true);
                        }
                    }
                }
            });
            glib::spawn_future_local(fut);
        }

        #[template_callback]
        fn validate_title(&self) {
            if self.title_row.text().is_empty() {
                self.title_row.add_css_class("error");
                self.title_ok.set(false);
            } else {
                self.title_row.remove_css_class("error");
                self.title_ok.set(true);
            }

            self.update_connect_button();
        }

        #[template_callback]
        fn validate_address(&self) {
            let host = self.host_row.text();
            let port = self.port_spinbutton.value();
            let path = self.path_row.text();
            let is_ssl = self.ssl_switch.is_active();

            let address = if is_ssl {
                format!("https://{host}:{port}{path}")
            } else {
                format!("http://{host}:{port}{path}")
            };
            *self.address.borrow_mut() = address.to_string();

            let valid_address = url::Url::parse(&address).is_ok();

            if !self.host_row.text().is_empty() && valid_address {
                self.host_row.remove_css_class("error");
                self.address_ok.set(true);
            } else {
                // Don't recolor entry if it is empty
                if !self.host_row.text().is_empty() {
                    self.host_row.add_css_class("error");
                } else {
                    self.host_row.remove_css_class("error");
                }

                self.address_ok.set(false);
            }

            self.update_connect_button();
        }

        fn update_connect_button(&self) {
            let sensitive = self.title_ok.get() && self.address_ok.get();
            self.connect_button.set_sensitive(sensitive);
        }
    }
}

glib::wrapper! {
    pub struct FrgAddConnectionDialog(ObjectSubclass<imp::FrgAddConnectionDialog>)
        @extends gtk::Widget, adw::Dialog;
}

impl FrgAddConnectionDialog {
    pub fn new() -> Self {
        Self::default()
    }
}

impl Default for FrgAddConnectionDialog {
    fn default() -> Self {
        glib::Object::new()
    }
}
