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

use gio::prelude::FileExt;
use glib::object::ObjectExt;
use glib::subclass::prelude::{ObjectSubclass, *};
use glib::{clone, Properties};
use transmission_client::{Session, SessionMutator};

use crate::{TrClient, TrEncryption};

mod imp {
    use super::*;

    #[derive(Debug, Default, Properties)]
    #[properties(wrapper_type = super::TrSession)]
    pub struct TrSession {
        #[property(get, set, construct_only)]
        pub client: OnceCell<TrClient>,

        #[property(get)]
        pub version: RefCell<String>,
        #[property(get, set = Self::set_download_dir)]
        pub download_dir: RefCell<Option<gio::File>>,
        #[property(get, set = Self::set_start_added_torrents)]
        pub start_added_torrents: Cell<bool>,
        #[property(get, set = Self::set_encryption, builder(Default::default()))]
        pub encryption: Cell<TrEncryption>,
        #[property(get, set = Self::set_incomplete_dir_enabled)]
        pub incomplete_dir_enabled: Cell<bool>,
        #[property(get, set = Self::set_incomplete_dir)]
        pub incomplete_dir: RefCell<Option<gio::File>>,
        #[property(get, set = Self::set_download_queue_enabled)]
        pub download_queue_enabled: Cell<bool>,
        #[property(get, set = Self::set_download_queue_size, minimum = 1, default_value = 1)]
        pub download_queue_size: Cell<i32>,
        #[property(get, set = Self::set_seed_queue_enabled)]
        pub seed_queue_enabled: Cell<bool>,
        #[property(get, set = Self::set_seed_queue_size, minimum = 1, default_value = 1)]
        pub seed_queue_size: Cell<i32>,
        #[property(get, set = Self::set_port_forwarding_enabled)]
        pub port_forwarding_enabled: Cell<bool>,
        #[property(get, set = Self::set_peer_port_random_on_start)]
        pub peer_port_random_on_start: Cell<bool>,
        #[property(get, set = Self::set_peer_port, minimum = 1, default_value = 1)]
        pub peer_port: Cell<i32>,
        #[property(get, set = Self::set_peer_limit_global, minimum = 1, default_value = 1)]
        pub peer_limit_global: Cell<i32>,
        #[property(get, set = Self::set_peer_limit_per_torrent, minimum = 1, default_value = 1)]
        pub peer_limit_per_torrent: Cell<i32>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for TrSession {
        const NAME: &'static str = "TrSession";
        type ParentType = glib::Object;
        type Type = super::TrSession;
    }

    #[glib::derived_properties]
    impl ObjectImpl for TrSession {}

    impl TrSession {
        pub fn set_download_dir(&self, value: gio::File) {
            *self.download_dir.borrow_mut() = Some(value.clone());

            let mutator = SessionMutator {
                download_dir: Some(value.path().unwrap()),
                ..Default::default()
            };
            self.mutate_session(mutator, "download_dir");
        }

        pub fn set_start_added_torrents(&self, value: bool) {
            self.start_added_torrents.set(value);

            let mutator = SessionMutator {
                start_added_torrents: Some(value),
                ..Default::default()
            };
            self.mutate_session(mutator, "start-added-torrents");
        }

        pub fn set_encryption(&self, value: TrEncryption) {
            self.encryption.set(value);

            let mutator = SessionMutator {
                encryption: Some(value.into()),
                ..Default::default()
            };
            self.mutate_session(mutator, "encryption");
        }

        pub fn set_incomplete_dir_enabled(&self, value: bool) {
            self.incomplete_dir_enabled.set(value);

            let mutator = SessionMutator {
                incomplete_dir_enabled: Some(value),
                ..Default::default()
            };
            self.mutate_session(mutator, "incomplete-dir-enabled");
        }

        pub fn set_incomplete_dir(&self, value: gio::File) {
            *self.incomplete_dir.borrow_mut() = Some(value.clone());

            let mutator = SessionMutator {
                incomplete_dir: Some(value.path().unwrap()),
                ..Default::default()
            };
            self.mutate_session(mutator, "incomplete-dir");
        }

        pub fn set_download_queue_enabled(&self, value: bool) {
            self.download_queue_enabled.set(value);

            let mutator = SessionMutator {
                download_queue_enabled: Some(value),
                ..Default::default()
            };
            self.mutate_session(mutator, "download-queue-enabled");
        }

        pub fn set_download_queue_size(&self, value: i32) {
            self.download_queue_size.set(value);

            let mutator = SessionMutator {
                download_queue_size: Some(value),
                ..Default::default()
            };
            self.mutate_session(mutator, "download-queue-size");
        }

        pub fn set_seed_queue_enabled(&self, value: bool) {
            self.seed_queue_enabled.set(value);

            let mutator = SessionMutator {
                seed_queue_enabled: Some(value),
                ..Default::default()
            };
            self.mutate_session(mutator, "seed-queue-enabled");
        }

        pub fn set_seed_queue_size(&self, value: i32) {
            self.seed_queue_size.set(value);

            let mutator = SessionMutator {
                seed_queue_size: Some(value),
                ..Default::default()
            };
            self.mutate_session(mutator, "seed-queue-size");
        }

        pub fn set_port_forwarding_enabled(&self, value: bool) {
            self.port_forwarding_enabled.set(value);

            let mutator = SessionMutator {
                port_forwarding_enabled: Some(value),
                ..Default::default()
            };
            self.mutate_session(mutator, "port-forwarding-enabled");
        }

        pub fn set_peer_port_random_on_start(&self, value: bool) {
            self.peer_port_random_on_start.set(value);

            let mutator = SessionMutator {
                peer_port_random_on_start: Some(value),
                ..Default::default()
            };
            self.mutate_session(mutator, "peer-port-random-on-start");
        }

        pub fn set_peer_port(&self, value: i32) {
            self.peer_port.set(value);

            let mutator = SessionMutator {
                peer_port: Some(value),
                ..Default::default()
            };
            self.mutate_session(mutator, "peer-port");
        }

        pub fn set_peer_limit_global(&self, value: i32) {
            self.peer_limit_global.set(value);

            let mutator = SessionMutator {
                peer_limit_global: Some(value),
                ..Default::default()
            };
            self.mutate_session(mutator, "peer-limit-global");
        }

        pub fn set_peer_limit_per_torrent(&self, value: i32) {
            self.peer_limit_per_torrent.set(value);

            let mutator = SessionMutator {
                peer_limit_per_torrent: Some(value),
                ..Default::default()
            };
            self.mutate_session(mutator, "peer-limit-per-torrent");
        }

        fn mutate_session(&self, mutator: SessionMutator, prop_name: &str) {
            self.obj().notify(prop_name);

            let fut = clone!(@weak self as this => async move {
                if let Some(rpc_client) = this.client.get().unwrap().rpc_client() {
                    rpc_client.session_set(mutator).await.unwrap();
                } else {
                    warn!("Unable set mutate session, no rpc connection.");
                }
            });
            glib::spawn_future_local(fut);
        }
    }
}

glib::wrapper! {
    pub struct TrSession(ObjectSubclass<imp::TrSession>);
}

impl TrSession {
    pub(crate) fn new(client: &TrClient) -> Self {
        glib::Object::builder().property("client", client).build()
    }

    pub(crate) fn refresh_values(&self, rpc_session: Session) {
        let imp = self.imp();

        // version
        if *imp.version.borrow() != rpc_session.version {
            *imp.version.borrow_mut() = rpc_session.version;
            self.notify_version();
        }

        // download_dir
        let download_dir = gio::File::for_path(rpc_session.download_dir);
        if download_dir.path() != imp.download_dir.borrow().as_ref().and_then(|f| f.path()) {
            *imp.download_dir.borrow_mut() = Some(download_dir);
            self.notify_download_dir();
        }

        // start_added_torrents
        if imp.start_added_torrents.get() != rpc_session.start_added_torrents {
            imp.start_added_torrents
                .set(rpc_session.start_added_torrents);
            self.notify_start_added_torrents();
        }

        // encryption
        if imp.encryption.get() != rpc_session.encryption.clone().into() {
            imp.encryption.set(rpc_session.encryption.into());
            self.notify_encryption();
        }

        // incomplete_dir_enabled
        if imp.incomplete_dir_enabled.get() != rpc_session.incomplete_dir_enabled {
            imp.incomplete_dir_enabled
                .set(rpc_session.incomplete_dir_enabled);
            self.notify_incomplete_dir_enabled();
        }

        // incomplete_dir
        let incomplete_dir = gio::File::for_path(rpc_session.incomplete_dir);
        if incomplete_dir.path() != imp.incomplete_dir.borrow().as_ref().and_then(|f| f.path()) {
            *imp.incomplete_dir.borrow_mut() = Some(incomplete_dir);
            self.notify_incomplete_dir();
        }

        // download_queue_enabled
        if imp.download_queue_enabled.get() != rpc_session.download_queue_enabled {
            imp.download_queue_enabled
                .set(rpc_session.download_queue_enabled);
            self.notify_download_queue_enabled();
        }

        // download_queue_size
        if imp.download_queue_size.get() != rpc_session.download_queue_size {
            imp.download_queue_size.set(rpc_session.download_queue_size);
            self.notify_download_queue_size();
        }

        // seed_queue_enabled
        if imp.seed_queue_enabled.get() != rpc_session.seed_queue_enabled {
            imp.seed_queue_enabled.set(rpc_session.seed_queue_enabled);
            self.notify_seed_queue_enabled();
        }

        // seed_queue_size
        if imp.seed_queue_size.get() != rpc_session.seed_queue_size {
            imp.seed_queue_size.set(rpc_session.seed_queue_size);
            self.notify_seed_queue_size();
        }

        // port_forwarding_enabled
        if imp.port_forwarding_enabled.get() != rpc_session.port_forwarding_enabled {
            imp.port_forwarding_enabled
                .set(rpc_session.port_forwarding_enabled);
            self.notify_port_forwarding_enabled();
        }

        // peer_port_random_on_start
        if imp.peer_port_random_on_start.get() != rpc_session.peer_port_random_on_start {
            imp.peer_port_random_on_start
                .set(rpc_session.peer_port_random_on_start);
            self.notify_peer_port_random_on_start();
        }

        // peer_port
        if imp.peer_port.get() != rpc_session.peer_port {
            imp.peer_port.set(rpc_session.peer_port);
            self.notify_peer_port();
        }

        // peer_limit_global
        if imp.peer_limit_global.get() != rpc_session.peer_limit_global {
            imp.peer_limit_global.set(rpc_session.peer_limit_global);
            self.notify_peer_limit_global();
        }

        // peer_limit_per_torrent
        if imp.peer_limit_per_torrent.get() != rpc_session.peer_limit_per_torrent {
            imp.peer_limit_per_torrent
                .set(rpc_session.peer_limit_per_torrent);
            self.notify_peer_limit_per_torrent();
        }
    }
}
