use crate::ecs::world::World;
use crate::ecs::world::commands::WorldCommand;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex, OnceLock};

static ASSET_SEARCH_PATHS: OnceLock<Vec<String>> = OnceLock::new();

pub fn set_asset_search_paths(paths: Vec<String>) {
    let _ = ASSET_SEARCH_PATHS.set(paths);
}

pub fn get_asset_search_paths() -> &'static [String] {
    ASSET_SEARCH_PATHS.get().map_or(&[], |v| v.as_slice())
}

#[derive(Default)]
pub struct TextureLoadQueue {
    pending: VecDeque<TextureLoadRequest>,
    in_flight: Vec<String>,
    completed: Vec<LoadedTexture>,
    failed: usize,
}

pub struct TextureLoadRequest {
    pub url: String,
    pub name: String,
}

pub struct LoadedTexture {
    pub name: String,
    pub rgba_data: Vec<u8>,
    pub width: u32,
    pub height: u32,
}

impl TextureLoadQueue {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn queue_texture(&mut self, name: String, url: String) {
        if !self.in_flight.contains(&name)
            && !self.pending.iter().any(|r| r.name == name)
            && !self.completed.iter().any(|t| t.name == name)
        {
            self.pending.push_back(TextureLoadRequest { url, name });
        }
    }

    pub fn take_completed(&mut self) -> Vec<LoadedTexture> {
        std::mem::take(&mut self.completed)
    }

    pub fn has_pending(&self) -> bool {
        !self.pending.is_empty() || !self.in_flight.is_empty()
    }

    pub fn pending_count(&self) -> usize {
        self.pending.len() + self.in_flight.len()
    }

    pub fn failed_count(&self) -> usize {
        self.failed
    }

    pub fn processed_count(&self) -> usize {
        self.completed.len() + self.failed
    }
}

pub type SharedTextureQueue = Arc<Mutex<TextureLoadQueue>>;

pub fn create_shared_queue() -> SharedTextureQueue {
    Arc::new(Mutex::new(TextureLoadQueue::new()))
}

#[cfg(target_arch = "wasm32")]
pub fn process_texture_queue(queue: &SharedTextureQueue, max_concurrent: usize) {
    let requests: Vec<TextureLoadRequest> = {
        let mut q = queue.lock().unwrap();
        let available_slots = max_concurrent.saturating_sub(q.in_flight.len());
        let mut requests = Vec::new();
        for _ in 0..available_slots {
            if let Some(req) = q.pending.pop_front() {
                q.in_flight.push(req.name.clone());
                requests.push(req);
            }
        }
        requests
    };

    for request in requests {
        let queue_clone = Arc::clone(queue);
        let name = request.name.clone();

        ehttp::fetch(
            ehttp::Request::get(&request.url),
            move |result: ehttp::Result<ehttp::Response>| {
                let mut q = queue_clone.lock().unwrap();
                q.in_flight.retain(|n| n != &name);

                match result {
                    Ok(response) => {
                        if response.ok {
                            if let Ok(img) = image::load_from_memory(&response.bytes) {
                                let rgba = img.to_rgba8();
                                let (width, height) = rgba.dimensions();
                                q.completed.push(LoadedTexture {
                                    name,
                                    rgba_data: rgba.into_raw(),
                                    width,
                                    height,
                                });
                            } else {
                                tracing::warn!("Failed to decode image: {}", name);
                                q.failed += 1;
                            }
                        } else {
                            tracing::warn!(
                                "HTTP error loading texture {}: {}",
                                name,
                                response.status
                            );
                            q.failed += 1;
                        }
                    }
                    Err(error) => {
                        tracing::warn!("Failed to fetch texture {}: {}", name, error);
                        q.failed += 1;
                    }
                }
            },
        );
    }
}

#[cfg(not(target_arch = "wasm32"))]
pub fn process_texture_queue(queue: &SharedTextureQueue, _max_concurrent: usize) {
    use std::path::Path;

    let requests: Vec<TextureLoadRequest> = {
        let mut q = queue.lock().unwrap();
        std::mem::take(&mut q.pending).into()
    };

    for request in requests {
        let path = Path::new(&request.url);
        let bytes = if path.exists() {
            std::fs::read(path)
        } else {
            find_texture_file(&request.url)
        };

        match bytes {
            Ok(bytes) => {
                if let Ok(img) = image::load_from_memory(&bytes) {
                    let rgba = img.to_rgba8();
                    let (width, height) = rgba.dimensions();
                    let mut q = queue.lock().unwrap();
                    q.completed.push(LoadedTexture {
                        name: request.name,
                        rgba_data: rgba.into_raw(),
                        width,
                        height,
                    });
                } else {
                    tracing::warn!("Failed to decode image: {}", request.name);
                    let mut q = queue.lock().unwrap();
                    q.failed += 1;
                }
            }
            Err(error) => {
                tracing::warn!("Failed to read texture {}: {}", request.name, error);
                let mut q = queue.lock().unwrap();
                q.failed += 1;
            }
        }
    }
}

#[cfg(not(target_arch = "wasm32"))]
fn find_texture_file(url: &str) -> std::io::Result<Vec<u8>> {
    use std::path::Path;

    let candidate = Path::new(url);
    if candidate.exists() {
        return std::fs::read(candidate);
    }

    for prefix in get_asset_search_paths() {
        let candidate = Path::new(prefix).join(url);
        if candidate.exists() {
            return std::fs::read(&candidate);
        }
    }

    Err(std::io::Error::new(
        std::io::ErrorKind::NotFound,
        format!("Texture not found: {}", url),
    ))
}

pub fn queue_texture_from_path(queue: &SharedTextureQueue, path: &str) {
    let normalized = path.replace('\\', "/");
    let name = normalized.clone();

    #[cfg(target_arch = "wasm32")]
    let url = {
        if let Some(stripped) = normalized.strip_prefix("assets/") {
            stripped.to_string()
        } else {
            normalized
        }
    };

    #[cfg(not(target_arch = "wasm32"))]
    let url = normalized;

    if let Ok(mut q) = queue.lock() {
        q.queue_texture(name, url);
    }
}

#[derive(Default, Clone, Copy)]
pub struct AssetLoadingState {
    pub total_textures: usize,
    pub loaded_textures: usize,
    pub failed_textures: usize,
}

impl AssetLoadingState {
    pub fn new(total_textures: usize) -> Self {
        Self {
            total_textures,
            loaded_textures: 0,
            failed_textures: 0,
        }
    }

    pub fn progress(&self) -> f32 {
        if self.total_textures == 0 {
            return 1.0;
        }
        (self.loaded_textures + self.failed_textures) as f32 / self.total_textures as f32
    }

    pub fn is_complete(&self) -> bool {
        self.total_textures > 0
            && (self.loaded_textures + self.failed_textures) >= self.total_textures
    }
}

#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub enum AssetLoadingStatus {
    #[default]
    Loading,
    Complete,
}

pub fn process_and_load_textures(
    queue: &SharedTextureQueue,
    world: &mut World,
    loading_state: &mut AssetLoadingState,
    max_concurrent: usize,
) -> AssetLoadingStatus {
    process_texture_queue(queue, max_concurrent);

    if let Ok(mut queue_guard) = queue.lock() {
        let completed = queue_guard.take_completed();
        let failed = queue_guard.failed_count();
        loading_state.loaded_textures += completed.len();
        loading_state.failed_textures = failed;

        for loaded in completed {
            world.queue_command(WorldCommand::LoadTexture {
                name: loaded.name,
                rgba_data: loaded.rgba_data,
                width: loaded.width,
                height: loaded.height,
            });
        }

        let pending = queue_guard.has_pending();
        let all_processed =
            (loading_state.loaded_textures + failed) >= loading_state.total_textures;

        if !pending && all_processed {
            if failed > 0 {
                tracing::warn!(
                    "Asset loading complete: {} textures loaded, {} failed",
                    loading_state.loaded_textures,
                    failed
                );
            } else if loading_state.loaded_textures > 0 {
                tracing::info!(
                    "Asset loading complete: {} textures loaded",
                    loading_state.loaded_textures
                );
            }
            return AssetLoadingStatus::Complete;
        }
    }

    AssetLoadingStatus::Loading
}
