use std::sync::Arc;

use barber::{ProgressBar, ProgressRenderer};
use lunar_lib::database::{Database, DbHandle, TransactionError, db_transaction};
use thiserror::Error;

use crate::{
    database::LibraryDb,
    library::{hash_source_files, track::Track},
};

#[derive(Debug, Error)]
pub enum OrphanRelinkError {
    #[error("IoError: {0}")]
    Io(#[from] std::io::Error),

    #[error("Transaction Error: {0}")]
    Transaction(#[from] TransactionError),
}

pub fn relink_orphans(
    mut check_tracks: Vec<Track>,
    progress_renderer: Arc<dyn ProgressRenderer>,
) -> Result<(), OrphanRelinkError> {
    if check_tracks.is_empty() {
        return Ok(());
    }

    let sources = hash_source_files(progress_renderer.clone())?;

    check_tracks.retain(|t| {
        let expected_source = t.container().path();
        !expected_source.exists() || sources.get(&t.id()).is_some_and(|t| t != expected_source)
    });

    let progress_bar = ProgressBar::new(0, check_tracks.len(), progress_renderer);
    progress_bar.set_label("Relinking orphaned sources...");

    let db = DbHandle::<LibraryDb>::open().unwrap();
    for mut track in check_tracks {
        if let Some(source) = sources.get(&track.id()) {
            track.set_source_file(source.to_owned());

            db_transaction(
                |cas_tx| cas_tx.tx_upsert(track.clone()).map_err(Into::into),
                db.clone(),
                false,
            )
            .map_err(TransactionError::from)?;

            progress_bar.set_label(&format!(
                "Relinked {} to {}",
                track.metadata.safe_title(),
                source.display()
            ));
        } else {
            db.db()
                .remove(*track.id())
                .map_err(TransactionError::Sled)?;

            progress_bar.set_label(&format!(
                "Failed to relink {}: No source found. Removing from library",
                track.metadata.safe_title()
            ));
        }

        progress_bar.increment();
    }
    progress_bar.flush();
    Ok(())
}
