use ahfail_module::*;
use gtk::prelude::*;
use gtk::{glib, gdk_pixbuf, gdk};
use gstreamer as gst;
use std::ptr;
use std::ffi::c_void;
use gtk::glib::translate::{ToGlibPtr, Stash};

// Mock implementation of the resource getter for tests
#[no_mangle]
extern "C" fn ahfail_get_resource() -> *mut gtk::gio::ffi::GResource {
    ptr::null_mut()
}

// Helper struct to simulate the C flexible array member allocation
#[repr(C)]
struct WindowWithStorage {
    window: Window,
    // Reserve space for the 'module_data' flexible array (1 pointer)
    storage: *mut c_void,
}

fn setup_test_environment() {
    static ONCE: std::sync::Once = std::sync::Once::new();
    ONCE.call_once(|| {
        let _ = gtk::init();
        let _ = gst::init();
    });
}

fn flush_events() {
    while gtk::events_pending() {
        gtk::main_iteration();
    }
}

fn create_mock_context() -> (WindowWithStorage, gtk::Window, gtk::Label, gtk::Overlay, gdk::Display) {
    setup_test_environment();
    
    let error_label = gtk::Label::new(Some(""));
    let overlay = gtk::Overlay::new();
    let window_widget = gtk::Window::new(gtk::WindowType::Toplevel);
    let display = gdk::Display::default().expect("No display available for testing");
    let monitor = display.monitor(0).expect("No monitor available");

    // Explicitly type the Stash to avoid ambiguity
    let monitor_stash: Stash<*mut gdk::ffi::GdkMonitor, _> = monitor.to_glib_none();
    let window_stash: Stash<*mut gtk::ffi::GtkWindow, _> = window_widget.to_glib_none();
    let overlay_stash: Stash<*mut gtk::ffi::GtkOverlay, _> = overlay.to_glib_none();
    let label_stash: Stash<*mut gtk::ffi::GtkLabel, _> = error_label.to_glib_none();

    let win = Window {
        monitor: monitor_stash.0,
        window: window_stash.0 as *mut gtk::ffi::GtkWidget,
        overlay: overlay_stash.0,
        window_box: ptr::null_mut(),
        body_revealer: ptr::null_mut(),
        body_grid: ptr::null_mut(),
        input_label: ptr::null_mut(),
        input_field: ptr::null_mut(),
        message_revealer: ptr::null_mut(),
        message_scrolled_window: ptr::null_mut(),
        message_box: ptr::null_mut(),
        unlock_button: ptr::null_mut(),
        error_label: label_stash.0 as *mut gtk::ffi::GtkWidget,
        warning_label: ptr::null_mut(),
        info_box: ptr::null_mut(),
        time_box: ptr::null_mut(),
        clock_label: ptr::null_mut(),
        date_label: ptr::null_mut(),
        module_data: __IncompleteArrayField::new(),
    };

    let storage = WindowWithStorage {
        window: win,
        storage: ptr::null_mut(),
    };

    (storage, window_widget, error_label, overlay, display)
}

fn inject_test_state() {
    let pixbuf = gdk_pixbuf::Pixbuf::new(gdk_pixbuf::Colorspace::Rgb, false, 8, 1, 1).unwrap();
    let anim = gdk_pixbuf::PixbufSimpleAnim::new(1, 1, 1.0);
    anim.add_frame(&pixbuf);

    // Use a minimal valid WAV (silent) to avoid GStreamer errors
    let wav_base64 = "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQEAAAAA";
    let data_uri = format!("data:audio/wav;base64,{}", wav_base64);

    MODULE_STATE.with(|state| {
        let mut state = state.borrow_mut();
        state.animation = Some(anim.upcast());
        state.audio_uri = Some(data_uri);
    });
}

fn run_test_01_initialization() {
    unsafe {
        on_activation(ptr::null_mut(), 0);
    }
    MODULE_STATE.with(|state| {
        let state = state.borrow();
        // It should have set the audio URI to the default
        assert_eq!(state.audio_uri.as_deref(), Some("resource:///ahfail/audio/magic-word.mp3"));
    });
}

fn run_test_02_window_create_null() {
    unsafe {
        on_window_create(ptr::null_mut(), ptr::null_mut());
    }
}

fn run_test_03_window_create_valid() {
    let (mut win_storage, _w, _l, _o, _) = create_mock_context();
    let ctx_ptr = &mut win_storage.window as *mut Window;
    
    unsafe {
        on_window_create(ptr::null_mut(), ctx_ptr);
        let data = get_window_data_ref(ctx_ptr);
        assert!(data.is_some(), "WindowData should be initialized");
        
        on_window_destroy(ptr::null_mut(), ctx_ptr);
        flush_events();
    }
}

fn run_test_04_trigger_effect_sprite() {
    let (mut win_storage, _w, label, _o, _) = create_mock_context();
    let ctx_ptr = &mut win_storage.window as *mut Window;
    inject_test_state();

    unsafe {
        on_window_create(ptr::null_mut(), ctx_ptr);
        label.set_text("Error");
        flush_events();
        
        let data = get_window_data_ref(ctx_ptr).unwrap();
        assert_eq!(data.sprites.len(), 1);
        
        on_window_destroy(ptr::null_mut(), ctx_ptr);
        flush_events();
    }
}

fn run_test_05_trigger_effect_audio() {
    let (mut win_storage, _w, label, _o, _) = create_mock_context();
    let ctx_ptr = &mut win_storage.window as *mut Window;
    inject_test_state();

    unsafe {
        on_window_create(ptr::null_mut(), ctx_ptr);
        label.set_text("Error");
        flush_events();
        
        let data = get_window_data_ref(ctx_ptr).unwrap();
        assert_eq!(data.active_players.len(), 1);
        
        on_window_destroy(ptr::null_mut(), ctx_ptr);
        flush_events();
    }
}

fn run_test_06_multiple_triggers() {
    let (mut win_storage, _w, label, _o, _) = create_mock_context();
    let ctx_ptr = &mut win_storage.window as *mut Window;
    inject_test_state();

    unsafe {
        on_window_create(ptr::null_mut(), ctx_ptr);
        for _ in 0..5 {
            label.set_text("Error"); 
            flush_events();
        }
        
        let data = get_window_data_ref(ctx_ptr).unwrap();
        assert_eq!(data.sprites.len(), 5);
        assert_eq!(data.active_players.len(), 5);
        
        on_window_destroy(ptr::null_mut(), ctx_ptr);
        flush_events();
    }
}

fn run_test_07_window_cleanup() {
    let (mut win_storage, _w, label, _o, _) = create_mock_context();
    let ctx_ptr = &mut win_storage.window as *mut Window;
    inject_test_state();

    unsafe {
        on_window_create(ptr::null_mut(), ctx_ptr);
        label.set_text("Error");
        flush_events();
        
        assert!(get_window_data_ref(ctx_ptr).is_some());
        on_window_destroy(ptr::null_mut(), ctx_ptr);
        assert!(get_window_data_ref(ctx_ptr).is_none());
        flush_events();
    }
}

fn run_test_08_multiple_windows() {
    let (mut win1, _w1, label1, _o1, _) = create_mock_context();
    let (mut win2, _w2, label2, _o2, _) = create_mock_context();
    let ctx1 = &mut win1.window as *mut Window;
    let ctx2 = &mut win2.window as *mut Window;
    inject_test_state();

    unsafe {
        on_window_create(ptr::null_mut(), ctx1);
        on_window_create(ptr::null_mut(), ctx2);

        label1.set_text("E1");
        flush_events();
        
        label2.set_text("E2");
        label2.set_text("E2 again");
        flush_events();

        let data1 = get_window_data_ref(ctx1).unwrap();
        let data2 = get_window_data_ref(ctx2).unwrap();

        assert_eq!(data1.sprites.len(), 1);
        assert_eq!(data2.sprites.len(), 2);
        
        on_window_destroy(ptr::null_mut(), ctx1);
        on_window_destroy(ptr::null_mut(), ctx2);
        flush_events();
    }
}

fn run_test_09_idle_hide_cleanup() {
    let (mut win_storage, _w, label, _o, _) = create_mock_context();
    let ctx_ptr = &mut win_storage.window as *mut Window;
    inject_test_state();

    unsafe {
        on_window_create(ptr::null_mut(), ctx_ptr);
        label.set_text("Error");
        flush_events();
        
        // Mock GtkLock struct
        let mut windows_array = glib::ffi::g_array_new(0, 0, std::mem::size_of::<*mut Window>() as u32);
        glib::ffi::g_array_append_vals(windows_array, &ctx_ptr as *const _ as *const c_void, 1);
        
        let mut lock = GtkLock {
            windows: windows_array,
        };

        on_idle_hide(&mut lock);

        assert!(get_window_data_ref(ctx_ptr).is_none());
        glib::ffi::g_array_free(windows_array, 1);
        flush_events();
    }
}

fn run_test_10_module_unload() {
    inject_test_state();
    MODULE_STATE.with(|state| {
        assert!(state.borrow().audio_uri.is_some());
    });
    unsafe {
        g_module_unload(ptr::null_mut());
    }
    MODULE_STATE.with(|state| {
        let state = state.borrow();
        assert!(state.animation.is_none());
        assert!(state.audio_uri.is_none());
    });
}

#[test]
fn run_all_tests_sequentially() {
    println!("Running test 01...");
    run_test_01_initialization();
    println!("Running test 02...");
    run_test_02_window_create_null();
    println!("Running test 03...");
    run_test_03_window_create_valid();
    println!("Running test 04...");
    run_test_04_trigger_effect_sprite();
    println!("Running test 05...");
    run_test_05_trigger_effect_audio();
    println!("Running test 06...");
    run_test_06_multiple_triggers();
    println!("Running test 07...");
    run_test_07_window_cleanup();
    println!("Running test 08...");
    run_test_08_multiple_windows();
    println!("Running test 09...");
    run_test_09_idle_hide_cleanup();
    println!("Running test 10...");
    run_test_10_module_unload();
    
    // Final flush
    flush_events();
}