use std::{process::Command, sync::Arc};

use super::Extractor;
use upend::{
    addressing::Address,
    database::{
        constants::LABEL_ATTR,
        entry::{Entry, EntryValue},
        stores::{fs::FILE_MIME_KEY, UpStore},
        UpEndConnection,
    },
    util::jobs::{JobContainer, JobState},
};
use anyhow::{anyhow, Result};

const DURATION_KEY: &str = "MEDIA_DURATION";

pub struct MediaExtractor;

impl Extractor for MediaExtractor {
    fn get(
        &self,
        address: &Address,
        _connection: &UpEndConnection,
        store: Arc<Box<dyn UpStore + Send + Sync>>,
        mut job_container: JobContainer,
    ) -> Result<Vec<Entry>> {
        if let Address::Hash(hash) = address {
            let files = store.retrieve(hash)?;

            if let Some(file) = files.get(0) {
                let file_path = file.get_file_path();
                let mut job_handle = job_container.add_job(
                    None,
                    &format!(
                        r#"Getting media info from "{:}""#,
                        file_path
                            .components()
                            .last()
                            .unwrap()
                            .as_os_str()
                            .to_string_lossy()
                    ),
                )?;

                // https://superuser.com/a/945604/409504
                let ffprobe_cmd = Command::new("ffprobe")
                    .args(["-v", "error"])
                    .args(["-show_entries", "format=duration"])
                    .args(["-of", "default=noprint_wrappers=1:nokey=1"])
                    .arg(file_path)
                    .output()?;

                if !ffprobe_cmd.status.success() {
                    return Err(anyhow!(
                        "Failed to retrieve file duration: {:?}",
                        String::from_utf8_lossy(&ffprobe_cmd.stderr)
                    ));
                }

                let duration = String::from_utf8(ffprobe_cmd.stdout)?
                    .trim()
                    .parse::<f64>()?;

                let result = vec![Entry {
                    entity: address.clone(),
                    attribute: DURATION_KEY.to_string(),
                    value: EntryValue::Number(duration),
                    provenance: "SYSTEM EXTRACTOR".to_string(),
                    timestamp: chrono::Utc::now().naive_utc(),
                }];

                let _ = job_handle.update_state(JobState::Done);
                Ok(result)
            } else {
                Err(anyhow!("Couldn't find file for {hash:?}!"))
            }
        } else {
            Ok(vec![])
        }
    }

    fn is_needed(&self, address: &Address, connection: &UpEndConnection) -> Result<bool> {
        let is_media = connection.retrieve_object(address)?.iter().any(|e| {
            if e.attribute == FILE_MIME_KEY {
                if let EntryValue::String(mime) = &e.value {
                    return mime.starts_with("audio") || mime.starts_with("video");
                }
            }
            if e.attribute == LABEL_ATTR {
                if let EntryValue::String(label) = &e.value {
                    let label = label.to_lowercase();
                    return label.ends_with(".ogg")
                        || label.ends_with(".mp3")
                        || label.ends_with(".wav");
                }
            }
            false
        });

        if !is_media {
            return Ok(false);
        }

        let is_extracted = !connection
            .query(format!("(matches @{} (contains \"{}\") ?)", address, DURATION_KEY).parse()?)?
            .is_empty();

        if is_extracted {
            return Ok(false);
        }

        Ok(true)
    }
}
