use std::path::PathBuf;

use crate::facedet::{FaceDetectionConfig, FaceDetector, retinaface};
use crate::faceembed::facenet;
use crate::gui::app::{ComparisonResult, DetectionResult, ExecutorType};
use ndarray_image::ImageToNdarray;

const RETINAFACE_MODEL_MNN: &[u8] = include_bytes!("../../models/retinaface.mnn");
const FACENET_MODEL_MNN: &[u8] = include_bytes!("../../models/facenet.mnn");
const RETINAFACE_MODEL_ONNX: &[u8] = include_bytes!("../../models/retinaface.onnx");
const FACENET_MODEL_ONNX: &[u8] = include_bytes!("../../models/facenet.onnx");

pub struct FaceDetectionBridge;

impl FaceDetectionBridge {
    pub async fn detect_faces(
        image_path: PathBuf,
        output_path: Option<PathBuf>,
        threshold: f32,
        nms_threshold: f32,
        executor_type: ExecutorType,
    ) -> DetectionResult {
        let start_time = std::time::Instant::now();

        match Self::run_detection_internal(
            image_path.clone(),
            output_path,
            threshold,
            nms_threshold,
            executor_type,
        )
        .await
        {
            Ok((faces_count, processed_image)) => {
                let processing_time = start_time.elapsed().as_secs_f64();
                DetectionResult::Success {
                    image_path,
                    faces_count,
                    processed_image,
                    processing_time,
                }
            }
            Err(error) => DetectionResult::Error(error.to_string()),
        }
    }

    pub async fn compare_faces(
        image1_path: PathBuf,
        image2_path: PathBuf,
        threshold: f32,
        nms_threshold: f32,
        executor_type: ExecutorType,
    ) -> ComparisonResult {
        let start_time = std::time::Instant::now();

        match Self::run_comparison_internal(
            image1_path,
            image2_path,
            threshold,
            nms_threshold,
            executor_type,
        )
        .await
        {
            Ok((image1_faces, image2_faces, best_similarity)) => {
                let processing_time = start_time.elapsed().as_secs_f64();
                ComparisonResult::Success {
                    image1_faces,
                    image2_faces,
                    best_similarity,
                    processing_time,
                }
            }
            Err(error) => ComparisonResult::Error(error.to_string()),
        }
    }

    async fn run_detection_internal(
        image_path: PathBuf,
        output_path: Option<PathBuf>,
        threshold: f32,
        nms_threshold: f32,
        executor_type: ExecutorType,
    ) -> Result<(usize, Option<Vec<u8>>), Box<dyn std::error::Error + Send + Sync>> {
        // Load the image
        let img = image::open(&image_path)?;
        let img_rgb = img.to_rgb8();

        // Convert to ndarray format
        let image_array = img_rgb.as_ndarray()?;

        // Create detection configuration
        let config = FaceDetectionConfig::default()
            .with_threshold(threshold)
            .with_nms_threshold(nms_threshold)
            .with_input_width(1024)
            .with_input_height(1024);

        // Create detector and detect faces
        let faces = match executor_type {
            ExecutorType::MnnCpu | ExecutorType::MnnMetal | ExecutorType::MnnCoreML => {
                let forward_type = match executor_type {
                    ExecutorType::MnnCpu => mnn::ForwardType::CPU,
                    ExecutorType::MnnMetal => mnn::ForwardType::Metal,
                    ExecutorType::MnnCoreML => mnn::ForwardType::CoreML,
                    _ => unreachable!(),
                };

                let mut detector = retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
                    .map_err(|e| format!("Failed to create MNN detector: {}", e))?
                    .with_forward_type(forward_type)
                    .build()
                    .map_err(|e| format!("Failed to build MNN detector: {}", e))?;

                detector
                    .detect_faces(image_array.view(), &config)
                    .map_err(|e| format!("Detection failed: {}", e))?
            }
            ExecutorType::OnnxCpu => {
                let mut detector = retinaface::ort::FaceDetection::builder(RETINAFACE_MODEL_ONNX)
                    .map_err(|e| format!("Failed to create ONNX detector: {}", e))?
                    .build()
                    .map_err(|e| format!("Failed to build ONNX detector: {}", e))?;

                detector
                    .detect_faces(image_array.view(), &config)
                    .map_err(|e| format!("Detection failed: {}", e))?
            }
        };

        let faces_count = faces.bbox.len();

        // Generate output image with bounding boxes if requested
        let processed_image = if output_path.is_some() || true {
            // Always generate for GUI display
            let mut output_img = img.to_rgb8();

            for bbox in &faces.bbox {
                let min_point = bbox.min_vertex();
                let size = bbox.size();
                let rect = imageproc::rect::Rect::at(min_point.x as i32, min_point.y as i32)
                    .of_size(size.x as u32, size.y as u32);
                imageproc::drawing::draw_hollow_rect_mut(
                    &mut output_img,
                    rect,
                    image::Rgb([255, 0, 0]),
                );
            }

            // Convert to bytes for GUI display
            let mut buffer = Vec::new();
            let mut cursor = std::io::Cursor::new(&mut buffer);
            image::DynamicImage::ImageRgb8(output_img.clone())
                .write_to(&mut cursor, image::ImageFormat::Png)?;

            // Save to file if output path is specified
            if let Some(ref output_path) = output_path {
                output_img.save(output_path)?;
            }

            Some(buffer)
        } else {
            None
        };

        Ok((faces_count, processed_image))
    }

    async fn run_comparison_internal(
        image1_path: PathBuf,
        image2_path: PathBuf,
        threshold: f32,
        nms_threshold: f32,
        executor_type: ExecutorType,
    ) -> Result<(usize, usize, f32), Box<dyn std::error::Error + Send + Sync>> {
        // Load both images
        let img1 = image::open(&image1_path)?.to_rgb8();
        let img2 = image::open(&image2_path)?.to_rgb8();

        // Convert to ndarray format
        let image1_array = img1.as_ndarray()?;
        let image2_array = img2.as_ndarray()?;

        // Create detection configuration
        let config1 = FaceDetectionConfig::default()
            .with_threshold(threshold)
            .with_nms_threshold(nms_threshold)
            .with_input_width(1024)
            .with_input_height(1024);

        let config2 = FaceDetectionConfig::default()
            .with_threshold(threshold)
            .with_nms_threshold(nms_threshold)
            .with_input_width(1024)
            .with_input_height(1024);

        // Create detector and embedder, detect faces and generate embeddings
        let (faces1, faces2, best_similarity) = match executor_type {
            ExecutorType::MnnCpu | ExecutorType::MnnMetal | ExecutorType::MnnCoreML => {
                let forward_type = match executor_type {
                    ExecutorType::MnnCpu => mnn::ForwardType::CPU,
                    ExecutorType::MnnMetal => mnn::ForwardType::Metal,
                    ExecutorType::MnnCoreML => mnn::ForwardType::CoreML,
                    _ => unreachable!(),
                };

                let mut detector = retinaface::mnn::FaceDetection::builder(RETINAFACE_MODEL_MNN)
                    .map_err(|e| format!("Failed to create MNN detector: {}", e))?
                    .with_forward_type(forward_type.clone())
                    .build()
                    .map_err(|e| format!("Failed to build MNN detector: {}", e))?;

                let embedder = facenet::mnn::EmbeddingGenerator::builder(FACENET_MODEL_MNN)
                    .map_err(|e| format!("Failed to create MNN embedder: {}", e))?
                    .with_forward_type(forward_type)
                    .build()
                    .map_err(|e| format!("Failed to build MNN embedder: {}", e))?;

                // Detect faces in both images
                let faces1 = detector
                    .detect_faces(image1_array.view(), &config1)
                    .map_err(|e| format!("Detection failed for image 1: {}", e))?;
                let faces2 = detector
                    .detect_faces(image2_array.view(), &config2)
                    .map_err(|e| format!("Detection failed for image 2: {}", e))?;

                // Extract face crops and generate embeddings
                let mut best_similarity = 0.0f32;

                for bbox1 in &faces1.bbox {
                    let crop1 = Self::crop_face_from_image(&img1, bbox1)?;
                    let crop1_array = ndarray::Array::from_shape_vec(
                        (1, crop1.height() as usize, crop1.width() as usize, 3),
                        crop1
                            .pixels()
                            .flat_map(|p| [p.0[0], p.0[1], p.0[2]])
                            .collect(),
                    )?;

                    let embedding1 = embedder
                        .run_models(crop1_array.view())
                        .map_err(|e| format!("Embedding generation failed: {}", e))?;

                    for bbox2 in &faces2.bbox {
                        let crop2 = Self::crop_face_from_image(&img2, bbox2)?;
                        let crop2_array = ndarray::Array::from_shape_vec(
                            (1, crop2.height() as usize, crop2.width() as usize, 3),
                            crop2
                                .pixels()
                                .flat_map(|p| [p.0[0], p.0[1], p.0[2]])
                                .collect(),
                        )?;

                        let embedding2 = embedder
                            .run_models(crop2_array.view())
                            .map_err(|e| format!("Embedding generation failed: {}", e))?;

                        let similarity = Self::cosine_similarity(
                            embedding1.row(0).as_slice().unwrap(),
                            embedding2.row(0).as_slice().unwrap(),
                        );
                        best_similarity = best_similarity.max(similarity);
                    }
                }

                (faces1, faces2, best_similarity)
            }
            ExecutorType::OnnxCpu => {
                let mut detector = retinaface::ort::FaceDetection::builder(RETINAFACE_MODEL_ONNX)
                    .map_err(|e| format!("Failed to create ONNX detector: {}", e))?
                    .build()
                    .map_err(|e| format!("Failed to build ONNX detector: {}", e))?;

                let mut embedder = facenet::ort::EmbeddingGenerator::builder(FACENET_MODEL_ONNX)
                    .map_err(|e| format!("Failed to create ONNX embedder: {}", e))?
                    .build()
                    .map_err(|e| format!("Failed to build ONNX embedder: {}", e))?;

                // Detect faces in both images
                let faces1 = detector
                    .detect_faces(image1_array.view(), &config1)
                    .map_err(|e| format!("Detection failed for image 1: {}", e))?;
                let faces2 = detector
                    .detect_faces(image2_array.view(), &config2)
                    .map_err(|e| format!("Detection failed for image 2: {}", e))?;

                // Extract face crops and generate embeddings
                let mut best_similarity = 0.0f32;

                for bbox1 in &faces1.bbox {
                    let crop1 = Self::crop_face_from_image(&img1, bbox1)?;
                    let crop1_array = ndarray::Array::from_shape_vec(
                        (1, crop1.height() as usize, crop1.width() as usize, 3),
                        crop1
                            .pixels()
                            .flat_map(|p| [p.0[0], p.0[1], p.0[2]])
                            .collect(),
                    )?;

                    let embedding1 = embedder
                        .run_models(crop1_array.view())
                        .map_err(|e| format!("Embedding generation failed: {}", e))?;

                    for bbox2 in &faces2.bbox {
                        let crop2 = Self::crop_face_from_image(&img2, bbox2)?;
                        let crop2_array = ndarray::Array::from_shape_vec(
                            (1, crop2.height() as usize, crop2.width() as usize, 3),
                            crop2
                                .pixels()
                                .flat_map(|p| [p.0[0], p.0[1], p.0[2]])
                                .collect(),
                        )?;

                        let embedding2 = embedder
                            .run_models(crop2_array.view())
                            .map_err(|e| format!("Embedding generation failed: {}", e))?;

                        let similarity = Self::cosine_similarity(
                            embedding1.row(0).as_slice().unwrap(),
                            embedding2.row(0).as_slice().unwrap(),
                        );
                        best_similarity = best_similarity.max(similarity);
                    }
                }

                (faces1, faces2, best_similarity)
            }
        };

        Ok((faces1.bbox.len(), faces2.bbox.len(), best_similarity))
    }

    fn crop_face_from_image(
        img: &image::RgbImage,
        bbox: &bounding_box::Aabb2<usize>,
    ) -> Result<image::RgbImage, Box<dyn std::error::Error + Send + Sync>> {
        let min_point = bbox.min_vertex();
        let size = bbox.size();
        let x = min_point.x as u32;
        let y = min_point.y as u32;
        let width = size.x as u32;
        let height = size.y as u32;

        // Ensure crop bounds are within image
        let img_width = img.width();
        let img_height = img.height();

        let crop_x = x.min(img_width.saturating_sub(1));
        let crop_y = y.min(img_height.saturating_sub(1));
        let crop_width = width.min(img_width - crop_x);
        let crop_height = height.min(img_height - crop_y);

        Ok(image::imageops::crop_imm(img, crop_x, crop_y, crop_width, crop_height).to_image())
    }

    fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
        let dot_product: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
        let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
        let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();

        if norm_a == 0.0 || norm_b == 0.0 {
            0.0
        } else {
            dot_product / (norm_a * norm_b)
        }
    }
}
