use crate::database::{Database, DatabaseError};
use std::path::{Path, PathBuf};

fn persist_image(
    db: &mut Database,
    bytes: &[u8],
    filename: &str,
    link: impl FnOnce(&mut Database, i64) -> Result<(), DatabaseError>,
    cache_dir: &Path,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
    let _ = std::fs::create_dir_all(cache_dir);
    let image_id = db.create_image(bytes, filename, "image/jpeg")?;
    link(db, image_id)?;
    let cache_path = cache_dir.join(filename.trim_start_matches('/'));
    let _ = std::fs::write(&cache_path, bytes);
    Ok(cache_path)
}

/// Store and cache movie poster and/or backdrop. Removes existing images of the same type first.
/// Returns (poster_cache_path, backdrop_cache_path).
pub fn store_movie_images(
    db: &mut Database,
    movie_id: i64,
    poster: Option<(String, Vec<u8>)>,
    backdrop: Option<(String, Vec<u8>)>,
    cache_dir: &Path,
) -> Result<(Option<PathBuf>, Option<PathBuf>), Box<dyn std::error::Error>> {
    let mut poster_path = None;
    if let Some((filename, bytes)) = poster {
        let _ = db.delete_movie_images(movie_id, "cover");
        poster_path = Some(persist_image(
            db,
            &bytes,
            &filename,
            |db, id| db.link_movie_image(movie_id, id, "cover"),
            cache_dir,
        )?);
    }
    let mut backdrop_path = None;
    if let Some((filename, bytes)) = backdrop {
        let _ = db.delete_movie_images(movie_id, "backdrop");
        match persist_image(
            db,
            &bytes,
            &filename,
            |db, id| db.link_movie_image(movie_id, id, "backdrop"),
            cache_dir,
        ) {
            Ok(path) => backdrop_path = Some(path),
            Err(e) => log::warn!("failed to store movie backdrop: {e}"),
        }
    }
    Ok((poster_path, backdrop_path))
}

/// Store and cache show poster, backdrop, and season posters. Removes existing images of the same
/// type first. Returns (poster_cache_path, backdrop_cache_path).
pub fn store_show_images(
    db: &mut Database,
    show_id: i64,
    poster: Option<(String, Vec<u8>)>,
    backdrop: Option<(String, Vec<u8>)>,
    seasons: &[(i64, Option<(String, Vec<u8>)>)],
    cache_dir: &Path,
) -> Result<(Option<PathBuf>, Option<PathBuf>), Box<dyn std::error::Error>> {
    let mut poster_path = None;
    if let Some((filename, bytes)) = poster {
        let _ = db.delete_show_images(show_id, "cover");
        poster_path = Some(persist_image(
            db,
            &bytes,
            &filename,
            |db, id| db.link_show_image(show_id, id, "cover"),
            cache_dir,
        )?);
    }
    let mut backdrop_path = None;
    if let Some((filename, bytes)) = backdrop {
        let _ = db.delete_show_images(show_id, "backdrop");
        match persist_image(
            db,
            &bytes,
            &filename,
            |db, id| db.link_show_image(show_id, id, "backdrop"),
            cache_dir,
        ) {
            Ok(path) => backdrop_path = Some(path),
            Err(e) => log::warn!("failed to store show backdrop: {e}"),
        }
    }
    for (season_id, season_poster) in seasons {
        let sid = *season_id;
        if let Some((filename, bytes)) = season_poster {
            let _ = db.delete_season_images(sid, "cover");
            if let Err(e) = persist_image(
                db,
                bytes,
                filename,
                move |db, id| db.link_season_image(sid, id, "cover"),
                cache_dir,
            ) {
                log::warn!("failed to store season poster: {e}");
            }
        }
    }
    Ok((poster_path, backdrop_path))
}
