use crate::config::Config;
use crate::input::KeyboardLockStates;
use crate::{GreeterData, GreeterDisplay, LoginOptionTileData};
use anyhow::{Context, anyhow};
use greetd_ipc::codec::TokioCodec;
use greetd_ipc::{AuthMessageType, ErrorType, Request, Response};
use i_slint_core::api::{ComponentHandle, Global, Image, Rgba8Pixel, SharedPixelBuffer, Weak};
use i_slint_core::model::{Model, VecModel};
use log::{debug, error, info, trace};
use magick_rust::MagickWand;
use pexels_api::{PexelsClient, SearchParams};
use rand::seq::SliceRandom;
use std::collections::VecDeque;
use std::fmt::Display;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::Arc;
use tokio::fs::File;
use tokio::io::{AsyncBufReadExt, BufReader, Lines};
use tokio::net::UnixStream;

#[cfg(not(debug_assertions))]
use slint::SharedString;
use tokio_stream::StreamExt;
use tokio_stream::wrappers::ReadDirStream;
#[cfg(not(debug_assertions))]
use zbus::{Connection, proxy};

pub fn register_login_callback(
    data: &GreeterData,
    display_weak: Weak<GreeterDisplay>,
    tiles: Rc<VecModel<LoginOptionTileData>>,
) {
    data.on_login({
        let tiles = tiles.clone();
        let display_weak = display_weak.clone();

        move |username, password| {
            debug!(target: "ckgreeter::callbacks::login", "----- Login callback called");
            let tiles = tiles.clone();
            let display_weak = display_weak.clone();
            debug!(target: "ckgreeter::callbacks::login", "spawning slint thread");
            slint::spawn_local(async move {
                debug!(target: "ckgreeter::callbacks::login", "spawned slint thread");
                let display = display_weak.unwrap();

                let username = username.to_string();
                let password = password.to_string();

                debug!(target: "ckgreeter::callbacks::login", "username: {username:?}");

                let index = display.get_selected_index();
                let tile = &tiles.row_data(index as usize);
                let tile = match tile {
                    Some(tile) => tile,
                    None => {
                        display.invoke_set_error("Failed to login: Invalid index for tile".into());
                        error!("Failed to login: Invalid index for tile: {index}");
                        return;
                    }
                };

                match login(username.as_str(), password.as_str(), tile.command.as_str()).await {
                    Ok(successful) => {
                        if !successful {
                            display.invoke_set_error("Failed to login: Bad Login".into());
                            error!("Failed to login: Bad Login");
                            return;
                        }

                        if let Err(err) = slint::quit_event_loop() {
                            display
                                .invoke_set_error(format!("Failed to exit greeter: {err}").into());
                            error!("Failed to exit greeter: {err}");
                        }
                    }
                    Err(err) => {
                        display.invoke_set_error(format!("Failed to login: {err}").into());
                        error!("Failed to login: {err}");
                        return;
                    }
                };
            })
            .unwrap();
        }
    });

    async fn login(username: &str, password: &str, cmd: &str) -> anyhow::Result<bool> {
        if username.trim().is_empty() {
            return Err(anyhow::anyhow!("Username is required"));
        }

        if cmd.trim().is_empty() {
            return Err(anyhow::anyhow!("Command is required"));
        }

        debug!(target: "ckgreeter::callbacks::login", "connecting to greetd socket");

        let socket = std::env::var("GREETD_SOCK").context("GREETD_SOCK is not set")?;

        let mut stream = UnixStream::connect(socket)
            .await
            .context("failed to connect to greetd socket")?;

        debug!(target: "ckgreeter::callbacks::login", "connected to greetd socket");

        let mut next_request = Request::CreateSession {
            username: username.to_string(),
        };
        trace!(target: "ckgreeter::callbacks::login", "sending request to create session: {next_request:?}");

        let mut starting = false;
        loop {
            next_request.write_to(&mut stream).await?;

            match Response::read_from(&mut stream).await? {
                Response::AuthMessage {
                    auth_message: _auth_message,
                    auth_message_type,
                } => {
                    let response = match auth_message_type {
                        AuthMessageType::Visible => None,
                        AuthMessageType::Secret => {
                            if !password.is_empty() {
                                Some(password.to_string())
                            } else {
                                None
                            }
                        }
                        AuthMessageType::Info => None,
                        AuthMessageType::Error => None,
                    };

                    trace!(target: "ckgreeter::callbacks::login", "sending request: {next_request:?}");
                    next_request = Request::PostAuthMessageResponse { response };
                }
                Response::Success => {
                    if starting {
                        debug!(target: "ckgreeter::callbacks::login", "starting session");
                        return Ok(true);
                    } else {
                        starting = true;
                        trace!(target: "ckgreeter::callbacks::login", "sending request: {next_request:?}");
                        next_request = Request::StartSession {
                            env: vec![],
                            cmd: vec![cmd.to_string()],
                        }
                    }
                }
                Response::Error {
                    error_type,
                    description,
                } => {
                    debug!(target: "ckgreeter::callbacks::login", "error logging in");
                    Request::CancelSession.write_to(&mut stream).await?;
                    return match error_type {
                        ErrorType::AuthError => Ok(false),
                        ErrorType::Error => Err(anyhow::anyhow!("login error: {description:?}")),
                    };
                }
            }
        }
    }
}
pub fn register_power_callback(_data: &GreeterData) {
    #[cfg(not(debug_assertions))]
    _data.on_loginctl(move |command: SharedString| {
        debug!(target: "ckgreeter::callbacks::power", "----- Power callback called");
        slint::spawn_local(async move {
            if let Err(err) = handle_power_command(command.as_str()).await {
                eprintln!("Power command failed: {err}");
            }
        })
        .unwrap();
    });

    #[cfg(not(debug_assertions))]
    async fn handle_power_command(command: &str) -> anyhow::Result<()> {
        debug!(target: "ckgreeter::callbacks::power", "command: {command:?}");

        trace!(target: "ckgreeter::callbacks::power", "connecting to dbus");
        let connection = Connection::system().await?;
        trace!(target: "ckgreeter::callbacks::power", "connected to dbus");
        trace!(target: "ckgreeter::callbacks::power", "getting power manager proxy");
        let proxy = PowerManagerProxy::new(&connection).await?;
        trace!(target: "ckgreeter::callbacks::power", "got power manager proxy");

        debug!(target: "ckgreeter::callbacks::power", "running power command: {command}");
        match command {
            "suspend" => proxy.suspend(&false).await?,
            "poweroff" => proxy.power_off(&false).await?,
            "reboot" => proxy.reboot(&false).await?,
            command => return Err(anyhow::anyhow!("Unknown power command: {command}")),
        }

        Ok(())
    }

    #[cfg(not(debug_assertions))]
    #[proxy(
        interface = "org.freedesktop.login1.Manager",
        default_service = "org.freedesktop.login1",
        default_path = "/org/freedesktop/login1"
    )]
    trait PowerManager {
        fn suspend(&self, interactive: &bool) -> zbus::Result<()>;
        fn reboot(&self, interactive: &bool) -> zbus::Result<()>;
        fn power_off(&self, interactive: &bool) -> zbus::Result<()>;
    }
}
pub fn register_lock_state_callback(data: &GreeterData, data_weak: Weak<GreeterData<'static>>) {
    data.on_check_lock_states({
        let data_weak = data_weak.clone();
        move || {
            let data_weak = data_weak.clone();
            slint::spawn_local(async move {
                debug!(target: "ckgreeter::callbacks::lock_state", "----- lock state callback called");
                let data = data_weak.unwrap();
                if let Some(states) = KeyboardLockStates::get_locks_state() {
                    data.set_caps_lock_state(states.caps_lock);
                    trace!(target: "ckgreeter::callbacks::lock_state", "caps lock state: {}", states.caps_lock);
                    data.set_num_lock_state(states.num_lock);
                    trace!(target: "ckgreeter::callbacks::lock_state", "num lock state: {}", states.num_lock);
                    data.set_scroll_lock_state(states.scroll_lock);
                    trace!(target: "ckgreeter::callbacks::lock_state", "scroll lock state: {}", states.scroll_lock)
                }
            })
                .unwrap();
        }
    });
}
pub fn register_wallpaper_callback(
    data: &GreeterData,
    display_weak: Weak<GreeterDisplay>,
    data_weak: Weak<GreeterData<'static>>,
    cache_dir: PathBuf,
    config: &Config,
) {
    //TODO: Seperate this out into seperate methods, cleanup!!!
    const MAX_RECENT_WALLPAPERS: usize = 25;

    async fn read_lines<P>(filename: P) -> std::io::Result<Lines<BufReader<File>>>
    where
        P: AsRef<Path> + std::fmt::Debug,
    {
        debug!(target: "ckgreeter::callbacks::wallpaper::read_lines", "reading lines from file: {:?}", filename);
        let file = File::open(filename).await?;
        Ok(BufReader::new(file).lines())
    }

    async fn write_lines<P>(filename: &P, lines: VecDeque<String>) -> std::io::Result<()>
    where
        P: AsRef<Path> + std::fmt::Debug,
    {
        let mut contents = String::new();

        for line in lines {
            contents.push_str(&line);
            contents.push('\n');
        }

        debug!(target: "ckgreeter::callbacks::wallpaper::write_lines", "writing lines to file: {:?}", filename);
        std::fs::write(filename, contents)
    }

    fn set_background_image(
        display: &GreeterDisplay,
        data: &GreeterData,
        pixels: &[u8],
        w: u32,
        h: u32,
    ) {
        debug!(target: "ckgreeter::callbacks::wallpaper::set_background_image", "setting background image");
        if !display.get_background_toggle() {
            trace!(target: "ckgreeter::callbacks::wallpaper::set_background_image", "setting background image 1");
            let old = data.get_wallpaper_image();
            data.set_wallpaper_image(Image::from_rgba8(
                SharedPixelBuffer::<Rgba8Pixel>::clone_from_slice(pixels, w, h),
            ));
            trace!(target: "ckgreeter::callbacks::wallpaper::set_background_image", "set background image 1");
            drop(old);
        } else {
            trace!(target: "ckgreeter::callbacks::wallpaper::set_background_image", "setting background image 2");
            let old = data.get_wallpaper_image_1();
            data.set_wallpaper_image_1(Image::from_rgba8(
                SharedPixelBuffer::<Rgba8Pixel>::clone_from_slice(pixels, w, h),
            ));
            trace!(target: "ckgreeter::callbacks::wallpaper::set_background_image", "set background image 2");
            drop(old);
        }
    }

    fn export_wand_rgba(wand: &MagickWand) -> anyhow::Result<(u32, u32, Vec<u8>)> {
        trace!(target: "ckgreeter::callbacks::wallpaper::export_wand_rgba", "exporting wand");
        let pixels = wand
            .export_image_pixels(
                0,
                0,
                wand.get_image_width(),
                wand.get_image_height(),
                "RGBA",
            )
            .ok_or_else(|| anyhow!("Failed to export image pixels"))?;

        trace!(target: "ckgreeter::callbacks::wallpaper::export_wand_rgba", "exported wand");
        Ok((
            wand.get_image_width() as u32,
            wand.get_image_height() as u32,
            pixels,
        ))
    }

    let reqwest_client = match reqwest::ClientBuilder::new()
        .tls_backend_rustls()
        .http2_prior_knowledge()
        .http2_keep_alive_timeout(std::time::Duration::from_secs(10))
        .build()
    {
        Ok(client) => client,
        Err(err) => {
            eprintln!("Failed to create reqwest client: {err}");
            return;
        }
    };
    debug!(target: "ckgreeter::callbacks::wallpaper", "reqwest client created");

    if let Some(pexels_api_key) = config.pexels_api_key.clone() {
        info!(target: "ckgreeter::callbacks::wallpaper", "Using pexels api");
        debug!(target: "ckgreeter::callbacks::wallpaper", "pexels api key found");
        let pexels_client = Arc::new(PexelsClient::new(pexels_api_key));
        let pexels_query = config
            .pexels_query
            .clone()
            .unwrap_or_else(|| String::from("nature"));
        debug!(target: "ckgreeter::callbacks::wallpaper", "pexels query: {pexels_query}");
        info!(target: "ckgreeter::callbacks::wallpaper", "Registering wallpaper callback");
        data.on_get_new_wallpaper({
            let data_weak = data_weak.clone();
            let display_weak = display_weak.clone();
            move || {
                debug!(target: "ckgreeter::callbacks::wallpaper", "----- wallpaper callback called");
                let pexels_client = pexels_client.clone();
                let reqwest_client = reqwest_client.clone();
                let cache_dir = cache_dir.clone();
                let pexels_query = pexels_query.clone();

                let display_weak = display_weak.clone();
                let data_weak = data_weak.clone();

                slint::spawn_local(async move {
                    trait ResultExt<T, E> {
                        fn wpaper_err(self, data: &GreeterData) -> Result<T, E>;
                    }
                    impl<T, E: Display> ResultExt<T, E> for Result<T, E> {
                        fn wpaper_err(self, data: &GreeterData) -> Result<T, E> {
                            self.inspect_err(|e| {
                                error!(target: "ckgreeter::callbacks::wallpaper", "Failed to get wallpaper: {e}");
                                data.set_wallpaper_author("Error".into());
                                data.set_wallpaper_alt(e.to_string().into());
                                data.set_wallpaper_url("Failed to get wallpaper".into());
                            })
                        }
                    }

                    let data = data_weak.unwrap();
                    let display = display_weak.unwrap();

                    let last_used_log = cache_dir.join("last_used_log.txt");
                    debug!(target: "ckgreeter::callbacks::wallpaper", "last used log: {:?}", last_used_log);
                    if !tokio::fs::try_exists(&last_used_log).await.wpaper_err(&data)? {
                        File::create(&last_used_log).await.wpaper_err(&data)?;
                        debug!(target: "ckgreeter::callbacks::wallpaper", "created last used log");
                    }

                    let mut file_lines = tokio::time::timeout(
                        std::time::Duration::from_secs(5),
                        read_lines(&last_used_log),
                    )
                        .await.wpaper_err(&data)?.wpaper_err(&data)?;
                    debug!(target: "ckgreeter::callbacks::wallpaper", "read last used log");

                    let mut idx = 0;
                    let mut last_used_lines = VecDeque::with_capacity(MAX_RECENT_WALLPAPERS);
                    while let Some(line) = file_lines.next_line().await.wpaper_err(&data)? {
                        last_used_lines.push_back(line);
                        idx += 1;
                        if idx > MAX_RECENT_WALLPAPERS {
                            last_used_lines.pop_front();
                        }
                    }
                    drop(file_lines);

                    let mut page = 0;

                    loop {
                        debug!(target: "ckgreeter::callbacks::wallpaper", "fetched photos list from pexels api");
                        let mut is_photo_set: bool = false;
                        let mut found_photo: bool = false;
                        match pexels_client
                            .search_photos(
                                pexels_query.as_str(),
                                &SearchParams::new()
                                    .page(page)
                                    .per_page(10)
                                    .size(pexels_api::Size::Medium)
                                    .orientation(pexels_api::Orientation::Landscape),
                            )
                            .await
                        {
                            Ok(photos) => {
                                if photos.total_results > 0 {
                                    found_photo = true;
                                    debug!(target: "ckgreeter::callbacks::wallpaper", "got photos from pexels api");
                                    for photo in photos.photos {
                                        let string_id = photo.id.to_string();
                                        trace!(target: "ckgreeter::callbacks::wallpaper", "pexels photo id: {}", string_id);
                                        if last_used_lines.contains(&string_id) {
                                            trace!(target: "ckgreeter::callbacks::wallpaper", "pexels photo id already used recently");
                                            continue;
                                        }
                                        is_photo_set = true;

                                        last_used_lines.push_back(string_id);
                                        if last_used_lines.len() > MAX_RECENT_WALLPAPERS {
                                            last_used_lines.pop_front();
                                        }

                                        let image_path = cache_dir.join(format!("{}.png", photo.id));
                                        trace!(target: "ckgreeter::callbacks::wallpaper", "pexels photo image path: {:?}", image_path);

                                        let _ = if !tokio::fs::try_exists(&image_path).await.wpaper_err(&data)? {
                                            debug!(target: "ckgreeter::callbacks::wallpaper", "pexels photo image not found, downloading");
                                            let response = reqwest_client
                                                .get(photo.src.large2x.clone()) // TODO: determine which photo src size is best, maybe determine based on the monitor size to reduce memory pressure
                                                .send()
                                                .await.wpaper_err(&data)?;
                                            let image_bytes = response.bytes().await.wpaper_err(&data)?;
                                            debug!(target: "ckgreeter::callbacks::wallpaper", "pexels photo image downloaded");

                                            let size = display.window().size();

                                            debug!(target: "ckgreeter::callbacks::wallpaper", "resizing image with ImageMagick");
                                            let (w, h, pixels) =
                                                tokio::task::spawn_blocking(move || {
                                                    let wand = MagickWand::new();

                                                    wand.read_image_blob(&image_bytes)?;
                                                    drop(image_bytes);

                                                    if size.width >= size.height {
                                                        wand.fit(
                                                            size.width as usize,
                                                            size.width as usize,
                                                        );
                                                    } else {
                                                        wand.fit(
                                                            size.height as usize,
                                                            size.height as usize,
                                                        );
                                                    }

                                                    trace!(target: "ckgreeter::callbacks::wallpaper", "exporting png data");
                                                    let png_data = wand.write_image_blob("png")?;
                                                    std::fs::write(image_path, png_data)?;
                                                    trace!(target: "ckgreeter::callbacks::wallpaper", "exported png data to disk");

                                                    export_wand_rgba(&wand)
                                                })
                                                    .await.wpaper_err(&data)?.wpaper_err(&data)?;

                                            debug!(target: "ckgreeter::callbacks::wallpaper", "image resized with ImageMagick");
                                            set_background_image(
                                                &display,
                                                &data,
                                                pixels.as_slice(),
                                                w,
                                                h,
                                            );
                                            debug!(target: "ckgreeter::callbacks::wallpaper", "set background image");

                                            drop(pixels);
                                        } else {
                                            debug!(target: "ckgreeter::callbacks::wallpaper", "pexels photo image found, using cached");
                                            let contents = tokio::fs::read(image_path).await.wpaper_err(&data)?;

                                            debug!(target: "ckgreeter::callbacks::wallpaper", "resizing image with ImageMagick");
                                            let (w, h, pixels) =
                                                tokio::task::spawn_blocking(move || {
                                                    let wand = MagickWand::new();

                                                    wand.read_image_blob(contents.as_slice())?;
                                                    export_wand_rgba(&wand)
                                                })
                                                    .await.wpaper_err(&data)?.wpaper_err(&data)?;

                                            debug!(target: "ckgreeter::callbacks::wallpaper", "image resized with ImageMagick");
                                            set_background_image(
                                                &display,
                                                &data,
                                                pixels.as_slice(),
                                                w,
                                                h,
                                            );

                                            debug!(target: "ckgreeter::callbacks::wallpaper", "set background image");

                                            drop(pixels);
                                        };

                                        debug!(target: "ckgreeter::callbacks::wallpaper", "set pexels photo author: {}", photo.photographer);
                                        data.set_wallpaper_author(photo.photographer.into());
                                        let photo_alt = photo.alt.unwrap_or("".into());
                                        debug!(target: "ckgreeter::callbacks::wallpaper", "set pexels photo alt: {}", photo_alt);
                                        data.set_wallpaper_alt(
                                            photo_alt.into(),
                                        );
                                        debug!(target: "ckgreeter::callbacks::wallpaper", "set pexels photo url: {}", photo.url);
                                        data.set_wallpaper_url(photo.url.into());

                                        debug!(target: "ckgreeter::callbacks::wallpaper", "switching background to foreground");
                                        display.invoke_switch_background();

                                        break;
                                    }
                                }
                            }
                            Err(err) => {
                                error!("Failed to fetch online images, using cached. {err}");
                                debug!(target: "ckgreeter::callbacks::wallpaper", "getting cached wallpaper images from disk");
                                let mut photos =
                                    ReadDirStream::new(tokio::fs::read_dir(&cache_dir).await.wpaper_err(&data)?)
                                        .filter(|de| match de {
                                            Ok(de) => {
                                                de.path().is_file()
                                                    && de.path().extension()
                                                    == Some(std::ffi::OsStr::new("png"))
                                            }
                                            Err(_) => false,
                                        })
                                        .collect::<Vec<_>>()
                                        .await;

                                debug!(target: "ckgreeter::callbacks::wallpaper", "got cached wallpaper images from disk");
                                photos.shuffle(&mut rand::rng());
                                debug!(target: "ckgreeter::callbacks::wallpaper", "shuffled cached wallpaper images");

                                let mut photos = tokio_stream::iter(photos);

                                while let Some(photo) = photos.next().await {
                                    match photo {
                                        Ok(photo) => {
                                            let photo_path = photo.path();
                                            let string_id = photo_path
                                                .file_prefix()
                                                .context("failed to get filename").wpaper_err(&data)?
                                                .to_string_lossy()
                                                .to_string();

                                            if last_used_lines
                                                .front()
                                                .is_some_and(|first| first == &string_id)
                                            {
                                                continue;
                                            }

                                            last_used_lines.push_back(string_id);
                                            if last_used_lines.len() > MAX_RECENT_WALLPAPERS {
                                                last_used_lines.pop_front();
                                            }

                                            debug!(target: "ckgreeter::callbacks::wallpaper", "using cached wallpaper image: {:?}", photo_path);

                                            debug!(target: "ckgreeter::callbacks::wallpaper", "resizing image with ImageMagick");
                                            let (w, h, pixels) =
                                                tokio::task::spawn_blocking(move || {
                                                    let wand = MagickWand::new();

                                                    wand.read_image(photo_path.to_str().context(
                                                        "failed to convert image path to UTF-8",
                                                    )?)?;
                                                    export_wand_rgba(&wand)
                                                })
                                                    .await.wpaper_err(&data)?.wpaper_err(&data)?;

                                            debug!(target: "ckgreeter::callbacks::wallpaper", "image resized with ImageMagick");

                                            set_background_image(
                                                &display,
                                                &data,
                                                pixels.as_slice(),
                                                w,
                                                h,
                                            );
                                            debug!(target: "ckgreeter::callbacks::wallpaper", "set background image");


                                            drop(pixels);

                                            data.set_wallpaper_author("Unknown".into());

                                            data.set_wallpaper_alt("".into());
                                            data.set_wallpaper_url("Cached wallpaper".into());

                                            debug!(target: "ckgreeter::callbacks::wallpaper", "switching background to foreground");
                                            display.invoke_switch_background();

                                            break;
                                        }
                                        Err(_err) => {
                                            continue;
                                        }
                                    }
                                }
                            }
                        }

                        if is_photo_set || !found_photo {
                            break;
                        }

                        page += 1;
                    }

                    tokio::time::timeout(
                        std::time::Duration::from_secs(5),
                        write_lines(&last_used_log, last_used_lines),
                    )
                        .await
                        .context("timed out while writing wallpaper log").wpaper_err(&data)?.wpaper_err(&data)?;
                    return anyhow::Ok(());
                }).unwrap();
            }
        });
    }
}

pub fn register_get_default_font_size(data: &GreeterData, display_weak: Weak<GreeterDisplay>) {
    data.on_get_default_font_size(move || {
        debug!(target: "ckgreeter::callbacks::font_size", "----- get default font size callback called");
        let display = display_weak.unwrap();
        let size = display.window().size();
        trace!(target: "ckgreeter::callbacks::font_size", "window size: {:?}", size);
        let font_size = ((size.width as f32).min(size.height as f32)) * 0.01;
        trace!(target: "ckgreeter::callbacks::font_size", "font size: {}", font_size);
        font_size
    });
}

pub fn register_callbacks(
    display: &GreeterDisplay,
    data: &GreeterData,
    tiles: Rc<VecModel<LoginOptionTileData>>,
    cache_dir: PathBuf,
    config: &Config,
) {
    let display_weak = display.as_weak();
    let data_weak = data.as_weak();

    debug!(target: "ckgreeter::callbacks", "Registering callbacks");
    register_login_callback(data, display_weak.clone(), tiles);
    debug!(target: "ckgreeter::callbacks", "Registered login callback");
    register_power_callback(data);
    debug!(target: "ckgreeter::callbacks", "Registered power callback");
    register_lock_state_callback(data, data_weak.clone());
    debug!(target: "ckgreeter::callbacks", "Registered lock state callback");
    register_wallpaper_callback(
        data,
        display_weak.clone(),
        data_weak.clone(),
        cache_dir,
        config,
    );
    debug!(target: "ckgreeter::callbacks", "Registered clean inactive wallpaper callback");
    register_get_default_font_size(data, display_weak.clone());
    debug!(target: "ckgreeter::callbacks", "Registered get default font size callback");
}
