use crate::{
    database::Database,
    scanner::{MovieScanner, ScannerError},
    tmdb::TmdbClient,
};
use std::{
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
    },
    thread,
};

#[derive(Debug, Clone)]
pub enum ScanEvent {
    Finished,
    Error(String),
}

pub struct ScanHandle {
    pub events: async_channel::Receiver<ScanEvent>,
    pub cancel: Arc<AtomicBool>,
    _join: thread::JoinHandle<()>,
}

pub struct ScanReceiver {
    id: u64,
    pub rx: async_channel::Receiver<ScanEvent>,
}

impl ScanReceiver {
    pub fn new(rx: async_channel::Receiver<ScanEvent>) -> Self {
        Self { id: 1337, rx }
    }
}

impl std::hash::Hash for ScanReceiver {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.id.hash(state);
    }
}

pub fn spawn<S>(db_path: S) -> ScanHandle
where
    S: Into<String>,
{
    let (tx, rx) = async_channel::bounded(32);
    let cancel = Arc::new(AtomicBool::new(false));
    let cancel_t = Arc::clone(&cancel);

    let db_path_t = db_path.into();

    let join = thread::spawn(move || {
        let send = |e: ScanEvent| {
            let _ = tx.send_blocking(e);
        };

        if let Err(e) = run_scan(&db_path_t, &cancel_t) {
            send(ScanEvent::Error(e.to_string()));
        }

        send(ScanEvent::Finished);
    });

    ScanHandle {
        events: rx,
        cancel,
        _join: join,
    }
}

fn run_scan<'a>(db_path: &'a str, cancel: &Arc<AtomicBool>) -> Result<(), ScannerError> {
    let mut db = Database::open(db_path)?;
    let mut tmdb = db.get_host("tmdb_api_key")?.map(TmdbClient::new);
    let scanner = MovieScanner::new()?;

    for lib in db.find_libraries_by_media_type("movie")? {
        if cancel.load(Ordering::Relaxed) {
            return Ok(());
        }

        log::info!("start scanning {}", &lib.root_path);
        scanner.scan_movies(&mut db, &lib.root_path)?;
        if let Some(tmdb) = tmdb.as_mut() {
            scanner.lookup_movies(&mut db, tmdb)?;
        }
    }

    for lib in db.find_libraries_by_media_type("show")? {
        if cancel.load(Ordering::Relaxed) {
            return Ok(());
        }

        log::info!("start scanning {}", &lib.root_path);
        scanner.scan_shows(&mut db, &lib.root_path)?;
        if let Some(tmdb) = tmdb.as_mut() {
            scanner.lookup_shows(&mut db, tmdb)?;
            scanner.lookup_new_seasons(&mut db, tmdb)?;
        }
    }

    Ok(())
}
