use std::rc::Rc;

use exr::prelude::{ReadChannels, ReadLayers};
use glam::{Vec2, Vec3};
use wgpu::util::DeviceExt;

use crate::{
    components::MeshComponent,
    entity::EntityHandle,
    mesh::{Mesh, Vertex},
    render,
    world::{Transform, World},
};

pub struct SnowConfig
{
    pub depth_map_path: String,
    pub heightmap_path: String,
    pub terrain_size: Vec2,
    pub resolution: (u32, u32),
}

impl SnowConfig
{
    pub fn new(depth_map_path: &str, heightmap_path: &str, terrain_size: Vec2, resolution: (u32, u32)) -> Self
    {
        Self {
            depth_map_path: depth_map_path.to_string(),
            heightmap_path: heightmap_path.to_string(),
            terrain_size,
            resolution,
        }
    }

    pub fn default() -> Self
    {
        Self {
            depth_map_path: "textures/snow_depth.exr".to_string(),
            heightmap_path: "textures/terrain_heightmap.exr".to_string(),
            terrain_size: Vec2::new(1000.0, 1000.0),
            resolution: (1000, 1000),
        }
    }
}

pub struct SnowLayer
{
    pub entity: EntityHandle,
    pub depth_texture: wgpu::Texture,
    pub depth_texture_view: wgpu::TextureView,
    pub depth_bind_group: wgpu::BindGroup,
    pub width: u32,
    pub height: u32,
    pub deform_bind_group: wgpu::BindGroup,
    pub deform_pipeline: wgpu::ComputePipeline,
    pub deform_params_buffer: wgpu::Buffer,
}

impl SnowLayer
{
    pub fn load(world: &mut World, config: &SnowConfig) -> anyhow::Result<Self>
    {
        println!("\n=== Loading Snow Layer ===");
        println!("Depth map path: {}", config.depth_map_path);
        println!("Heightmap path: {}", config.heightmap_path);
        println!("Terrain size: {:?}", config.terrain_size);

        let (depth_data, width, height) = Self::load_depth_map(&config.depth_map_path)?;
        let (heightmap_data, hm_width, hm_height) = Self::load_depth_map(&config.heightmap_path)?;

        if width != hm_width || height != hm_height {
            anyhow::bail!("Snow depth map ({}×{}) and heightmap ({}×{}) dimensions don't match!",
                width, height, hm_width, hm_height);
        }

        println!("Using EXR dimensions: {}×{}", width, height);

        let (depth_texture, depth_texture_view, depth_bind_group) =
            Self::create_depth_texture(&depth_data, width, height);

        let mesh = Self::generate_snow_mesh(&depth_data, &heightmap_data, width, height, config.terrain_size);
        let num_indices = mesh.num_indices;

        let entity = world.spawn();
        world.transforms.insert(entity, Transform::IDENTITY);

        if num_indices > 0 {
            world.meshes.insert(
                entity,
                MeshComponent {
                    mesh: Rc::new(mesh),
                    pipeline: render::Pipeline::Snow,
                    instance_buffer: None,
                    num_instances: 1,
                },
            );
            println!("Snow mesh created with {} indices", num_indices);
        } else {
            println!("⚠️  No snow mesh created - all depth values are zero");
        }

        let (deform_pipeline, deform_bind_group, deform_params_buffer) =
            Self::create_deform_pipeline(&depth_texture_view);

        Ok(Self {
            entity,
            depth_texture,
            depth_texture_view,
            depth_bind_group,
            width,
            height,
            deform_bind_group,
            deform_pipeline,
            deform_params_buffer,
        })
    }

    fn load_depth_map(path: &str) -> anyhow::Result<(Vec<f32>, u32, u32)>
    {
        println!("Loading snow depth map from: {}", path);

        let image = exr::prelude::read()
            .no_deep_data()
            .largest_resolution_level()
            .all_channels()
            .all_layers()
            .all_attributes()
            .from_file(path)?;

        let layer = &image.layer_data[0];
        let width = layer.size.width() as u32;
        let height = layer.size.height() as u32;

        println!("  Layer size: {}×{}", width, height);
        println!("  Available channels: {:?}", layer.channel_data.list.iter().map(|c| &c.name).collect::<Vec<_>>());

        let channel = layer.channel_data.list.iter()
            .find(|c| format!("{:?}", c.name).contains("\"R\""))
            .or_else(|| layer.channel_data.list.first())
            .ok_or_else(|| anyhow::anyhow!("No channels found in EXR"))?;

        println!("  Using channel: {:?}", channel.name);

        let depths: Vec<f32> = channel.sample_data.values_as_f32().collect();

        let min_value = depths.iter().cloned().fold(f32::INFINITY, f32::min);
        let max_value = depths.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
        let avg_value = depths.iter().sum::<f32>() / depths.len() as f32;
        let non_zero_count = depths.iter().filter(|&&v| v > 0.0001).count();

        println!("  Total values: {}", depths.len());
        println!("  Min: {:.6}, Max: {:.6}, Avg: {:.6}", min_value, max_value, avg_value);
        println!("  Non-zero values: {} ({:.1}%)", non_zero_count, (non_zero_count as f32 / depths.len() as f32) * 100.0);

        if max_value < 0.0001 {
            println!("  ⚠️  WARNING: All values are effectively zero! Snow depth map may be invalid.");
        } else {
            println!("  ✓ Snow depth data loaded successfully");
        }

        Ok((depths, width, height))
    }

    fn create_depth_texture(
        depth_data: &[f32],
        width: u32,
        height: u32,
    ) -> (wgpu::Texture, wgpu::TextureView, wgpu::BindGroup)
    {
        render::with_device(|device| {
            let size = wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            };

            let texture = device.create_texture(&wgpu::TextureDescriptor {
                label: Some("Snow Depth Texture"),
                size,
                mip_level_count: 1,
                sample_count: 1,
                dimension: wgpu::TextureDimension::D2,
                format: wgpu::TextureFormat::R32Float,
                usage: wgpu::TextureUsages::TEXTURE_BINDING
                    | wgpu::TextureUsages::COPY_DST
                    | wgpu::TextureUsages::STORAGE_BINDING,
                view_formats: &[],
            });

            let data_bytes: &[u8] = bytemuck::cast_slice(depth_data);

            render::with_queue(|queue| {
                queue.write_texture(
                    wgpu::TexelCopyTextureInfo {
                        texture: &texture,
                        mip_level: 0,
                        origin: wgpu::Origin3d::ZERO,
                        aspect: wgpu::TextureAspect::All,
                    },
                    data_bytes,
                    wgpu::TexelCopyBufferLayout {
                        offset: 0,
                        bytes_per_row: Some(width * 4),
                        rows_per_image: Some(height),
                    },
                    size,
                );
            });

            let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());

            let bind_group_layout =
                device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                    label: Some("Snow Depth Bind Group Layout"),
                    entries: &[wgpu::BindGroupLayoutEntry {
                        binding: 0,
                        visibility: wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE,
                        ty: wgpu::BindingType::Texture {
                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
                            view_dimension: wgpu::TextureViewDimension::D2,
                            multisampled: false,
                        },
                        count: None,
                    }],
                });

            let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Snow Depth Bind Group"),
                layout: &bind_group_layout,
                entries: &[wgpu::BindGroupEntry {
                    binding: 0,
                    resource: wgpu::BindingResource::TextureView(&texture_view),
                }],
            });

            (texture, texture_view, bind_group)
        })
    }

    fn generate_snow_mesh(
        depth_data: &[f32],
        heightmap_data: &[f32],
        width: u32,
        height: u32,
        terrain_size: Vec2,
    ) -> Mesh
    {
        let mut vertices = Vec::new();
        let mut indices = Vec::new();

        let cell_size_x = terrain_size.x / (width - 1) as f32;
        let cell_size_z = terrain_size.y / (height - 1) as f32;
        let half_width = terrain_size.x / 2.0;
        let half_height = terrain_size.y / 2.0;

        for z in 0..height
        {
            for x in 0..width
            {
                let index = (z * width + x) as usize;
                let snow_depth = depth_data.get(index).copied().unwrap_or(0.0);
                let terrain_height = heightmap_data.get(index).copied().unwrap_or(0.0);

                let world_x = x as f32 * cell_size_x - half_width;
                let world_z = z as f32 * cell_size_z - half_height;
                let world_y = terrain_height + snow_depth;

                vertices.push(Vertex {
                    position: [world_x, world_y, world_z],
                    normal: [0.0, 1.0, 0.0],
                    uv: [x as f32 / width as f32, z as f32 / height as f32],
                });
            }
        }

        for z in 0..(height - 1)
        {
            for x in 0..(width - 1)
            {
                let index = (z * width + x) as usize;
                let depth_tl = depth_data.get(index).copied().unwrap_or(0.0);
                let depth_tr = depth_data.get(index + 1).copied().unwrap_or(0.0);
                let depth_bl = depth_data.get(index + width as usize).copied().unwrap_or(0.0);
                let depth_br = depth_data
                    .get(index + width as usize + 1)
                    .copied()
                    .unwrap_or(0.0);

                if depth_tl > 0.001
                    || depth_tr > 0.001
                    || depth_bl > 0.001
                    || depth_br > 0.001
                {
                    let vertex_index = (z * width + x) as u32;

                    indices.push(vertex_index);
                    indices.push(vertex_index + width);
                    indices.push(vertex_index + 1);

                    indices.push(vertex_index + 1);
                    indices.push(vertex_index + width);
                    indices.push(vertex_index + width + 1);
                }
            }
        }

        let vertex_buffer = render::with_device(|device| {
            device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
                label: Some("Snow Vertex Buffer"),
                contents: bytemuck::cast_slice(&vertices),
                usage: wgpu::BufferUsages::VERTEX,
            })
        });

        let index_buffer = render::with_device(|device| {
            device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
                label: Some("Snow Index Buffer"),
                contents: bytemuck::cast_slice(&indices),
                usage: wgpu::BufferUsages::INDEX,
            })
        });

        Mesh {
            vertex_buffer,
            index_buffer,
            num_indices: indices.len() as u32,
        }
    }

    fn create_deform_pipeline(
        depth_texture_view: &wgpu::TextureView,
    ) -> (wgpu::ComputePipeline, wgpu::BindGroup, wgpu::Buffer)
    {
        render::with_device(|device| {
            let shader_source = std::fs::read_to_string("src/shaders/snow_deform.wgsl")
                .expect("Failed to load snow deform shader");

            let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
                label: Some("Snow Deform Shader"),
                source: wgpu::ShaderSource::Wgsl(shader_source.into()),
            });

            let params_buffer = device.create_buffer(&wgpu::BufferDescriptor {
                label: Some("Snow Deform Params"),
                size: 32,
                usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
                mapped_at_creation: false,
            });

            let bind_group_layout =
                device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                    label: Some("Snow Deform Bind Group Layout"),
                    entries: &[
                        wgpu::BindGroupLayoutEntry {
                            binding: 0,
                            visibility: wgpu::ShaderStages::COMPUTE,
                            ty: wgpu::BindingType::StorageTexture {
                                access: wgpu::StorageTextureAccess::ReadWrite,
                                format: wgpu::TextureFormat::R32Float,
                                view_dimension: wgpu::TextureViewDimension::D2,
                            },
                            count: None,
                        },
                        wgpu::BindGroupLayoutEntry {
                            binding: 1,
                            visibility: wgpu::ShaderStages::COMPUTE,
                            ty: wgpu::BindingType::Buffer {
                                ty: wgpu::BufferBindingType::Uniform,
                                has_dynamic_offset: false,
                                min_binding_size: None,
                            },
                            count: None,
                        },
                    ],
                });

            let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: Some("Snow Deform Bind Group"),
                layout: &bind_group_layout,
                entries: &[
                    wgpu::BindGroupEntry {
                        binding: 0,
                        resource: wgpu::BindingResource::TextureView(depth_texture_view),
                    },
                    wgpu::BindGroupEntry {
                        binding: 1,
                        resource: params_buffer.as_entire_binding(),
                    },
                ],
            });

            let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("Snow Deform Pipeline Layout"),
                bind_group_layouts: &[&bind_group_layout],
                push_constant_ranges: &[],
            });

            let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
                label: Some("Snow Deform Pipeline"),
                layout: Some(&pipeline_layout),
                module: &shader,
                entry_point: Some("deform"),
                compilation_options: Default::default(),
                cache: None,
            });

            (pipeline, bind_group, params_buffer)
        })
    }

    pub fn deform_at_position(&self, position: Vec3, radius: f32, depth: f32)
    {
        render::with_queue(|queue| {
            let params_data = [position.x, position.z, radius, depth];
            let params_bytes: &[u8] = bytemuck::cast_slice(&params_data);

            queue.write_buffer(&self.deform_params_buffer, 0, params_bytes);
        });

        render::with_device(|device| {
            let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
                label: Some("Snow Deform Encoder"),
            });

            {
                let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
                    label: Some("Snow Deform Pass"),
                    timestamp_writes: None,
                });

                compute_pass.set_pipeline(&self.deform_pipeline);
                compute_pass.set_bind_group(0, &self.deform_bind_group, &[]);

                let workgroup_size = 16;
                let dispatch_x = (self.width + workgroup_size - 1) / workgroup_size;
                let dispatch_y = (self.height + workgroup_size - 1) / workgroup_size;
                compute_pass.dispatch_workgroups(dispatch_x, dispatch_y, 1);
            }

            render::with_queue(|queue| {
                queue.submit(Some(encoder.finish()));
            });
        });
    }

    #[allow(dead_code)]
    pub fn regenerate_mesh(&mut self, _world: &mut World, _config: &SnowConfig)
    {
        todo!("Implement regenerate_mesh with correct wgpu types for texture-to-buffer copy");
    }
}
