use std::{collections::HashSet, sync::LazyLock};

use tauri_plugin_dialog::MessageDialogBuilder;
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
use tauri_specta::Event as _;
use tokio::sync::Mutex;
use tracing::warn;

use crate::{
    get_app_handle, lock_r, lock_w,
    remotes::{dolls::DollsRemote, friends::FriendRemote, user::UserRemote},
    services::{
        app_events::{ActiveDollSpriteChanged, AppDataRefreshed},
        friends, neko_positions, sprite,
    },
    state::FDOLL,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AppDataRefreshScope {
    All,
    User,
    Friends,
    Dolls,
}

static REFRESH_IN_FLIGHT: LazyLock<Mutex<HashSet<AppDataRefreshScope>>> =
    LazyLock::new(|| Mutex::new(HashSet::new()));
static REFRESH_PENDING: LazyLock<Mutex<HashSet<AppDataRefreshScope>>> =
    LazyLock::new(|| Mutex::new(HashSet::new()));

pub async fn init_app_data_scoped(scope: AppDataRefreshScope) {
    loop {
        {
            let mut in_flight = REFRESH_IN_FLIGHT.lock().await;
            if in_flight.contains(&scope) {
                let mut pending = REFRESH_PENDING.lock().await;
                pending.insert(scope);
                return;
            }
            in_flight.insert(scope);
        }

        let result: Result<(), ()> = async {
            let user_remote = UserRemote::new();
            let friend_remote = FriendRemote::new();
            let dolls_remote = DollsRemote::new();

            if matches!(scope, AppDataRefreshScope::All | AppDataRefreshScope::User) {
                match user_remote.get_user(None).await {
                    Ok(user) => {
                        let mut guard = lock_w!(FDOLL);
                        guard.user_data.user = Some(user);
                        drop(guard);
                        neko_positions::sync_from_app_data();
                    }
                    Err(error) => {
                        warn!("Failed to fetch user profile: {}", error);
                        show_refresh_error_dialog(
                            "Network Error",
                            "Failed to fetch user profile. You may be offline.",
                        );
                        return Err(());
                    }
                }
            }

            if matches!(
                scope,
                AppDataRefreshScope::All | AppDataRefreshScope::Friends
            ) {
                match friend_remote.get_friends().await {
                    Ok(friends_list) => {
                        let mut guard = lock_w!(FDOLL);
                        guard.user_data.friends = Some(friends_list);
                        drop(guard);
                        friends::sync_from_app_data();
                    }
                    Err(error) => {
                        warn!("Failed to fetch friends list: {}", error);
                        show_refresh_error_dialog(
                            "Network Error",
                            "Failed to fetch friends list. You may be offline.",
                        );
                        return Err(());
                    }
                }
            }

            if matches!(scope, AppDataRefreshScope::All | AppDataRefreshScope::Dolls) {
                match dolls_remote.get_dolls().await {
                    Ok(dolls) => {
                        let mut guard = lock_w!(FDOLL);
                        guard.user_data.dolls = Some(dolls);
                    }
                    Err(error) => {
                        warn!("Failed to fetch dolls list: {}", error);
                        show_refresh_error_dialog(
                            "Network Error",
                            "Failed to fetch dolls list. You may be offline.",
                        );
                        return Err(());
                    }
                }
            }

            emit_refresh_events(scope);

            Ok(())
        }
        .await;

        {
            let mut in_flight = REFRESH_IN_FLIGHT.lock().await;
            in_flight.remove(&scope);
        }

        let rerun = {
            let mut pending = REFRESH_PENDING.lock().await;
            pending.remove(&scope)
        };

        if rerun {
            continue;
        }

        if result.is_err() {
            return;
        }

        break;
    }
}

pub fn clear_app_data() {
    let mut guard = lock_w!(FDOLL);
    guard.user_data.dolls = None;
    guard.user_data.user = None;
    guard.user_data.friends = None;
    drop(guard);
    friends::clear();
}

fn emit_refresh_events(scope: AppDataRefreshScope) {
    let guard = lock_r!(FDOLL);
    let app_data = guard.user_data.clone();
    drop(guard);

    if let Err(error) = AppDataRefreshed(app_data).emit(get_app_handle()) {
        warn!("Failed to emit app-data-refreshed event: {}", error);
        show_refresh_error_dialog(
            "Sync Error",
            "Could not broadcast refreshed data to the UI. Some data may be stale.",
        );
    }

    if matches!(
        scope,
        AppDataRefreshScope::All | AppDataRefreshScope::User | AppDataRefreshScope::Dolls
    ) {
        match sprite::get_active_doll_sprite_base64() {
            Ok(sprite_b64) => {
                if let Err(error) = ActiveDollSpriteChanged(sprite_b64).emit(get_app_handle()) {
                    warn!("Failed to emit active-doll-sprite-changed event: {}", error);
                }
            }
            Err(error) => {
                warn!("Failed to generate active doll sprite: {}", error);
            }
        }
    }
}

fn show_refresh_error_dialog(title: &str, message: &str) {
    let handle = get_app_handle();
    MessageDialogBuilder::new(handle.dialog().clone(), title, message)
        .kind(MessageDialogKind::Error)
        .show(|_| {});
}
