use std::collections::HashMap;

use tauri_winrt_notification::Toast;
use tokio::sync::broadcast::{Receiver, Sender};
use tray_icon::menu::{MenuId, MenuItem};
use winit::{
    application::ApplicationHandler,
    event::StartCause,
    event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
};

use crate::{config::Config, icon::TrayIcon, oauth, state_machine::Event, BASE_URL};

pub async fn start(
    config: Config,
    sender: Sender<Event>,
    receiver: Receiver<Event>,
) -> Result<(), anyhow::Error> {
    let mut app = App::new(config, sender, receiver)?;
    let event_loop = EventLoop::new()?;

    event_loop.run_app(&mut app)?;
    tracing::info!("EventLoop Shutdown");

    Ok(())
}

pub struct App {
    config: Config,
    tray_icon: TrayIcon,
    sender: Sender<Event>,
    receiver: Receiver<Event>,
    items: HashMap<&'static str, MenuId>,
}

impl App {
    pub fn new(
        config: Config,
        sender: Sender<Event>,
        receiver: Receiver<Event>,
    ) -> Result<Self, anyhow::Error> {
        let mut tray_icon = TrayIcon::new(load_icon(), crate::PROJECT_NAME);

        let login = MenuItem::new("Login", true, None);
        let forget = MenuItem::new("Logout", false, None);
        let quit = MenuItem::new("Exit", true, None);

        let mut items = HashMap::new();

        let c = config.clone();
        let login_id = tray_icon.add_menu_item(&login, move |item| {
            // ..
            let item = item.as_menuitem().unwrap();
            if item.text() != "Login" {
                open::that(BASE_URL)?;
                return Ok(());
            }

            oauth::open_browser(c.clone())?;

            Ok(())
        })?;

        let c = config.clone();
        let s = sender.clone();
        let forget_id = tray_icon.add_menu_item(&forget, move |_| {
            c.set_token(None)?;
            c.set_open_browser(false)?;

            let _ = s.send(Event::Logout);

            Ok(())
        })?;

        tray_icon.add_seperator()?;

        let s = sender.clone();
        tray_icon.add_menu_item(&quit, move |_| {
            s.send(Event::Quit)?;
            Ok(())
        })?;

        items.insert("login", login_id);
        items.insert("forget", forget_id);

        Ok(Self {
            config,
            tray_icon,
            sender,
            receiver,
            items,
        })
    }
}

impl ApplicationHandler for App {
    fn new_events(&mut self, event_loop: &ActiveEventLoop, _: StartCause) {
        event_loop.set_control_flow(ControlFlow::WaitUntil(
            std::time::Instant::now() + std::time::Duration::from_millis(16),
        ));

        if let Ok(event) = tray_icon::menu::MenuEvent::receiver().try_recv() {
            self.tray_icon.handle(event.id());
        }

        if let Ok(event) = self.receiver.try_recv() {
            match event {
                Event::Logout | Event::Ready => {
                    self.tray_icon
                        .set_text(self.items.get("login").unwrap(), "Login")
                        .unwrap();

                    self.tray_icon
                        .set_enabled(self.items.get("forget").unwrap(), false)
                        .unwrap();
                }
                Event::TokenReceived { .. } | Event::Connected => {
                    self.tray_icon
                        .set_text(self.items.get("login").unwrap(), "Open Avam")
                        .unwrap();

                    self.tray_icon
                        .set_enabled(self.items.get("forget").unwrap(), true)
                        .unwrap();
                }
                Event::Quit => {
                    tracing::info!("Shutting down EventLoop");
                    event_loop.exit()
                }
                _ => {}
            }
        }
    }

    fn resumed(&mut self, _: &winit::event_loop::ActiveEventLoop) {
        let _ = self.tray_icon.build();

        let _ = self.sender.send(Event::Ready);

        if !self.config.toast_shown() {
            let _ = Toast::new(crate::AVAM_APP_ID)
                .title("Avam is running in your system tray!")
                .show();
        }

        let _ = self.config.set_toast_shown(true);
    }

    fn window_event(
        &mut self,
        _: &winit::event_loop::ActiveEventLoop,
        _: winit::window::WindowId,
        _: winit::event::WindowEvent,
    ) {
        // We don't have a window
    }
}

fn load_icon() -> tray_icon::Icon {
    let icon = include_bytes!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/../public/favicon-32x32.png"
    ));
    let (icon_rgba, icon_width, icon_height) = {
        let image = image::load_from_memory(icon)
            .expect("Failed to open icon path")
            .into_rgba8();
        let (width, height) = image.dimensions();
        let rgba = image.into_raw();
        (rgba, width, height)
    };
    tray_icon::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
}
