use crate::ecs::window::resources::{SecondaryWindowInput, SecondaryWindowState};
use crate::ecs::world::World;
use crate::run::State;
use std::collections::HashMap;
use std::sync::Arc;
use winit::event::{DeviceEvent, WindowEvent};

#[cfg(feature = "egui")]
mod egui_support;

pub struct SecondaryWindowInfo {
    pub handle: Arc<winit::window::Window>,
    pub index: usize,
    #[cfg(feature = "egui")]
    pub egui_state: Option<egui_winit::State>,
}

pub struct MultiWindowContext {
    pub state: Box<dyn State>,
    pub world: World,
    pub renderer: Option<Box<dyn crate::ecs::world::Render>>,
    primary_window_id: Option<winit::window::WindowId>,
    secondary_windows: HashMap<winit::window::WindowId, SecondaryWindowInfo>,
    focused_window: Option<winit::window::WindowId>,
    next_secondary_index: usize,
    #[cfg(not(target_arch = "wasm32"))]
    pub snapshot_path: Option<std::path::PathBuf>,
    #[cfg(not(target_arch = "wasm32"))]
    pub snapshot_queued: bool,
}

impl MultiWindowContext {
    pub fn new(state: Box<dyn State>, world: World) -> Self {
        Self {
            state,
            world,
            renderer: None,
            primary_window_id: None,
            secondary_windows: HashMap::new(),
            focused_window: None,
            next_secondary_index: 1,
            #[cfg(not(target_arch = "wasm32"))]
            snapshot_path: std::env::var("NIGHTSHADE_SNAPSHOT_PATH")
                .ok()
                .map(std::path::PathBuf::from),
            #[cfg(not(target_arch = "wasm32"))]
            snapshot_queued: false,
        }
    }

    fn is_primary_window(&self, window_id: winit::window::WindowId) -> bool {
        self.primary_window_id == Some(window_id)
    }

    fn process_pending_spawns(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
        let pending: Vec<_> = self
            .world
            .resources
            .secondary_windows
            .pending_spawns
            .drain(..)
            .collect();
        for request in pending {
            let attributes = winit::window::Window::default_attributes()
                .with_title(&request.title)
                .with_inner_size(winit::dpi::LogicalSize::new(request.width, request.height));

            let Ok(window) = event_loop.create_window(attributes) else {
                continue;
            };

            let window_handle = Arc::new(window);
            let index = self.next_secondary_index;
            self.next_secondary_index += 1;

            if let Some(renderer) = &mut self.renderer
                && let Err(error) = renderer.create_secondary_surface(
                    index,
                    window_handle.clone(),
                    request.width.max(1),
                    request.height.max(1),
                )
            {
                tracing::error!("Failed to create secondary surface {index}: {error}");
                continue;
            }

            #[cfg(feature = "egui")]
            let egui_state = if request.egui_enabled {
                egui_support::create_secondary_egui_state(&mut self.renderer, index, &window_handle)
            } else {
                None
            };

            let winit_id = window_handle.id();
            self.secondary_windows.insert(
                winit_id,
                SecondaryWindowInfo {
                    handle: window_handle,
                    index,
                    #[cfg(feature = "egui")]
                    egui_state,
                },
            );

            self.world
                .resources
                .secondary_windows
                .states
                .push(SecondaryWindowState {
                    index,
                    title: request.title,
                    size: (request.width, request.height),
                    is_focused: false,
                    input: SecondaryWindowInput::default(),
                    close_requested: false,
                    egui_enabled: request.egui_enabled,
                });
        }
    }

    fn process_close_requests(&mut self) {
        let closed_indices: Vec<usize> = self
            .world
            .resources
            .secondary_windows
            .states
            .iter()
            .filter(|window| window.close_requested)
            .map(|window| window.index)
            .collect();

        for index in &closed_indices {
            if let Some(renderer) = &mut self.renderer {
                renderer.remove_secondary_surface(*index);
            }
            self.secondary_windows
                .retain(|_, info| info.index != *index);
        }

        self.world
            .resources
            .secondary_windows
            .states
            .retain(|window| !window.close_requested);
    }

    fn handle_secondary_window_event(
        &mut self,
        window_id: winit::window::WindowId,
        event: WindowEvent,
    ) {
        let Some(info) = self.secondary_windows.get_mut(&window_id) else {
            return;
        };
        let index = info.index;

        #[cfg(feature = "egui")]
        if let Some(egui_state) = info.egui_state.as_mut() {
            let _ = egui_state.on_window_event(&info.handle, &event);
        }

        match event {
            WindowEvent::Resized(winit::dpi::PhysicalSize { width, height })
                if width > 0 && height > 0 =>
            {
                if let Some(renderer) = &mut self.renderer
                    && let Err(error) = renderer.resize_secondary_surface(index, width, height)
                {
                    tracing::error!("Failed to resize secondary surface {index}: {error}");
                }
                if let Some(state) = self
                    .world
                    .resources
                    .secondary_windows
                    .states
                    .iter_mut()
                    .find(|w| w.index == index)
                {
                    state.size = (width, height);
                }
            }
            WindowEvent::CloseRequested => {
                if let Some(state) = self
                    .world
                    .resources
                    .secondary_windows
                    .states
                    .iter_mut()
                    .find(|w| w.index == index)
                {
                    state.close_requested = true;
                }
            }
            WindowEvent::Focused(focused) => {
                if focused {
                    self.focused_window = Some(window_id);
                    self.world.resources.secondary_windows.focused_index = Some(index);
                    self.world.resources.window.is_focused = false;
                } else if self.focused_window == Some(window_id) {
                    self.focused_window = None;
                    self.world.resources.secondary_windows.focused_index = None;
                }
                if let Some(state) = self
                    .world
                    .resources
                    .secondary_windows
                    .states
                    .iter_mut()
                    .find(|w| w.index == index)
                {
                    state.is_focused = focused;
                }
            }
            WindowEvent::CursorMoved { position, .. } => {
                if let Some(state) = self
                    .world
                    .resources
                    .secondary_windows
                    .states
                    .iter_mut()
                    .find(|w| w.index == index)
                {
                    let new_pos = nalgebra_glm::Vec2::new(position.x as f32, position.y as f32);
                    state.input.mouse_position_delta = new_pos - state.input.mouse_position;
                    state.input.mouse_position = new_pos;
                    state
                        .input
                        .mouse_state
                        .set(crate::ecs::input::resources::MouseState::MOVED, true);
                }
            }
            WindowEvent::MouseInput { button, state, .. } => {
                if let Some(window_state) = self
                    .world
                    .resources
                    .secondary_windows
                    .states
                    .iter_mut()
                    .find(|w| w.index == index)
                {
                    let pressed = state == winit::event::ElementState::Pressed;
                    let released = state == winit::event::ElementState::Released;
                    use crate::ecs::input::resources::MouseState;
                    match button {
                        winit::event::MouseButton::Left => {
                            let was_clicked = window_state
                                .input
                                .mouse_state
                                .contains(MouseState::LEFT_CLICKED);
                            window_state
                                .input
                                .mouse_state
                                .set(MouseState::LEFT_CLICKED, pressed);
                            if pressed && !was_clicked {
                                window_state
                                    .input
                                    .mouse_state
                                    .set(MouseState::LEFT_JUST_PRESSED, true);
                            }
                            if released && was_clicked {
                                window_state
                                    .input
                                    .mouse_state
                                    .set(MouseState::LEFT_JUST_RELEASED, true);
                            }
                        }
                        winit::event::MouseButton::Middle => {
                            let was_clicked = window_state
                                .input
                                .mouse_state
                                .contains(MouseState::MIDDLE_CLICKED);
                            window_state
                                .input
                                .mouse_state
                                .set(MouseState::MIDDLE_CLICKED, pressed);
                            if pressed && !was_clicked {
                                window_state
                                    .input
                                    .mouse_state
                                    .set(MouseState::MIDDLE_JUST_PRESSED, true);
                            }
                            if released && was_clicked {
                                window_state
                                    .input
                                    .mouse_state
                                    .set(MouseState::MIDDLE_JUST_RELEASED, true);
                            }
                        }
                        winit::event::MouseButton::Right => {
                            let was_clicked = window_state
                                .input
                                .mouse_state
                                .contains(MouseState::RIGHT_CLICKED);
                            window_state
                                .input
                                .mouse_state
                                .set(MouseState::RIGHT_CLICKED, pressed);
                            if pressed && !was_clicked {
                                window_state
                                    .input
                                    .mouse_state
                                    .set(MouseState::RIGHT_JUST_PRESSED, true);
                            }
                            if released && was_clicked {
                                window_state
                                    .input
                                    .mouse_state
                                    .set(MouseState::RIGHT_JUST_RELEASED, true);
                            }
                        }
                        _ => {}
                    }
                }
            }
            WindowEvent::KeyboardInput {
                event:
                    winit::event::KeyEvent {
                        physical_key: winit::keyboard::PhysicalKey::Code(key_code),
                        state,
                        ref text,
                        ..
                    },
                ..
            } => {
                if let Some(window_state) = self
                    .world
                    .resources
                    .secondary_windows
                    .states
                    .iter_mut()
                    .find(|w| w.index == index)
                {
                    *window_state
                        .input
                        .keyboard_keystates
                        .entry(key_code)
                        .or_insert(state) = state;
                    let pressed = state == winit::event::ElementState::Pressed;
                    window_state.input.frame_keys.push((key_code, pressed));
                    if pressed && let Some(text) = text {
                        for character in text.chars() {
                            window_state.input.frame_chars.push(character);
                        }
                    }
                }
            }
            WindowEvent::MouseWheel { delta, .. } => {
                if let Some(window_state) = self
                    .world
                    .resources
                    .secondary_windows
                    .states
                    .iter_mut()
                    .find(|w| w.index == index)
                {
                    let (horizontal_delta, vertical_delta) = match delta {
                        winit::event::MouseScrollDelta::LineDelta(horizontal, vertical) => {
                            (horizontal, vertical)
                        }
                        winit::event::MouseScrollDelta::PixelDelta(position) => {
                            (position.x as f32 / 120.0, position.y as f32 / 120.0)
                        }
                    };
                    window_state.input.mouse_wheel_delta =
                        nalgebra_glm::Vec2::new(horizontal_delta, vertical_delta);
                    window_state
                        .input
                        .mouse_state
                        .set(crate::ecs::input::resources::MouseState::SCROLLED, true);
                }
            }
            _ => {}
        }
    }
}

impl winit::application::ApplicationHandler for MultiWindowContext {
    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
        #[allow(unused_mut)]
        let mut attributes = winit::window::Window::default_attributes();

        #[cfg(not(target_arch = "wasm32"))]
        {
            attributes = attributes.with_title(self.state.title());

            #[cfg(feature = "assets")]
            if let Some(icon_bytes) = self.state.icon_bytes()
                && let Ok(icon_image) = image::load_from_memory(icon_bytes)
            {
                let icon_rgba = icon_image.to_rgba8();
                let (width, height) = icon_rgba.dimensions();
                if let Ok(icon) =
                    winit::window::Icon::from_rgba(icon_rgba.into_raw(), width, height)
                {
                    attributes = attributes.with_window_icon(Some(icon));
                }
            }
        }

        let Ok(window) = event_loop.create_window(attributes) else {
            return;
        };

        let window_handle = Arc::new(window);
        self.primary_window_id = Some(window_handle.id());
        self.focused_window = Some(window_handle.id());
        self.world.resources.window.cached_scale_factor = window_handle.scale_factor() as f32;
        self.world.resources.window.handle = Some(window_handle.clone());
        self.world.resources.window.window_index = 0;
        self.world.resources.window.is_focused = true;

        #[cfg(not(target_arch = "wasm32"))]
        let dimension = window_handle.inner_size();

        #[cfg(all(not(target_arch = "wasm32"), feature = "async_runtime"))]
        {
            let runtime = tokio::runtime::Runtime::new().unwrap();
            match runtime.block_on(crate::render::core::create_renderer(
                window_handle.clone(),
                dimension.width,
                dimension.height,
            )) {
                Ok(mut renderer) => {
                    if let Err(error) = renderer.configure_with_state(self.state.as_mut()) {
                        tracing::error!("Failed to configure renderer with state: {error}");
                    }
                    self.renderer = Some(renderer);
                }
                Err(error) => {
                    tracing::error!("Failed to create renderer: {error}");
                }
            }
        }

        #[cfg(all(not(target_arch = "wasm32"), not(feature = "async_runtime")))]
        {
            match pollster::block_on(crate::render::core::create_renderer(
                window_handle.clone(),
                dimension.width,
                dimension.height,
            )) {
                Ok(mut renderer) => {
                    if let Err(error) = renderer.configure_with_state(self.state.as_mut()) {
                        tracing::error!("Failed to configure renderer with state: {error}");
                    }
                    self.renderer = Some(renderer);
                }
                Err(error) => {
                    tracing::error!("Failed to create renderer: {error}");
                }
            }
        }

        #[cfg(not(target_arch = "wasm32"))]
        {
            #[cfg(feature = "egui")]
            crate::run::initialize_user_interface(&mut self.world);
            self.world.resources.frame_schedule = crate::schedule::build_default_frame_schedule();
            self.state.initialize(&mut self.world);
            crate::ecs::camera::systems::update_camera_aspect_ratios(&mut self.world);

            if self.world.resources.graphics.use_fullscreen
                && let Some(window_handle) = &self.world.resources.window.handle
            {
                window_handle.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
            }

            if let Some(window_handle) = &self.world.resources.window.handle {
                window_handle.set_cursor_visible(self.world.resources.graphics.show_cursor);
            }
        }
    }

    fn window_event(
        &mut self,
        event_loop: &winit::event_loop::ActiveEventLoop,
        window_id: winit::window::WindowId,
        event: WindowEvent,
    ) {
        if self.is_primary_window(window_id) {
            if self.world.resources.window.should_exit
                || matches!(event, WindowEvent::CloseRequested)
            {
                event_loop.exit();
                return;
            }

            match &event {
                WindowEvent::Focused(focused) => {
                    self.world.resources.window.is_focused = *focused;
                    if *focused {
                        self.focused_window = Some(window_id);
                        self.world.resources.secondary_windows.focused_index = None;
                    }
                }
                WindowEvent::ScaleFactorChanged { .. } => {
                    if let Some(handle) = self.world.resources.window.handle.as_ref() {
                        self.world.resources.window.cached_scale_factor =
                            handle.scale_factor() as f32;
                    }
                    #[cfg(feature = "egui")]
                    crate::run::update_egui_scale_factor(&mut self.world);
                }
                WindowEvent::Resized(winit::dpi::PhysicalSize { width, height })
                    if *width > 0 && *height > 0 =>
                {
                    self.world.resources.window.cached_viewport_size = Some((*width, *height));
                    if let Some(handle) = self.world.resources.window.handle.as_ref() {
                        self.world.resources.window.cached_scale_factor =
                            handle.scale_factor() as f32;
                    }

                    if let Some(renderer) = &mut self.renderer
                        && let Err(error) = renderer.resize_surface(*width, *height)
                    {
                        tracing::error!("Failed to resize surface: {error}");
                    }

                    #[cfg(feature = "egui")]
                    crate::run::update_egui_scale_factor(&mut self.world);
                }
                _ => {}
            }

            if let Some(new_state) =
                crate::run::step(&mut self.world, &mut self.state, &mut self.renderer, &event)
            {
                self.state = new_state;
            }

            #[cfg(not(target_arch = "wasm32"))]
            if let Some(path) = &self.snapshot_path {
                let uptime = self.world.resources.window.timing.uptime_milliseconds;
                if !self.snapshot_queued && uptime >= 4000 {
                    crate::ecs::world::commands::capture_screenshot_to_path(
                        &mut self.world,
                        path.clone(),
                    );
                    self.snapshot_queued = true;
                } else if self.snapshot_queued && uptime >= 5000 {
                    self.world.resources.window.should_exit = true;
                }
            }

            crate::run::request_redraw_system(&mut self.world);
        } else {
            self.handle_secondary_window_event(window_id, event);
        }
    }

    fn device_event(
        &mut self,
        _event_loop: &winit::event_loop::ActiveEventLoop,
        _device_id: winit::event::DeviceId,
        event: DeviceEvent,
    ) {
        if let DeviceEvent::MouseMotion { delta } = event
            && let Some(focused_id) = self.focused_window
        {
            if self.is_primary_window(focused_id) {
                let mouse = &mut self.world.resources.input.mouse;
                mouse.raw_mouse_delta.x += delta.0 as f32;
                mouse.raw_mouse_delta.y += delta.1 as f32;
            } else if let Some(info) = self.secondary_windows.get(&focused_id) {
                let index = info.index;
                if let Some(window_state) = self
                    .world
                    .resources
                    .secondary_windows
                    .states
                    .iter_mut()
                    .find(|w| w.index == index)
                {
                    window_state.input.raw_mouse_delta.x += delta.0 as f32;
                    window_state.input.raw_mouse_delta.y += delta.1 as f32;
                }
            }
        }
    }

    fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
        self.process_pending_spawns(event_loop);
        self.process_close_requests();
        #[cfg(feature = "egui")]
        self.process_secondary_egui();
        for state in &mut self.world.resources.secondary_windows.states {
            state.input.frame_keys.clear();
            state.input.frame_chars.clear();
        }
        crate::run::request_redraw_system(&mut self.world);
    }

    fn exiting(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {}
}
