use std::cell::RefCell;
use std::fs;
use std::io::Write;
use std::rc::Rc;
use std::time::{SystemTime, UNIX_EPOCH};

use wxdragon::prelude::*;

use crate::localization::Localizer;
use crate::review_ui::ReviewUi;
use crate::{AppState, trace_debug_event};

const RESIDENT_TRAY_MINIMIZE_POLL_MS: i32 = 250;
pub(crate) const TRAY_MENU_RESTORE_ID: i32 = 32_001;
pub(crate) const TRAY_MENU_QUIT_ID: i32 = 32_002;
const RESIDENT_TRAY_TRACE_ENV: &str = "COSIMO_TRAY_TRACE";

pub(crate) struct ResidentTray {
    taskbar: Rc<TaskBarIcon>,
    icon: Bitmap,
    _menu: Menu,
    visible: bool,
}

pub(crate) fn install_resident_tray_handlers(
    ui: &ReviewUi,
    state: Rc<RefCell<AppState>>,
    resident_tray: Rc<RefCell<Option<ResidentTray>>>,
) {
    let ui_copy = *ui;
    let state_copy = Rc::clone(&state);
    let resident_tray_copy = Rc::clone(&resident_tray);
    ui.frame.on_size(move |event| {
        if should_minimize_to_resident_tray(&ui_copy, &state_copy, &resident_tray_copy) {
            event.skip(!minimize_to_resident_tray(
                &ui_copy,
                &state_copy,
                &resident_tray_copy,
            ));
        } else {
            event.skip(true);
        }
    });
}

pub(crate) fn install_resident_tray_minimize_timer(
    ui: &ReviewUi,
    state: Rc<RefCell<AppState>>,
    resident_tray: Rc<RefCell<Option<ResidentTray>>>,
) -> Rc<RefCell<Option<Timer<Frame>>>> {
    let timer_store = Rc::new(RefCell::new(None));
    let timer = Timer::new(&ui.frame);
    let ui_copy = *ui;
    timer.on_tick(move |_| {
        if should_minimize_to_resident_tray(&ui_copy, &state, &resident_tray) {
            minimize_to_resident_tray(&ui_copy, &state, &resident_tray);
        }
    });
    timer.start(RESIDENT_TRAY_MINIMIZE_POLL_MS, false);
    timer_store.borrow_mut().replace(timer);
    timer_store
}

fn should_minimize_to_resident_tray(
    ui: &ReviewUi,
    state: &Rc<RefCell<AppState>>,
    resident_tray: &Rc<RefCell<Option<ResidentTray>>>,
) -> bool {
    should_enter_resident_tray(
        state.borrow().settings.minimize_to_tray,
        ui.frame.is_iconized(),
        resident_tray_visible(resident_tray),
    )
}

fn should_enter_resident_tray(
    minimize_to_tray: bool,
    frame_iconized: bool,
    resident_tray_visible: bool,
) -> bool {
    minimize_to_tray && frame_iconized && !resident_tray_visible
}

pub(crate) fn resident_tray_visible(resident_tray: &Rc<RefCell<Option<ResidentTray>>>) -> bool {
    resident_tray
        .borrow()
        .as_ref()
        .is_some_and(|tray| tray.visible)
}

pub(crate) fn trace_resident_tray_event(event: &str) {
    trace_debug_event(event);
    let Ok(path) = std::env::var(RESIDENT_TRAY_TRACE_ENV) else {
        return;
    };
    if path.trim().is_empty() {
        return;
    }

    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| duration.as_secs())
        .unwrap_or(0);
    if let Ok(mut file) = fs::OpenOptions::new().create(true).append(true).open(path) {
        let _ = writeln!(file, "{timestamp}\t{event}");
    }
}

fn minimize_to_resident_tray(
    ui: &ReviewUi,
    state: &Rc<RefCell<AppState>>,
    resident_tray: &Rc<RefCell<Option<ResidentTray>>>,
) -> bool {
    trace_resident_tray_event("minimize_to_resident_tray: requested");
    let localizer = state.borrow().localizer;
    {
        let mut resident_tray_ref = resident_tray.borrow_mut();
        if let Some(tray) = resident_tray_ref.as_mut() {
            if tray.visible {
                trace_resident_tray_event("minimize_to_resident_tray: tray already visible");
                return true;
            }
            if !tray
                .taskbar
                .set_icon(&tray.icon, localizer.main_window_title())
            {
                trace_resident_tray_event("minimize_to_resident_tray: set_icon failed");
                return false;
            }
            update_resident_tray_menu(tray, localizer);
            tray.visible = true;
            ui.frame.show(false);
            trace_resident_tray_event("minimize_to_resident_tray: reused tray and hid frame");
            return true;
        }
    }

    let taskbar = Rc::new(TaskBarIcon::builder().build());
    let Some(icon) = resident_tray_icon_bitmap() else {
        trace_resident_tray_event("minimize_to_resident_tray: icon bitmap failed");
        return false;
    };
    if !taskbar.set_icon(&icon, localizer.main_window_title()) {
        taskbar.destroy();
        trace_resident_tray_event("minimize_to_resident_tray: initial set_icon failed");
        return false;
    }
    let mut menu = resident_tray_menu(localizer);
    taskbar.set_popup_menu(&mut menu);

    let ui_copy = *ui;
    let resident_tray_copy = Rc::clone(resident_tray);
    taskbar.on_left_down(move |_| restore_from_resident_tray(&ui_copy, &resident_tray_copy));

    let frame_copy = ui.frame;
    taskbar.on_menu(move |event| match event.get_id() {
        TRAY_MENU_RESTORE_ID => {
            trace_resident_tray_event("taskbar_menu: posting restore command");
            frame_copy.post_menu_command(TRAY_MENU_RESTORE_ID);
        }
        TRAY_MENU_QUIT_ID => {
            trace_resident_tray_event("taskbar_menu: posting quit command");
            frame_copy.post_menu_command(TRAY_MENU_QUIT_ID);
        }
        _ => event.skip(true),
    });

    resident_tray.borrow_mut().replace(ResidentTray {
        taskbar,
        icon,
        _menu: menu,
        visible: true,
    });
    ui.frame.show(false);
    trace_resident_tray_event("minimize_to_resident_tray: created tray and hid frame");
    true
}

fn resident_tray_menu(localizer: Localizer) -> Menu {
    Menu::builder()
        .append_item(TRAY_MENU_RESTORE_ID, localizer.tray_restore_menu_item(), "")
        .append_separator()
        .append_item(TRAY_MENU_QUIT_ID, localizer.tray_quit_menu_item(), "")
        .build()
}

fn update_resident_tray_menu(tray: &mut ResidentTray, localizer: Localizer) {
    let mut menu = resident_tray_menu(localizer);
    tray.taskbar.set_popup_menu(&mut menu);
    tray._menu = menu;
}

pub(crate) fn restore_from_resident_tray(
    ui: &ReviewUi,
    resident_tray: &Rc<RefCell<Option<ResidentTray>>>,
) {
    trace_resident_tray_event("restore_from_resident_tray: requested");
    hide_resident_tray_icon(resident_tray);
    ui.frame.show(true);
    ui.frame.iconize(false);
    ui.frame.raise();
    trace_resident_tray_event("restore_from_resident_tray: frame shown");
}

pub(crate) fn quit_from_resident_tray(
    ui: &ReviewUi,
    resident_tray: &Rc<RefCell<Option<ResidentTray>>>,
) {
    trace_resident_tray_event("quit_from_resident_tray: requested");
    hide_resident_tray_icon(resident_tray);
    if WxWidget::close(&ui.frame, false) {
        trace_resident_tray_event("quit_from_resident_tray: close accepted; exiting process");
        std::process::exit(0);
    } else {
        trace_resident_tray_event("quit_from_resident_tray: close rejected; restoring frame");
        restore_from_resident_tray(ui, resident_tray);
    }
}

fn hide_resident_tray_icon(resident_tray: &Rc<RefCell<Option<ResidentTray>>>) {
    if let Some(tray) = resident_tray.borrow_mut().as_mut() {
        trace_resident_tray_event("hide_resident_tray_icon: removing icon");
        tray.taskbar.remove_icon();
        tray.visible = false;
        trace_resident_tray_event("hide_resident_tray_icon: icon removed");
    } else {
        trace_resident_tray_event("hide_resident_tray_icon: no resident tray");
    }
}

pub(crate) fn release_resident_tray_icon_for_shutdown(
    resident_tray: &Rc<RefCell<Option<ResidentTray>>>,
) -> bool {
    if let Some(mut tray) = resident_tray.borrow_mut().take() {
        trace_resident_tray_event("release_resident_tray_icon_for_shutdown: removing icon");
        tray.taskbar.remove_icon();
        tray.visible = false;
        trace_resident_tray_event(
            "release_resident_tray_icon_for_shutdown: leaving taskbar object to process teardown",
        );
        std::mem::forget(tray);
        true
    } else {
        trace_resident_tray_event("release_resident_tray_icon_for_shutdown: no resident tray");
        false
    }
}

fn resident_tray_icon_bitmap() -> Option<Bitmap> {
    const SIZE: usize = 32;
    let mut pixels = vec![0_u8; SIZE * SIZE * 4];
    for y in 0..SIZE {
        for x in 0..SIZE {
            let offset = (y * SIZE + x) * 4;
            let border = !(3..SIZE - 3).contains(&x) || !(3..SIZE - 3).contains(&y);
            let c_shape = (8..=24).contains(&x)
                && ((7..=10).contains(&y) || (21..=24).contains(&y))
                || (7..=10).contains(&x) && (8..=23).contains(&y);
            let (red, green, blue) = if c_shape {
                (255, 255, 255)
            } else if border {
                (24, 78, 112)
            } else {
                (36, 118, 155)
            };
            pixels[offset] = red;
            pixels[offset + 1] = green;
            pixels[offset + 2] = blue;
            pixels[offset + 3] = 255;
        }
    }
    Bitmap::from_rgba(&pixels, SIZE as u32, SIZE as u32)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::menu_dispatch::DeferredMenuCommand;

    #[test]
    fn tray_minimize_decision_requires_setting_iconized_frame_and_hidden_tray() {
        assert!(should_enter_resident_tray(true, true, false));
        assert!(!should_enter_resident_tray(false, true, false));
        assert!(!should_enter_resident_tray(true, false, false));
        assert!(!should_enter_resident_tray(true, true, true));
    }

    #[test]
    fn resident_tray_minimise_restore_and_quit_are_wired() {
        let main_source = include_str!("main.rs");
        let menu_dispatch_source = include_str!("menu_dispatch.rs");
        let tray_source = include_str!("resident_tray.rs");
        let main_runtime_source = main_source
            .split("#[cfg(test)]")
            .next()
            .expect("runtime source before tests");
        let tray_runtime_source = tray_source
            .split("#[cfg(test)]")
            .next()
            .expect("tray runtime source before tests");

        assert!(main_source.contains("install_resident_tray_handlers("));
        assert!(main_source.contains("install_deferred_menu_handlers("));
        assert!(main_source.contains("install_resident_tray_minimize_timer("));
        assert!(tray_source.contains("const RESIDENT_TRAY_MINIMIZE_POLL_MS: i32 = 250;"));
        assert!(tray_source.contains("ui.frame.on_size(move |event|"));
        assert!(tray_source.contains("fn should_minimize_to_resident_tray("));
        assert!(tray_source.contains("should_enter_resident_tray("));
        assert!(tray_source.contains("fn resident_tray_visible("));
        assert!(tray_source.contains(".is_some_and(|tray| tray.visible)"));
        assert!(tray_source.contains("state.borrow().settings.minimize_to_tray"));
        assert!(tray_source.contains("ui.frame.is_iconized()"));
        assert!(tray_source.contains("resident_tray_visible(resident_tray)"));
        assert!(tray_source.contains(".set_icon(&tray.icon, localizer.main_window_title())"));
        assert!(tray_source.contains("tray.visible = true;"));
        assert!(tray_source.contains("event.skip(!minimize_to_resident_tray("));
        assert!(tray_source.contains("timer.on_tick(move |_|"));
        assert!(
            tray_source
                .contains("if should_minimize_to_resident_tray(&ui_copy, &state, &resident_tray)")
        );
        assert!(menu_dispatch_source.contains("fn install_deferred_menu_handlers"));
        assert!(menu_dispatch_source.contains("frame.on_menu("));
        assert!(menu_dispatch_source.contains("deferred_menu_event_disposition(event.get_id())"));
        assert!(tray_source.contains("_menu: Menu,"));
        assert!(tray_source.contains("fn resident_tray_menu(localizer: Localizer) -> Menu"));
        assert!(tray_source.contains("fn update_resident_tray_menu("));
        assert!(tray_source.contains("taskbar.set_popup_menu(&mut menu);"));
        assert!(tray_source.contains("update_resident_tray_menu(tray, localizer);"));
        assert!(tray_source.contains("taskbar.on_left_down(move |_| restore_from_resident_tray"));
        assert!(!tray_runtime_source.contains("taskbar.on_right_up(move |_|"));
        assert!(!tray_runtime_source.contains("taskbar_copy.popup_menu(&mut menu);"));
        assert!(tray_source.contains("frame_copy.post_menu_command(TRAY_MENU_RESTORE_ID);"));
        assert!(tray_source.contains("frame_copy.post_menu_command(TRAY_MENU_QUIT_ID);"));
        assert!(main_source.contains("DeferredMenuCommand::TrayRestore"));
        assert!(main_source.contains("restore_from_resident_tray(ui, resident_tray);"));
        assert!(main_source.contains("DeferredMenuCommand::TrayQuit"));
        assert!(main_source.contains("quit_from_resident_tray(ui, resident_tray);"));
        assert!(tray_source.contains("fn quit_from_resident_tray("));
        assert!(tray_source.contains("if WxWidget::close(&ui.frame, false)"));
        assert!(tray_source.contains("std::process::exit(0);"));
        assert!(!main_runtime_source.contains("exit_process_after_tray_shutdown();"));
        assert!(!tray_runtime_source.contains("exit_process_after_tray_shutdown();"));
        assert!(!main_runtime_source.contains("TerminateProcess(GetCurrentProcess(), 0);"));
        assert!(!tray_runtime_source.contains("TerminateProcess(GetCurrentProcess(), 0);"));
        assert!(
            tray_source.contains("const RESIDENT_TRAY_TRACE_ENV: &str = \"COSIMO_TRAY_TRACE\";")
        );
        assert!(tray_source.contains("fn trace_resident_tray_event("));
        assert!(
            tray_source
                .contains("trace_resident_tray_event(\"quit_from_resident_tray: requested\")")
        );
        assert!(tray_source.contains(
            "trace_resident_tray_event(\"quit_from_resident_tray: close rejected; restoring frame\")"
        ));
        assert!(tray_source.contains("restore_from_resident_tray(ui, resident_tray);"));
        assert!(tray_source.contains("hide_resident_tray_icon(resident_tray);"));
        assert!(tray_source.contains("fn hide_resident_tray_icon("));
        assert!(
            main_source.contains("release_resident_tray_icon_for_shutdown(&resident_tray_copy);")
        );
        assert!(tray_source.contains("fn release_resident_tray_icon_for_shutdown("));
        assert!(tray_source.contains("resident_tray.borrow_mut().take()"));
        assert!(tray_source.contains("tray.visible = false;"));
        assert!(tray_source.contains("std::mem::forget(tray);"));
        assert!(main_source.contains("frame_close: tray object released; exiting process"));
        let removed_destroy_flag = concat!("destroy", "_on_frame_close");
        let removed_destroy_helper = concat!("fn destroy", "_resident_tray_icon(");
        assert!(!main_source.contains(removed_destroy_flag));
        assert!(!tray_source.contains(removed_destroy_flag));
        assert!(!main_source.contains(removed_destroy_helper));
        assert!(!tray_source.contains(removed_destroy_helper));
        let shutdown_release_start = tray_source
            .find("fn release_resident_tray_icon_for_shutdown(")
            .expect("shutdown tray release helper");
        let next_function = tray_source[shutdown_release_start..]
            .find("fn resident_tray_icon_bitmap(")
            .expect("resident tray icon bitmap helper follows shutdown release helper");
        let shutdown_release_source =
            &tray_source[shutdown_release_start..shutdown_release_start + next_function];
        assert!(!shutdown_release_source.contains("taskbar.destroy();"));
        assert!(!shutdown_release_source.contains("unbind_all();"));
        assert!(main_source.contains("resident_tray_minimize_timer.borrow().as_ref()"));
        assert!(main_source.contains("ready_refresh_timer.borrow_mut().take();"));
        assert!(main_source.contains("resident_tray_minimize_timer.borrow_mut().take();"));
        assert!(tray_source.contains("let timer = Timer::new(&ui.frame);"));
        assert_eq!(
            DeferredMenuCommand::from_id(TRAY_MENU_RESTORE_ID),
            Some(DeferredMenuCommand::TrayRestore)
        );
        assert_eq!(
            DeferredMenuCommand::from_id(TRAY_MENU_QUIT_ID),
            Some(DeferredMenuCommand::TrayQuit)
        );
        assert!((0..32_767).contains(&TRAY_MENU_RESTORE_ID));
        assert!((0..32_767).contains(&TRAY_MENU_QUIT_ID));
    }
}
