use image::{ImageBuffer, RgbaImage};
use imageproc::{
    drawing::{
        draw_filled_circle_mut, draw_filled_rect_mut, draw_hollow_circle_mut, draw_hollow_rect_mut,
        draw_line_segment_mut,
    },
    rect::Rect,
};
use indicatif::{ProgressBar, ProgressStyle};

use crate::renderer::{prelude::*, video_encoder::VideoEncoder};

#[derive(Debug)]
pub struct VideoInfo {
    pub width: u32,
    pub height: u32,
    pub fps: f64,
    pub duration: f64,
    pub total_frames: u32,
    pub num_scenes: usize,
}

// Main renderer state
#[derive(Debug)]
pub struct VideoRenderer {
    pub config: Config,
    pub current_layer: usize,
    pub scenes: Vec<Scene>,
    pub current_scene_drawables: Vec<Drawable>,
    pub current_scene_animations: Vec<(Animation, usize)>,
}

impl VideoRenderer {
    pub fn new(config: Config) -> Self {
        VideoRenderer {
            config,
            current_layer: 0,
            scenes: Vec::new(),
            current_scene_drawables: Vec::new(),
            current_scene_animations: Vec::new(),
        }
    }

    pub fn process_tasks(&mut self, tasks: Vec<Task>) {
        for task in tasks {
            self.process_task(task);
        }
    }

    pub fn process_task(&mut self, task: Task) {
        match task {
            // Task::SetConfig(config) => {
            //     self.config = config;
            // }
            Task::MoveToLayer(layer) => {
                if layer < self.config.num_layers {
                    self.current_layer = layer;
                }
            }
            Task::Draw(element) => {
                let drawable = Drawable::new(element, self.current_layer);
                self.current_scene_drawables.push(drawable);
            }
            Task::Animate(animation) => {
                self.current_scene_animations
                    .push((animation, self.current_layer));
            }
            Task::RenderScene(duration) => {
                let mut scene = Scene::new(duration);
                scene.drawables = std::mem::take(&mut self.current_scene_drawables);
                scene.animations = std::mem::take(&mut self.current_scene_animations);
                self.scenes.push(scene);
            }
        }
    }

    pub fn get_total_duration(&self) -> f64 {
        self.scenes.iter().map(|s| s.duration).sum()
    }

    pub fn get_frame_at_time(&self, time: f64) -> Option<Vec<Drawable>> {
        let mut current_time = 0.0;

        for scene in &self.scenes {
            if time >= current_time && time < current_time + scene.duration {
                let scene_time = time - current_time;
                return Some(scene.get_frame_at(scene_time));
            }
            current_time += scene.duration;
        }

        None
    }

    pub fn get_frame_number(&self, frame: u32) -> Option<Vec<Drawable>> {
        let time = frame as f64 / self.config.fps;
        self.get_frame_at_time(time)
    }

    pub fn render_frame_to_image(&self, drawables: &[Drawable]) -> RgbaImage {
        let mut image = ImageBuffer::new(self.config.width, self.config.height);

        // Fill with background color
        for pixel in image.pixels_mut() {
            *pixel = self.config.background_color.to_rgba();
        }

        // Render each drawable using imageproc
        for drawable in drawables {
            self.render_element_to_image(&mut image, &drawable.element);
        }

        image
    }

    fn render_element_to_image(&self, image: &mut RgbaImage, element: &Element) {
        match element {
            Element::Line(line) => {
                let start = (line.start.x as f32, line.start.y as f32);
                let end = (line.end.x as f32, line.end.y as f32);

                // Draw multiple lines for thickness
                let thickness = line.thickness.max(1.0) as i32;
                for i in -(thickness / 2)..=(thickness / 2) {
                    for j in -(thickness / 2)..=(thickness / 2) {
                        let offset_start = (start.0 + i as f32, start.1 + j as f32);
                        let offset_end = (end.0 + i as f32, end.1 + j as f32);

                        if offset_start.0 >= 0.0
                            && offset_start.1 >= 0.0
                            && offset_end.0 >= 0.0
                            && offset_end.1 >= 0.0
                            && offset_start.0 < self.config.width as f32
                            && offset_start.1 < self.config.height as f32
                            && offset_end.0 < self.config.width as f32
                            && offset_end.1 < self.config.height as f32
                        {
                            draw_line_segment_mut(
                                image,
                                offset_start,
                                offset_end,
                                line.color.to_rgba(),
                            );
                        }
                    }
                }
            }
            Element::Rectangle(rect) => {
                let x = rect.position.x as i32;
                let y = rect.position.y as i32;
                let w = rect.size.width as u32;
                let h = rect.size.height as u32;

                if x >= 0
                    && y >= 0
                    && x + w as i32 <= self.config.width as i32
                    && y + h as i32 <= self.config.height as i32
                {
                    let rectangle = Rect::at(x, y).of_size(w, h);

                    if rect.filled {
                        draw_filled_rect_mut(image, rectangle, rect.color.to_rgba());
                    } else {
                        draw_hollow_rect_mut(image, rectangle, rect.color.to_rgba());
                    }
                }
            }
            Element::Circle(circle) => {
                let center = (circle.center.x as i32, circle.center.y as i32);
                let radius = circle.radius as i32;

                if center.0 - radius >= 0
                    && center.1 - radius >= 0
                    && center.0 + radius < self.config.width as i32
                    && center.1 + radius < self.config.height as i32
                {
                    if circle.filled {
                        draw_filled_circle_mut(image, center, radius, circle.color.to_rgba());
                    } else {
                        draw_hollow_circle_mut(image, center, radius, circle.color.to_rgba());
                    }
                }
            }
        }
    }

    pub fn export_info(&self) -> VideoInfo {
        VideoInfo {
            width: self.config.width,
            height: self.config.height,
            fps: self.config.fps,
            duration: self.get_total_duration(),
            total_frames: (self.get_total_duration() * self.config.fps) as u32,
            num_scenes: self.scenes.len(),
        }
    }

    pub fn render_to_video(&self, output_path: &str) -> Result<(), Box<dyn std::error::Error>> {
        let encoder = VideoEncoder::new(&self.config, output_path)?;
        encoder.start()?;

        let total_frames = (self.get_total_duration() * self.config.fps) as u32;

        println!("Rendering {} frames to {}", total_frames, output_path);

        let pb = ProgressBar::new(total_frames as u64);

        pb.set_style(
            ProgressStyle::with_template(
                "{msg} [{elapsed_precise}>{duration_precise}] {wide_bar} {pos:>4}/{len:4} ({percent_precise}%)",
            )
            .unwrap(),
        );
        pb.set_message(output_path.to_string());

        pb.inc(1);

        for frame_num in 0..total_frames {
            if let Some(drawables) = self.get_frame_number(frame_num) {
                let image = self.render_frame_to_image(&drawables);
                encoder.push_frame(
                    &image,
                    ((frame_num as f64 / self.config.fps) * 1000.) as u64,
                )?;
                pb.inc(1);
            }
        }

        encoder.finish()?;
        pb.finish();
        println!("Video rendering complete!");
        Ok(())
    }

    pub fn save_frame(
        &self,
        frame_num: u32,
        output_path: &str,
    ) -> Result<(), Box<dyn std::error::Error>> {
        if let Some(drawables) = self.get_frame_number(frame_num) {
            let image = self.render_frame_to_image(&drawables);
            image.save(output_path)?;
            println!("Frame {} saved to {}", frame_num, output_path);
        }
        Ok(())
    }
}
