use anyhow::{Context, Result};
use chrono::Utc;
use sqlx;
use std::path::Path;
use tracing::{info, warn};

use crate::core::db::{PostgresDb, VideoRecord, VideoStatus};
use crate::core::probe;
use crate::core::storage::uuid as uuid_utils;
use crate::core::storage::FileManager;

pub struct IngestionService {
    db: PostgresDb,
}

impl IngestionService {
    pub fn new(db: PostgresDb) -> Self {
        Self { db }
    }

    pub async fn ingest(&self, file_path: &str) -> Result<Option<String>> {
        let path = Path::new(file_path);

        if !is_video_extension(path) {
            return Ok(None);
        }

        let canonical_path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
        let filename = path
            .file_name()
            .unwrap_or_default()
            .to_string_lossy()
            .to_string();

        // Stable UUID based on MAC + Birthday + Filename.
        // Moving the file (path change) keeps the SAME identity.

        // 1. Look for existing Birthday (Identity Anchor)
        // If the file (by name) was registered before, use its original birth time.
        let birthday = sqlx::query_scalar::<_, chrono::DateTime<chrono::Utc>>(
            "SELECT registration_time FROM dev.videos WHERE file_name = $1 AND registration_time IS NOT NULL LIMIT 1"
        )
        .bind(&filename)
        .fetch_optional(self.db.pool())
        .await
        .ok()
        .flatten()
        .map(|t| t.to_rfc3339())
        .unwrap_or_else(|| Utc::now().to_rfc3339());

        let parent = canonical_path
            .parent()
            .map(|p| p.to_string_lossy().to_string())
            .unwrap_or_default();

        // 2. Compute UUID
        let uuid = uuid_utils::compute_birth_uuid(
            &uuid_utils::get_mac_address(),
            &birthday,
            &canonical_path.to_string_lossy(),
            &filename,
        );

        let parent = canonical_path
            .parent()
            .map(|p| p.to_string_lossy().to_string())
            .unwrap_or_default();

        let username = uuid_utils::extract_username_from_path(&parent);

        if let Ok(Some(_)) = self.db.get_video_by_uuid(&uuid).await {
            info!(
                "Video already registered: {} ({})",
                path.file_name().unwrap_or_default().to_string_lossy(),
                uuid
            );
            return Ok(None);
        }

        info!("Starting ingestion for: {} ({})", path.display(), uuid);

        let probe_result = probe::probe_video(file_path)
            .with_context(|| format!("Failed to probe video: {}", file_path))?;

        let duration = probe_result
            .format
            .duration
            .as_ref()
            .and_then(|s| s.parse::<f64>().ok())
            .unwrap_or(0.0);

        let mut width = 0u32;
        let mut height = 0u32;
        let mut fps = 0.0;

        for stream in &probe_result.streams {
            if stream.codec_type.as_deref() == Some("video") {
                width = stream.width.unwrap_or(0);
                height = stream.height.unwrap_or(0);
                if let Some(fps_str) = &stream.r_frame_rate {
                    if let Some((num, den)) = fps_str.split_once('/') {
                        if let (Ok(n), Ok(d)) = (num.parse::<f64>(), den.parse::<f64>()) {
                            if d > 0.0 {
                                fps = n / d;
                            }
                        }
                    }
                }
            }
        }

        let file_manager = FileManager::new(std::path::PathBuf::from("."));
        let probe_json_str = serde_json::to_string_pretty(&probe_result)?;

        if let Err(e) = file_manager.save_json(&uuid, "probe", &probe_json_str) {
            warn!("Failed to save probe JSON for {}: {}", uuid, e);
        } else {
            info!("Probe JSON saved for {}", uuid);
        }

        let total_frames = {
            let video_stream = probe_result
                .streams
                .iter()
                .find(|s| s.codec_type.as_deref() == Some("video"));

            if let Some(stream) = video_stream {
                if let Some(nb_frames_str) = &stream.nb_frames {
                    if let Ok(nb_frames) = nb_frames_str.parse::<u64>() {
                        info!(
                            "Using nb_frames from ffprobe: {} frames for {}",
                            nb_frames,
                            path.display()
                        );
                        Some(nb_frames)
                    } else {
                        warn!(
                            "Failed to parse nb_frames, using duration * fps fallback for {}",
                            path.display()
                        );
                        Some((duration * fps).floor() as u64)
                    }
                } else {
                    warn!(
                        "nb_frames not available, using duration * fps fallback for {}",
                        path.display()
                    );
                    Some((duration * fps).floor() as u64)
                }
            } else {
                warn!("No video stream found for {}", path.display());
                Some(0)
            }
        };

        let birth_registration = serde_json::json!({
            "registration_source": {
                "username": username,
                "original_path": parent,
                "original_filename": filename
            }
        });

        let record = VideoRecord {
            id: 0,
            file_uuid: uuid.clone(),
            file_path: canonical_path.to_string_lossy().to_string(),
            file_name: filename,
            file_type: None,
            duration,
            width,
            height,
            fps,
            probe_json: Some(probe_json_str),
            storage: Default::default(),
            status: VideoStatus::Pending,
            processing_status: Some(serde_json::json!({"phase": "REGISTERED"})),
            birth_registration: None,
            user_id: None,
            job_id: None,
            created_at: String::new(),
            registration_time: None,
            total_frames: total_frames.unwrap_or(0),
            parent_uuid: None,
        };

        self.db
            .register_video(&record)
            .await
            .with_context(|| "Failed to register video in database")?;

        self.db
            .set_registration_time(&uuid)
            .await
            .with_context(|| "Failed to set registration_time")?;

        self.db
            .update_birth_registration(&uuid, &birth_registration)
            .await
            .with_context(|| "Failed to set birth_registration")?;

        info!(
            "Successfully registered video: {} (UUID: {}, Birth UUID: {})",
            record.file_name, uuid, uuid
        );
        Ok(Some(uuid))
    }
}

fn is_video_extension(path: &Path) -> bool {
    if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
        let ext = ext.to_lowercase();
        matches!(ext.as_str(), "mp4" | "mov" | "mkv" | "avi" | "webm" | "m4v")
    } else {
        false
    }
}
