use std::ffi::OsString;

use adw::prelude::*;
use adw::subclass::prelude::*;
use gtk::{
    self, gio,
    glib::{self, clone},
};
use vte4::prelude::*;

use crate::i18n::gettext;
use crate::widgets::DistroShelfWindow;
use crate::{
    fakers::{CommandRunner, FileSystem, Settings},
    gtk_utils::ColorPalette,
    models::RootStore,
};
use std::cell::OnceCell;

use gtk::glib::{Properties, derived_properties};

mod imp {

    use super::*;

    // Object holding the state
    #[derive(Default, Properties)]
    #[properties(wrapper_type=super::IntegratedTerminal)]
    pub struct IntegratedTerminal {
        #[property(get, set, construct)]
        pub container_name: OnceCell<String>,
        #[property(get, set)]
        pub root_store: OnceCell<RootStore>,
        pub terminal: vte4::Terminal,
        pub reload_button: gtk::Button,
        pub terminal_pid: std::cell::Cell<Option<glib::Pid>>,
    }

    // The central trait for subclassing a GObject
    #[glib::object_subclass]
    impl ObjectSubclass for IntegratedTerminal {
        const NAME: &'static str = "IntegratedTerminal";
        type Type = super::IntegratedTerminal;
        type ParentType = adw::Bin;

        fn new() -> Self {
            Self {
                container_name: OnceCell::new(),
                root_store: OnceCell::new(),
                terminal: vte4::Terminal::new(),
                reload_button: gtk::Button::new(),
                terminal_pid: std::cell::Cell::new(None),
            }
        }
    }

    #[derived_properties]
    impl ObjectImpl for IntegratedTerminal {
        fn constructed(&self) {
            self.parent_constructed();
        }
    }

    impl WidgetImpl for IntegratedTerminal {}
    impl BinImpl for IntegratedTerminal {}
}

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

impl Default for IntegratedTerminal {
    fn default() -> Self {
        let obj: Self = glib::Object::builder()
            .property("container_name", "default")
            .property(
                "root_store",
                RootStore::new(
                    CommandRunner::new_null(),
                    Settings::new_null(),
                    FileSystem::new_null(),
                ),
            )
            .build();
        obj
    }
}

impl IntegratedTerminal {
    pub fn new(container_name: &str, root_store: &RootStore) -> Self {
        let obj: Self = glib::Object::builder()
            .property("container_name", container_name)
            .property("root_store", root_store)
            .build();
        obj.build_ui();
        obj
    }

    fn build_ui(&self) {
        let imp = self.imp();
        let terminal = &imp.terminal;
        let reload_button = &imp.reload_button;

        // Configure terminal appearance
        terminal.set_scroll_on_output(true);
        terminal.set_scroll_on_keystroke(true);

        // Apply the current theme's color palette
        let palette = ColorPalette::current();
        palette.apply_to_terminal(terminal);

        // Create context menu actions
        let action_group = gio::SimpleActionGroup::new();

        let copy_action = gio::SimpleAction::new("copy", None);
        copy_action.connect_activate(clone!(
            #[weak]
            terminal,
            #[weak(rename_to=this)]
            self,
            move |_, _| {
                terminal.copy_clipboard_format(vte4::Format::Text);
                if let Some(win) = this.root().and_downcast::<DistroShelfWindow>() {
                    win.add_toast(adw::Toast::new(&gettext("Text copied")));
                }
            }
        ));
        action_group.add_action(&copy_action);

        let paste_action = gio::SimpleAction::new("paste", None);
        paste_action.connect_activate(clone!(
            #[weak]
            terminal,
            move |_, _| {
                terminal.paste_clipboard();
            }
        ));
        action_group.add_action(&paste_action);

        terminal.insert_action_group("terminal", Some(&action_group));

        // Create context menu
        let menu_model = gio::Menu::new();
        menu_model.append(Some(&gettext("Copy")), Some("terminal.copy"));
        menu_model.append(Some(&gettext("Paste")), Some("terminal.paste"));

        terminal.set_context_menu_model(Some(&menu_model));

        // Create a container for the terminal with a reload button overlay
        let terminal_overlay = gtk::Overlay::new();
        terminal_overlay.set_child(Some(terminal));

        reload_button.set_icon_name("view-refresh-symbolic");
        reload_button.set_tooltip_text(Some(&gettext("Reload Terminal")));
        reload_button.add_css_class("circular");
        reload_button.add_css_class("suggested-action");
        reload_button.set_halign(gtk::Align::Center);
        reload_button.set_valign(gtk::Align::Center);
        reload_button.set_visible(true);
        terminal_overlay.add_overlay(reload_button);

        // Connect to terminal child-exited signal to show reload button
        terminal.connect_child_exited(clone!(
            #[weak(rename_to=this)]
            self,
            move |_, _status| {
                this.imp().terminal_pid.set(None);
                this.imp().reload_button.set_visible(true);
            }
        ));

        terminal.connect_notify_local(
            Some("has-focus"),
            clone!(
                #[weak(rename_to=this)]
                self,
                move |terminal, _| {
                    let root_store = this.root_store();
                    if terminal.has_focus() {
                        root_store.disable_shortcuts();
                    } else {
                        root_store.enable_shortcuts();
                    }
                }
            ),
        );

        // Reload button click handler
        reload_button.connect_clicked(clone!(
            #[weak(rename_to=this)]
            self,
            move |_| {
                this.spawn_terminal();
            }
        ));

        let shortcut_controller = gtk::ShortcutController::new();

        let copy_trigger = gtk::ShortcutTrigger::parse_string("<Control><Shift>c")
            .expect("Invalid copy shortcut trigger");
        shortcut_controller.add_shortcut(gtk::Shortcut::new(
            Some(copy_trigger),
            Some(gtk::NamedAction::new("terminal.copy")),
        ));

        let paste_trigger = gtk::ShortcutTrigger::parse_string("<Control><Shift>v")
            .expect("Invalid paste shortcut trigger");
        shortcut_controller.add_shortcut(gtk::Shortcut::new(
            Some(paste_trigger),
            Some(gtk::NamedAction::new("terminal.paste")),
        ));

        terminal.add_controller(shortcut_controller);

        self.set_child(Some(&terminal_overlay));
    }

    pub fn spawn_terminal(&self) {
        let imp = self.imp();

        if imp.terminal_pid.get().is_some() {
            return;
        }

        imp.reload_button.set_visible(false);
        glib::MainContext::ref_thread_default().spawn_local(clone!(
            #[weak(rename_to=this)]
            self,
            async move {
                let root_store = this.root_store();

                // Prepare the shell command via the Distrobox backend (uses injected factory)
                let name = this.container_name();
                let main = root_store
                    .main_store()
                    .expect("IntegratedTerminal requires Main view");
                let enter_cmd = main.distrobox().enter_cmd(&name);
                let command_runner = root_store.command_runner();
                let shell: Vec<OsString> = command_runner.wrap_command(enter_cmd).to_vec();
                let shell_args = shell
                    .iter()
                    .filter_map(|s: &OsString| s.to_str())
                    .collect::<Vec<_>>();

                // We don't need to resolve the environment from the host, I think `flatpak-spawn --host cmd` will already handle that for the subprocess we are spawning.
                let env_list = std::env::vars()
                    .map(|(key, value)| format!("{key}={value}"))
                    .collect::<Vec<_>>();
                let env_refs = env_list.iter().map(String::as_str).collect::<Vec<_>>();

                let fut = this.imp().terminal.spawn_future(
                    vte4::PtyFlags::DEFAULT,
                    None,
                    &shell_args,
                    &env_refs,
                    glib::SpawnFlags::SEARCH_PATH,
                    || {},
                    10,
                );

                match fut.await {
                    Ok(pid) => {
                        this.imp().terminal_pid.set(Some(pid));
                    }
                    Err(err) => {
                        eprintln!("Failed to spawn terminal: {}", err);
                        this.imp().reload_button.set_visible(true);
                    }
                }
            }
        ));
    }
}
