use proc_macro::TokenStream;
use quote::quote;
use syn::{Ident, Lit, Token, parse::Parser, parse_macro_input, punctuated::Punctuated};

extern crate proc_macro;

#[proc_macro]
pub fn entrypoint(_: TokenStream) -> TokenStream {
    quote! {
        eetoee::lazy_static::lazy_static! {
            static ref BROWSER: std::sync::Arc<std::sync::Mutex<Option<eetoee::chrome::Browser>>> = {
                #[cfg(debug_assertions)]
                let debug = false;

                #[cfg(not(debug_assertions))]
                let debug = true;

                #[cfg(debug_assertions)]
                let args = vec![];

                #[cfg(not(debug_assertions))]
                let args = vec![
                    std::ffi::OsStr::new("--no-sandbox"),
                    std::ffi::OsStr::new("--disable-gpu"),
                    std::ffi::OsStr::new("--headless=new"),
                    std::ffi::OsStr::new("--disable-dev-shm-usage"),
                ];

                let options = eetoee::chrome::LaunchOptions {
                    headless: debug,
                    idle_browser_timeout: std::time::Duration::from_secs(60),
                    sandbox: !debug,
                    args,
                    ..Default::default()
                };

                std::sync::Arc::new(std::sync::Mutex::new(Some(eetoee::chrome::Browser::new(options).expect("Failed to create browser"))))
            };
        }

        fn main() {
            let result = std::panic::catch_unwind(|| {
                eetoee::run();
            });

            let mut cell = BROWSER.lock().expect("Failed to lock browser");
            *cell = None;

            result.unwrap();
        }

        #[cfg(test)]
        extern "C" fn dtor() {
            let mut browser_cell = BROWSER.lock().expect("Failed to lock browser");
            *browser_cell = None;
        }

        #[cfg(test)]
        extern "C" fn ctor() {
            unsafe {
                eetoee::atexit(dtor);
            }
        }

        #[cfg(test)]
        #[used]
        #[cfg_attr(target_os = "linux", unsafe(link_section = ".init_array"))]
        #[cfg_attr(target_vendor = "apple", unsafe(link_section = "__DATA,__mod_init_func,mod_init_funcs"))]
        #[cfg_attr(target_os = "windows", unsafe(link_section = ".CRT$XCU"))]
        static CTOR: extern "C" fn() = ctor;
    }.into()
}

#[proc_macro_attribute]
pub fn test_case_at(attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut input = parse_macro_input!(item as syn::ItemFn);
    let test_fn_name = &input.sig.ident.clone();
    let new_fn_name = Ident::new(&format!("_{}", input.sig.ident), input.sig.ident.span());
    input.sig.ident = new_fn_name.clone();

    let args = Punctuated::<Lit, Token![,]>::parse_terminated
        .parse(attr)
        .expect("Failed to parse attribute args");

    let path = if let Some(Lit::Str(lit_str)) = args.first() {
        lit_str.value()
    } else {
        panic!("Expected a string literal with a path to concrete page");
    };

    quote! {
        #input

        #[test]
        fn #test_fn_name() {
            let browser_cell = BROWSER.lock().expect("Failed to lock browser");

            let Some(browser) = browser_cell.as_ref() else {
                panic!("Failed to lock browser");
            };

            let base_url = std::env::args().nth(2).and_then(|arg| eetoee::url::Url::parse(&arg).ok()).unwrap_or_else(|| eetoee::url::Url::parse(option_env!("TEST_BASE_URL").expect("Either TEST_BASE_URL environment variable or command line argument must be provided")).unwrap());

            let mut tab = browser.new_tab().expect("Failed to create new tab");
            let url = base_url.join(#path).expect("Failed to join base URL with path");

            tab.navigate_to(&url.to_string()).expect("Failed to navigate to given URL");
            let _ = tab.wait_until_navigated();
            #new_fn_name(Arc::clone(&tab));
            tab.close(false).expect("Failed to close tab");
        }

        eetoee::inventory::submit! {
            eetoee::Test { path: #path, runnable: |base_url| {
                let tab = {
                    let browser = BROWSER.lock().expect("Failed to lock browser");

                    let Some(browser) = browser.as_ref() else {
                        panic!("Failed to lock browser");
                    };

                    browser.new_tab().expect("Failed to create new tab")
                };

                let url = base_url.join(#path).expect("Failed to join base URL with path");

                tab.navigate_to(&url.to_string()).expect("Failed to navigate to given URL");
                let _ = tab.wait_until_navigated();
                #new_fn_name(Arc::clone(&tab));
                tab.close(false).expect("Failed to close tab");
            }, name: stringify!(#new_fn_name) }
        }
    }
    .into()
}
