use std::sync::Arc;

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

pub struct ID3Extractor;

impl Extractor for ID3Extractor {
    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 ID3 info from "{:}""#,
                        file_path
                            .components()
                            .last()
                            .unwrap()
                            .as_os_str()
                            .to_string_lossy()
                    ),
                )?;

                let tags = id3::Tag::read_from_path(file_path)?;

                let result: Vec<Entry> = tags
                    .frames()
                    .flat_map(|frame| match frame.content() {
                        id3::Content::Text(text) => vec![
                            Entry {
                                entity: address.clone(),
                                attribute: format!("ID3_{}", frame.id()),
                                value: match frame.id() {
                                    "TYER" | "TBPM" => EntryValue::guess_from(text),
                                    _ => text.clone().into(),
                                },
                                provenance: "SYSTEM EXTRACTOR".to_string(),
                                timestamp: chrono::Utc::now().naive_utc(),
                            },
                            Entry {
                                entity: Address::Attribute(format!("ID3_{}", frame.id())),
                                attribute: constants::LABEL_ATTR.into(),
                                value: format!("ID3: {}", frame.name()).into(),
                                provenance: "SYSTEM EXTRACTOR".to_string(),
                                timestamp: chrono::Utc::now().naive_utc(),
                            },
                        ],
                        _ => vec![],
                    })
                    .collect();

                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_audio = 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 == "application/x-riff";
                }
            }
            false
        });

        if !is_audio {
            return Ok(false);
        }

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

        if is_extracted {
            return Ok(false);
        }

        Ok(true)
    }
}
