use anyhow::Context;
use anyhow::Result;
use glam::{UVec2, Vec2};
use std::{cell::RefCell, rc::Rc, sync::Arc};
use wgpu::PresentMode;
use winit::application::ApplicationHandler;
use winit::dpi::PhysicalPosition;
use winit::dpi::{LogicalSize, PhysicalSize};
use winit::event::MouseButton;
use winit::event::MouseScrollDelta;
use winit::keyboard::ModifiersState;
use winit::keyboard::PhysicalKey;
#[cfg(target_arch = "wasm32")]
use winit::platform::web::EventLoopExtWebSys;
use winit::window::{Fullscreen, WindowId};
use winit::{
    event::WindowEvent,
    event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
    window::WindowAttributes,
};

use crate::commands::*;
use crate::input::KeyCode;
use crate::input::KeyState;
use crate::platform::window::setup_window;
use crate::render::BackendState;
use crate::{app::App, engine::Engine, tasks};

use super::clock::InstantClock;
use super::key_code::to_key_code;
use crate::commands::ResourceCommand;
#[cfg(feature = "egui")]
use crate::egui::system::EguiSystem;
use crate::renderer::driver::RenderDriver;

pub struct WGPUPlatform {
    surface: wgpu::Surface<'static>,
    device: wgpu::Device,
    queue: wgpu::Queue,
    config: wgpu::SurfaceConfiguration,
    window_size: UVec2,
    window: Arc<winit::window::Window>,
    driver: RenderDriver,
    surface_view_format: wgpu::TextureFormat,
    #[cfg(feature = "egui")]
    egui: Option<EguiSystem>,
}

impl WGPUPlatform {
    async fn new(
        window: Arc<winit::window::Window>,
        present_mode: PresentMode,
        desired: UVec2,
    ) -> Result<Self> {
        let mut inner_size = window.inner_size();
        if inner_size.width == 0 || inner_size.height == 0 {
            inner_size.width = inner_size.width.max(desired.x);
            inner_size.height = inner_size.height.max(desired.y);
            let _ = window.request_inner_size(inner_size);
        }
        let window_size = UVec2::new(inner_size.width, inner_size.height);

        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default().with_env());
        let surface = instance.create_surface(window.clone())?;

        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: wgpu::PowerPreference::HighPerformance,
                compatible_surface: Some(&surface),
                force_fallback_adapter: false,
            })
            .await
            .context("Failed to find an appropriate adapter")?;

        let (device, queue) = adapter
            .request_device(&wgpu::DeviceDescriptor {
                required_features: wgpu::Features::empty(),
                required_limits: wgpu::Limits::default(),
                label: None,
                memory_hints: Default::default(),
                trace: wgpu::Trace::Off,
                experimental_features: Default::default(),
            })
            .await?;

        let capabilities = surface.get_capabilities(&adapter);
        let surface_format = capabilities
            .formats
            .iter()
            .copied()
            .find(|fmt| fmt.is_srgb())
            .unwrap_or(capabilities.formats[0]);

        let mut surface_view_format = surface_format;
        if !surface_view_format.is_srgb() {
            let srgb_candidate = surface_format.add_srgb_suffix();
            if srgb_candidate != surface_format {
                let features = adapter.get_texture_format_features(srgb_candidate);
                let supports_attachment = features
                    .allowed_usages
                    .contains(wgpu::TextureUsages::RENDER_ATTACHMENT);
                let supports_sampling = features
                    .allowed_usages
                    .contains(wgpu::TextureUsages::TEXTURE_BINDING);
                if supports_attachment && supports_sampling {
                    surface_view_format = srgb_candidate;
                }
            }
        }

        let mut view_formats = Vec::new();
        if surface_view_format != surface_format {
            view_formats.push(surface_view_format);
        }
        // Keep in-flight frames low when not using vsync to reduce backpressure hitches
        let desired_maximum_frame_latency = if present_mode == PresentMode::AutoNoVsync {
            1
        } else {
            2
        };
        let config = wgpu::SurfaceConfiguration {
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
            format: surface_format,
            width: window_size.x,
            height: window_size.y,
            present_mode,
            desired_maximum_frame_latency,
            alpha_mode: capabilities.alpha_modes[0],
            view_formats,
        };
        surface.configure(&device, &config);

        let backend_state = BackendState {
            device: device.clone(),
            queue: queue.clone(),
            surface_config: config.clone(),
            surface_view_format,
        };
        let driver = RenderDriver::new(&backend_state);

        #[cfg(feature = "egui")]
        let egui = Some(EguiSystem::new(&window, &backend_state));

        Ok(Self {
            window,
            surface,
            device,
            queue,
            config,
            window_size,
            driver,
            surface_view_format,
            #[cfg(feature = "egui")]
            egui,
        })
    }

    // Return visible of window
    // Note in wayland and other few platforms the visible of window is not supported.
    // This function always returns false
    fn is_window_visible(&self) -> bool {
        self.window.as_ref().is_visible().unwrap_or_default()
    }

    fn resize(&mut self, size: PhysicalSize<u32>) {
        self.window_size = UVec2::new(size.width, size.height);
        self.config.width = size.width;
        self.config.height = size.height;
        self.surface.configure(&self.device, &self.config);
        self.driver.resize();
    }

    fn handle_resource_cmds(&mut self, cmds: Vec<ResourceCommand>) {
        self.driver.apply_resource_cmds(cmds);
    }

    fn render_frame(
        &mut self,
        g: &Engine,
        draw_commands: Vec<Command>,
        draw3d: Vec<crate::renderer::shader3d::draw3d::Draw3D>,
    ) {
        if self.config.width == 0 || self.config.height == 0 {
            // Window is zero-sized (e.g., minimized); skip rendering until we get a resize.
            return;
        }

        let frame = match self.surface.get_current_texture() {
            Ok(frame) => frame,
            Err(wgpu::SurfaceError::Outdated) => {
                // Reconfigure the surface when it becomes outdated (e.g., due to window resize)
                self.surface.configure(&self.device, &self.config);
                match self.surface.get_current_texture() {
                    Ok(frame) => frame,
                    Err(e) => {
                        log::error!(
                            "Failed to acquire next swap chain texture after reconfiguration: {e:?}"
                        );
                        return;
                    }
                }
            }
            Err(e) => {
                log::error!("Failed to acquire next swap chain texture: {e:?}");
                return;
            }
        };
        let mut encoder = self
            .device
            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Render Encoder"),
            });
        self.driver
            .record_frame(g, draw_commands, draw3d, &mut encoder, &frame);

        #[cfg(feature = "egui")]
        if let Some(egui) = self.egui.as_mut() {
            let frame_view = frame.texture.create_view(&wgpu::TextureViewDescriptor {
                label: Some("Swapchain sRGB View (egui)"),
                format: Some(self.surface_view_format),
                ..Default::default()
            });
            egui.paint(
                &self.window,
                &self.device,
                &self.queue,
                &mut encoder,
                &frame_view,
                self.window_size,
            );
        }

        self.queue.submit(Some(encoder.finish()));

        #[cfg(feature = "egui")]
        if let Some(egui) = self.egui.as_mut() {
            egui.after_submit();
        }

        self.window.pre_present_notify();
        frame.present();
        self.window.request_redraw();
    }

    // Texture creation is handled inside the RenderDriver now.

    pub fn run(app: App) -> Result<()>
    where
        Self: Sized,
    {
        let event_loop = EventLoop::new().unwrap();
        event_loop.set_control_flow(ControlFlow::Poll);
        let app = AppContext {
            config: app,
            engine: None,
            state: None,
            modifiers: None,
            pending_state: None,
        };
        #[cfg(target_arch = "wasm32")]
        {
            event_loop.spawn_app(app);
        }
        #[cfg(not(target_arch = "wasm32"))]
        {
            let mut app = app;
            event_loop.run_app(&mut app).unwrap();
        }
        Ok(())
    }
}

struct AppContext {
    config: App,
    engine: Option<Engine>,
    state: Option<WGPUPlatform>,
    modifiers: Option<ModifiersState>,
    pending_state: Option<Rc<RefCell<Option<Result<WGPUPlatform>>>>>,
}

impl AppContext {
    fn install_state(&mut self, state: WGPUPlatform) {
        self.state = Some(state);

        let desired = self.config.window;
        let window_size = if let Some(state) = self.state.as_ref() {
            let _ = state
                .window
                .request_inner_size(LogicalSize::new(desired.x, desired.y));
            state.window.inner_size()
        } else {
            PhysicalSize::new(desired.x, desired.y)
        };

        if self.engine.is_none() {
            let state = self.state.as_ref().expect("state set before engine init");
            let backend_state = BackendState {
                device: state.device.clone(),
                queue: state.queue.clone(),
                surface_config: state.config.clone(),
                surface_view_format: state.surface_view_format,
            };
            let mut engine = Engine::new(InstantClock::default(), backend_state);
            if let Some(setup) = self.config.setup.take() {
                engine.init(setup);
            }
            self.engine = Some(engine);
        }

        self.resize(window_size);
        if let Some(state) = self.state.as_ref() {
            state.window.request_redraw();
        }
    }

    fn poll_pending_state(&mut self) {
        let result = self.pending_state.as_ref().and_then(|pending| {
            let mut pending_ref = pending.borrow_mut();
            pending_ref.take()
        });

        if let Some(result) = result {
            self.pending_state = None;
            match result {
                Ok(state) => self.install_state(state),
                Err(err) => log::error!("Failed to initialize WGPU platform: {err:?}"),
            }
        }
    }

    fn resize(&mut self, size: PhysicalSize<u32>) {
        let Some(state) = self.state.as_mut() else {
            return;
        };
        if size.width > 0 && size.height > 0 {
            let scale_factor = state.window.scale_factor();
            state.resize(size);
            let Some(engine) = self.engine.as_mut() else {
                return;
            };
            engine.on_resize(UVec2::new(size.width, size.height), scale_factor);
        }
    }

    fn update(&mut self, event_loop: &ActiveEventLoop, render: bool) {
        self.poll_pending_state();

        if self.state.is_none() || self.engine.is_none() {
            return;
        }

        let (draw_commands, res_commands, draw3d, exit_requested) = {
            let engine = self.engine.as_mut().unwrap();

            #[cfg(feature = "egui")]
            {
                let state = self.state.as_mut().unwrap();
                if let Some(egui) = state.egui.as_mut() {
                    let ctx = egui.begin_frame(&state.window).clone();
                    engine.update();
                    engine.run_egui_layers(&ctx);
                    egui.end_frame(&state.window);
                }
            }

            #[cfg(not(feature = "egui"))]
            engine.update();

            (
                engine.take_draw_commands(),
                engine.take_resource_commands(),
                engine.take_3d_draws(),
                engine.exit,
            )
        };

        if exit_requested && !event_loop.exiting() {
            event_loop.exit();
            return;
        }

        let state = self.state.as_mut().unwrap();

        if render {
            state.handle_resource_cmds(res_commands);
            let engine = self.engine.as_ref().unwrap();
            state.render_frame(engine, draw_commands, draw3d);
        } else {
            state.handle_resource_cmds(res_commands);
        }
    }
}

impl ApplicationHandler for AppContext {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        let App {
            title,
            window,
            vsync,
            resizable,
            fullscreen,
            cursor_visible,
            ..
        } = &self.config;
        let mut window_attrs = WindowAttributes::default()
            .with_title(title.to_owned())
            .with_resizable(*resizable)
            .with_inner_size(LogicalSize::new(window.x, window.y));
        if *fullscreen {
            window_attrs = window_attrs.with_fullscreen(Some(Fullscreen::Borderless(None)));
        }
        let window = Arc::new(event_loop.create_window(window_attrs).unwrap());
        window.set_cursor_visible(*cursor_visible);
        setup_window(&window, self.config.window);
        let present_mode = if *vsync {
            PresentMode::AutoVsync
        } else {
            PresentMode::AutoNoVsync
        };

        let pending = Rc::new(RefCell::new(None));
        self.pending_state = Some(pending.clone());
        let window_clone = window.clone();
        let desired = self.config.window;
        tasks::spawn_local({
            let pending = pending.clone();
            async move {
                let result = WGPUPlatform::new(window_clone.clone(), present_mode, desired).await;
                *pending.borrow_mut() = Some(result);
                window_clone.request_redraw();
            }
        });
    }

    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        self.poll_pending_state();

        if self.state.is_none() || self.engine.is_none() {
            return;
        }

        // Only run in background (no window is visible) and not vsync
        // Because we cannot detect visible of window on wayland and some platforms,
        // the FPS will increased very high even we enable vsync, so we skip update while vsync is
        // enabled to keep FPS to approach vsync rate.
        let is_visible = self.state.as_ref().unwrap().is_window_visible();
        if !self.config.vsync && !is_visible {
            self.update(event_loop, false);
        }
    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
        self.poll_pending_state();

        #[cfg(feature = "egui")]
        if let Some(state) = self.state.as_mut()
            && let Some(egui) = state.egui.as_mut()
        {
            let response = egui.on_window_event(&state.window, &event);
            if response.consumed {
                return;
            }
        }

        match event {
            WindowEvent::CloseRequested => {
                log::debug!("The close button was pressed; stopping");
                event_loop.exit();
            }
            WindowEvent::RedrawRequested => {
                self.update(event_loop, true);
            }
            WindowEvent::Resized(size) => {
                self.resize(size);
            }
            WindowEvent::KeyboardInput { event, .. } => {
                let Some(engine) = self.engine.as_mut() else {
                    return;
                };
                let key_state = if event.state.is_pressed() {
                    KeyState::down()
                } else {
                    KeyState::up()
                };
                let PhysicalKey::Code(code) = event.physical_key else {
                    return;
                };

                // Detect ALT+Enter press to toggle fullscreen
                if self.modifiers.is_some_and(|m| m.alt_key())
                    && key_state.is_down()
                    && code == winit::keyboard::KeyCode::Enter
                {
                    if let Some(state) = self.state.as_mut() {
                        match state.window.fullscreen() {
                            Some(_) => {
                                state.window.set_fullscreen(None);
                            }
                            None => {
                                state
                                    .window
                                    .set_fullscreen(Some(Fullscreen::Borderless(None)));
                            }
                        }
                    }
                    return;
                }

                let code = to_key_code(code);
                if code != KeyCode::Invalid {
                    engine.input.set_input_state(code, key_state);
                }
            }
            WindowEvent::ModifiersChanged(m) => {
                self.modifiers.replace(m.state());
            }
            WindowEvent::MouseInput { state, button, .. } => {
                let Some(engine) = self.engine.as_mut() else {
                    return;
                };
                // Mouse buttons
                let code = match button {
                    MouseButton::Left => KeyCode::MouseLeft,
                    MouseButton::Middle => KeyCode::MouseMiddle,
                    MouseButton::Right => KeyCode::MouseRight,
                    _ => return,
                };
                let state = if state.is_pressed() {
                    KeyState::down()
                } else {
                    KeyState::up()
                };
                engine.input.set_input_state(code, state);
            }
            WindowEvent::MouseWheel { delta, .. } => {
                let Some(engine) = self.engine.as_mut() else {
                    return;
                };
                let MouseScrollDelta::LineDelta(_x, y) = delta else {
                    return;
                };
                // Mouse wheel
                let code = if y > 0.0 {
                    KeyCode::MouseWheelUp
                } else {
                    KeyCode::MouseWheelDown
                };
                engine.input.set_input_state(code, KeyState::down());
            }
            WindowEvent::CursorMoved {
                position: PhysicalPosition { x, y },
                ..
            } => {
                let Some(engine) = self.engine.as_mut() else {
                    return;
                };
                // Mouse position is left-top
                let mut mp = Vec2::new(x as f32, y as f32) * engine.render.inv_screen_scale;
                // We convert position to left-bottom by revert y-axis
                let logical_size = engine.render.logical_size();
                // revert y-axis
                mp.y = logical_size.y - mp.y;
                // center the mouse position
                mp -= logical_size * 0.5;
                engine.input.set_mouse_pos(mp);
            }
            _ => (),
        }
    }
}
