use std::{
    collections::{HashMap, HashSet},
    io,
    path::PathBuf,
    time::SystemTime,
};

use lunar_lib::{caching::Cachable, database::Id, hash_file, log::warn, paths::cache_dir};
use rayon::iter::{IntoParallelIterator, ParallelIterator};

use crate::{config::common_config, library::track::Track};

pub mod album;
pub mod artist;
pub mod collectable;
pub mod collection;
pub mod track;

pub mod image_art;
pub mod metadata;

mod extract;
pub use extract::*;

pub mod loudnorm;

pub fn hash_source_files() -> io::Result<HashMap<Id<Track>, PathBuf>> {
    let (sources, multithreaded) = {
        let common = common_config();
        (common.get_source_files(), common.main.multithreading)
    };
    let cache_file = cache_dir().join("sources");

    type Cached = HashMap<PathBuf, (Id<Track>, u64)>;

    let mut cached = Cached::load_from_cache(&cache_file);
    cached.retain(|p, (_, ct)| {
        p.metadata()
            .ok()
            .and_then(|p| p.modified().ok())
            .and_then(|d| d.duration_since(SystemTime::UNIX_EPOCH).ok())
            .is_some_and(|pt| *ct == pt.as_secs())
    });

    let cached_sources: HashSet<&PathBuf> = cached.keys().collect();
    let rehash: Vec<PathBuf> = sources
        .iter()
        .filter(|source| !cached_sources.contains(source))
        .cloned()
        .collect();

    if rehash.is_empty() {
        return Ok(cached
            .into_iter()
            .map(|(path, (id, _))| (id, path))
            .collect());
    }

    if multithreaded {
        let results: Vec<(PathBuf, (Id<Track>, u64))> = rehash
            .into_par_iter()
            .map(|source| -> io::Result<_> {
                let hash = hash_file(&source)?;
                let time = source
                    .metadata()?
                    .modified()?
                    .duration_since(SystemTime::UNIX_EPOCH)
                    .expect("Time went backwards")
                    .as_secs();
                Ok((source, (Id::<Track>::from(*hash.as_bytes()), time)))
            })
            .collect::<io::Result<_>>()?;
        cached.extend(results);
    } else {
        for source in rehash {
            let hash = hash_file(&source)?;
            let time = source
                .metadata()?
                .modified()?
                .duration_since(SystemTime::UNIX_EPOCH)
                .expect("Time went backwards")
                .as_secs();
            cached.insert(source, (Id::<Track>::from(*hash.as_bytes()), time));
        }
    }

    if let Err(err) = cached.save_to_cache(cache_file) {
        warn!("Failed to save to cache: {err}");
    }

    Ok(cached
        .into_iter()
        .map(|(path, (id, _))| (id, path))
        .collect())
}
