use std::cell::RefCell;

use adw::prelude::*;
use adw::subclass::prelude::*;
use gettextrs::gettext;
use glib::Properties;
use glib::clone;
use glib::closure;
use gtk::CompositeTemplate;
use gtk::gdk;
use gtk::glib;

use crate::model;
use crate::rt;
use crate::utils;
use crate::view;

const ACTION_TAG: &str = "image-details-page.tag";
const ACTION_INSPECT_IMAGE: &str = "image-details-page.inspect-image";
const ACTION_SHOW_HISTORY: &str = "image-details-page.show-history";
const ACTION_DELETE_IMAGE: &str = "image-details-page.delete-image";

mod imp {
    use super::*;

    #[derive(Debug, Default, Properties, CompositeTemplate)]
    #[properties(wrapper_type = super::ImageDetailsPage)]
    #[template(resource = "/com/github/marhkb/Pods/ui/view/image_details_page.ui")]
    pub(crate) struct ImageDetailsPage {
        pub(super) handler_id: RefCell<Option<glib::SignalHandlerId>>,
        #[property(get, set = Self::set_image, construct, nullable)]
        pub(super) image: glib::WeakRef<model::Image>,

        #[template_child]
        pub(super) stack: TemplateChild<gtk::Stack>,
        #[template_child]
        pub(super) create_tag_row: TemplateChild<gtk::ListBoxRow>,
        #[template_child]
        pub(super) window_title: TemplateChild<adw::WindowTitle>,
        #[template_child]
        pub(super) id_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub(super) created_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub(super) size_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub(super) command_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub(super) entrypoint_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub(super) ports_row: TemplateChild<adw::ActionRow>,
        #[template_child]
        pub(super) repo_tags_list_box: TemplateChild<gtk::ListBox>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for ImageDetailsPage {
        const NAME: &'static str = "PdsImageDetailsPage";
        type Type = super::ImageDetailsPage;
        type ParentType = gtk::Widget;

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

            klass.install_action_async(ACTION_TAG, None, |widget, _, _| async move {
                widget.tag().await;
            });

            klass.install_action_async(ACTION_INSPECT_IMAGE, None, |widget, _, _| async move {
                widget.show_inspection().await;
            });

            klass.install_action_async(ACTION_SHOW_HISTORY, None, |widget, _, _| async move {
                widget.show_history().await;
            });

            klass.install_action_async(ACTION_DELETE_IMAGE, None, |widget, _, _| async move {
                widget.delete_image().await;
            });

            // For displaying a mnemonic.
            klass.add_binding_action(
                gdk::Key::N,
                gdk::ModifierType::CONTROL_MASK,
                view::ContainersGroup::action_create_container(),
            );
            klass.install_action_async(
                view::ContainersGroup::action_create_container(),
                None,
                async move |widget, _, _| {
                    widget.create_container().await;
                },
            );
        }

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

    impl ObjectImpl for ImageDetailsPage {
        fn properties() -> &'static [glib::ParamSpec] {
            Self::derived_properties()
        }

        fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
            self.derived_set_property(id, value, pspec);
        }

        fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
            self.derived_property(id, pspec)
        }

        fn constructed(&self) {
            self.parent_constructed();

            let obj = &*self.obj();

            let image_expr = Self::Type::this_expression("image");
            let image_id_expr = image_expr.chain_property::<model::Image>("id");
            let image_formatted_id_expr =
                image_id_expr.chain_closure::<String>(closure!(|_: Self::Type, id: &str| {
                    utils::format_id(id).to_owned()
                }));
            let image_details_expr = image_expr.chain_property::<model::Image>("details");
            let cmd_expr = image_details_expr.chain_property::<model::ImageDetails>("cmd");
            let entrypoint_expr =
                image_details_expr.chain_property::<model::ImageDetails>("entrypoint");
            let exposed_ports_expr =
                image_details_expr.chain_property::<model::ImageDetails>("exposed-ports");

            image_formatted_id_expr.bind(&*self.window_title, "subtitle", Some(obj));

            image_details_expr
                .chain_closure::<String>(closure!(
                    |_: Self::Type, details: Option<model::ImageDetails>| details
                        .map(|_| "loaded")
                        .unwrap_or("loading")
                ))
                .bind(&*self.stack, "visible-child-name", Some(obj));

            image_expr
                .chain_property::<model::Image>("to-be-deleted")
                .watch(
                    Some(obj),
                    clone!(
                        #[weak]
                        obj,
                        move || {
                            obj.action_set_enabled(
                                ACTION_DELETE_IMAGE,
                                obj.image()
                                    .map(|image| !image.to_be_deleted())
                                    .unwrap_or(false),
                            );
                        }
                    ),
                );

            image_formatted_id_expr.bind(&*self.id_row, "subtitle", Some(obj));

            gtk::ClosureExpression::new::<String>(
                &[
                    Self::Type::this_expression("root")
                        .chain_property::<gtk::Window>("application")
                        .chain_property::<crate::Application>("ticks"),
                    image_expr.chain_property::<model::Image>("created"),
                ],
                closure!(|_: Self::Type, _ticks: u64, created: i64| {
                    utils::format_ago(utils::timespan_now(created))
                }),
            )
            .bind(&*self.created_row, "subtitle", Some(obj));

            gtk::ClosureExpression::new::<String>(
                &[
                    image_expr.chain_property::<model::Image>("size").upcast(),
                    image_details_expr
                        .chain_property::<model::ImageDetails>("shared-size")
                        .upcast(),
                    image_details_expr
                        .chain_property::<model::ImageDetails>("virtual-size")
                        .upcast(),
                ],
                closure!(
                    |_: Self::Type, size: u64, shared_size: u64, virtual_size: u64| {
                        let formatted_size = glib::format_size(size);
                        if size == shared_size {
                            if shared_size == virtual_size {
                                formatted_size.to_string()
                            } else {
                                gettext!(
                                    // Translators: "{}" are placeholders for storage space.
                                    "{} (Virtual: {})",
                                    formatted_size,
                                    glib::format_size(virtual_size),
                                )
                            }
                        } else if size == virtual_size {
                            if shared_size > 0 {
                                gettext!(
                                    // Translators: "{}" are placeholders for storage space.
                                    "{} (Shared: {})",
                                    formatted_size,
                                    glib::format_size(shared_size),
                                )
                            } else {
                                formatted_size.to_string()
                            }
                        } else {
                            gettext!(
                                // Translators: "{}" are placeholders for storage space.
                                "{} (Shared: {}, Virtual: {})",
                                formatted_size,
                                glib::format_size(shared_size),
                                glib::format_size(virtual_size),
                            )
                        }
                    }
                ),
            )
            .bind(&*self.size_row, "subtitle", Some(obj));

            cmd_expr.bind(&*self.command_row, "subtitle", Some(obj));
            cmd_expr
                .chain_closure::<bool>(closure!(|_: Self::Type, cmd: Option<&str>| {
                    cmd.is_some()
                }))
                .bind(&*self.command_row, "visible", Some(obj));

            entrypoint_expr.bind(&*self.entrypoint_row, "subtitle", Some(obj));
            entrypoint_expr
                .chain_closure::<bool>(closure!(|_: Self::Type, entrypoint: Option<&str>| {
                    entrypoint.is_some()
                }))
                .bind(&*self.entrypoint_row, "visible", Some(obj));

            exposed_ports_expr
                .chain_closure::<String>(closure!(
                    |_: Self::Type, exposed_ports: gtk::StringList| {
                        let exposed_ports = exposed_ports
                            .iter::<glib::Object>()
                            .map(|obj| {
                                obj.unwrap()
                                    .downcast::<gtk::StringObject>()
                                    .unwrap()
                                    .string()
                            })
                            .collect::<Vec<_>>();

                        utils::format_iter(exposed_ports.iter().map(glib::GString::as_str), ", ")
                    }
                ))
                .bind(&*self.ports_row, "subtitle", Some(obj));

            exposed_ports_expr
                .chain_closure::<bool>(closure!(|_: Self::Type, exposed_ports: gtk::StringList| {
                    exposed_ports.n_items() > 0
                }))
                .bind(&*self.ports_row, "visible", Some(obj));
        }

        fn dispose(&self) {
            utils::unparent_children(&*self.obj());
        }
    }

    impl WidgetImpl for ImageDetailsPage {}

    impl ImageDetailsPage {
        pub(super) fn set_image(&self, value: Option<&model::Image>) {
            let obj = &*self.obj();
            if obj.image().as_ref() == value {
                return;
            }

            if let Some(image) = obj.image() {
                image.disconnect(self.handler_id.take().unwrap());
            }
            self.repo_tags_list_box.unbind_model();

            if let Some(image) = value {
                if image.details().is_none() {
                    image.inspect_and_update(clone!(
                        #[weak]
                        obj,
                        move |e| {
                            utils::show_error_toast(
                                &obj,
                                &gettext("Error on loading image details"),
                                &e.to_string(),
                            );
                        }
                    ));
                }

                let handler_id = image.connect_deleted(clone!(
                    #[weak]
                    obj,
                    move |image| {
                        utils::show_toast(
                            &obj,
                            gettext!("Image '{}' has been deleted", utils::format_id(&image.id())),
                        );
                        utils::navigation_view(&obj).pop();
                    }
                ));
                self.handler_id.replace(Some(handler_id));

                let model = gtk::SortListModel::new(
                    Some(image.repo_tags()),
                    Some(gtk::StringSorter::new(Some(
                        model::RepoTag::this_expression("full"),
                    ))),
                );
                self.repo_tags_list_box.bind_model(Some(&model), |tag| {
                    let repo_tag = tag.downcast_ref::<model::RepoTag>().unwrap();
                    view::RepoTagRow::from(repo_tag).upcast()
                });
                self.repo_tags_list_box.append(&*self.create_tag_row);
            }

            self.image.set(value);
        }
    }
}

glib::wrapper! {
    pub(crate) struct ImageDetailsPage(ObjectSubclass<imp::ImageDetailsPage>)
        @extends gtk::Widget,
        @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
}

impl From<&model::Image> for ImageDetailsPage {
    fn from(image: &model::Image) -> Self {
        glib::Object::builder().property("image", image).build()
    }
}

impl ImageDetailsPage {
    async fn tag(&self) {
        self.exec_action(async || {
            let Some(api) = self.image().and_then(|image| image.api()) else {
                return;
            };

            let dialog = view::RepoTagAddDialog::default();
            if dialog.clone().choose_future(Some(&utils::root(self))).await != "add" {
                return;
            }
            let repo = dialog.repo();
            let tag = dialog.tag();

            let result = rt::Promise::new(async move { api.tag(repo, tag).await })
                .exec()
                .await;

            if let Err(e) = result {
                utils::show_error_toast(self, &gettext("Error"), &e.to_string());
            }
        })
        .await;
    }

    async fn show_inspection(&self) {
        self.exec_action(async || {
            if let Some(image) = self.image() {
                let weak_ref = glib::WeakRef::new();
                weak_ref.set(Some(&image));

                utils::navigation_view(self).push(
                    &adw::NavigationPage::builder()
                        .child(&view::ScalableTextViewPage::from(view::Entity::Image(
                            weak_ref,
                        )))
                        .build(),
                );
            }
        })
        .await;
    }

    async fn show_history(&self) {
        self.exec_action(async || {
            if let Some(image) = self.image() {
                utils::navigation_view(self).push(
                    &adw::NavigationPage::builder()
                        .child(&view::ImageHistoryPage::from(&image))
                        .build(),
                );
            }
        })
        .await;
    }

    async fn delete_image(&self) {
        self.exec_action(async || {
            view::image::delete_image_show_confirmation(self, self.image());
        })
        .await;
    }

    async fn create_container(&self) {
        self.exec_action(async || {
            view::image::create_container(self, self.image());
        })
        .await;
    }

    async fn exec_action<F: AsyncFn()>(&self, op: F) {
        if utils::navigation_view(self)
            .visible_page()
            .filter(|page| page.child().as_ref() == Some(self.upcast_ref()))
            .is_some()
        {
            op().await;
        }
    }
}
