use std::fs::{create_dir_all, rename};
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};

use audiotags::Tag;
use walkdir::WalkDir;

fn build_unique_path(path: &Path) -> String {
    if !path.exists() {
        return path.to_string_lossy().to_string();
    }

    println!(
        "Duplicate file, building unique path: {}",
        path.to_string_lossy().to_string()
    );

    let stem = path.file_stem().unwrap().to_string_lossy().to_string();
    let ext = path
        .extension()
        .map(|e| e.to_string_lossy().to_string())
        .unwrap_or_default();

    let timestamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();

    let parent = path.parent().unwrap().to_string_lossy().to_string();

    format!("{}/{}_{}.{}", parent, stem, timestamp, ext)
}

fn build_missing_tag_filename(
    path: &Path,
    artist: &Option<String>,
    album: &Option<String>,
    date: &Option<i32>,
    track_num: &Option<u16>,
    track_title: &Option<String>,
) -> String {
    let stem = path.file_stem().unwrap().to_string_lossy().to_string();
    let ext = path
        .extension()
        .map(|e| e.to_string_lossy().to_string())
        .unwrap_or_default();
    let mut filename = stem;

    if album.is_none() {
        filename.push_str("_ALBUMNAME");
    }
    if date.is_none() {
        filename.push_str("_DATE");
    }
    if artist.is_none() {
        filename.push_str("_ARTIST");
    }
    if track_num.is_none() {
        filename.push_str("_TRACKNUM");
    }
    if track_title.is_none() {
        filename.push_str("_TRACKTITLE");
    }

    format!("{}.{}", filename, ext)
}

fn main() {
    let root = "/home/merlin/MusicTest";
    let processed = format!("{}/PROCESSED", root);

    for entry in WalkDir::new(root).into_iter().filter_map(|e| e.ok()) {
        // Skip hidden directories (starting with .)
        if entry.file_type().is_dir() {
            if let Some(name) = entry.path().file_name()
                && entry.path().parent().unwrap().to_str().unwrap() == root
            {
                if name.to_string_lossy().starts_with('.') {
                    let target_path_str = format!("{}/{}", processed, entry.file_name().display());
                    let target_path = Path::new(&target_path_str);
                    let _ = rename(entry.path(), build_unique_path(target_path));
                    continue;
                }
            }
        }

        if entry.file_type().is_file() {
            let ext = entry.path().extension().and_then(|s| s.to_str());
            if ext == Some("m3u") {
                // playlist file, move it to playlist folder
                let target_path_str =
                    format!("{}/Playlists/{}", processed, entry.file_name().display());
                let target_path = Path::new(&target_path_str);
                let _ = create_dir_all(target_path.parent().unwrap());
                let _ = rename(entry.path(), build_unique_path(target_path));
                continue;
            } else if ext != Some("flac") && ext != Some("mp3") {
                println!("File not in good format: {}", entry.path().display());
                let target_path_str =
                    format!("{}/FORMAT/{}", processed, entry.file_name().display());
                let target_path = Path::new(&target_path_str);
                let _ = create_dir_all(target_path.parent().unwrap());
                let _ = rename(entry.path(), build_unique_path(target_path));
                continue;
            }

            let tag = match Tag::new().read_from_path(entry.path()) {
                Ok(t) => t,
                Err(_) => {
                    println!("Could not process file {}", entry.path().display());
                    let target_path_str =
                        format!("{}/ERROR/{}", processed, entry.file_name().display());
                    let target_path = Path::new(&target_path_str);
                    let _ = create_dir_all(target_path.parent().unwrap());
                    let _ = rename(entry.path(), build_unique_path(target_path));
                    continue;
                }
            };

            let artist = tag.album_artist();
            let album = tag.album_title();
            let date = tag.date();
            let track_num = tag.track_number();
            let track_title = tag.title();

            if artist.is_none()
                || album.is_none()
                || date.is_none()
                || track_num.is_none()
                || track_title.is_none()
            {
                println!("missing tag! {}", entry.path().display());
                let filename_with_tags = build_missing_tag_filename(
                    entry.path(),
                    &artist.map(|s| s.to_string()),
                    &album.map(|s| s.to_string()),
                    &date.map(|t| t.year),
                    &track_num,
                    &track_title.map(|s| s.to_string()),
                );
                let target_path_str = format!("{}/TAG/{}", processed, filename_with_tags);
                let target_path = Path::new(&target_path_str);
                let _ = create_dir_all(target_path.parent().unwrap());
                let _ = rename(entry.path(), build_unique_path(target_path));
                continue;
            }

            let newpath = format!(
                "{}/{}/{} ({})/{:02} {}.{}",
                processed,
                artist.unwrap(),
                album.unwrap(),
                date.unwrap().year,
                track_num.unwrap(),
                track_title.unwrap(),
                entry.path().extension().unwrap().display()
            );
            let target_path = Path::new(&newpath);
            let _ = create_dir_all(target_path.parent().unwrap());
            let _ = rename(entry.path(), build_unique_path(target_path));
        }
    }

    let processed_path = Path::new(&processed);
    if processed_path.exists() {
        // Move all files from PROCESSED to root, preserving organization
        for entry in WalkDir::new(&processed).into_iter().filter_map(|e| e.ok()) {
            if entry.file_type().is_file() {
                let relative_path = entry
                    .path()
                    .strip_prefix(processed_path)
                    .expect("Failed to get relative path");
                let target_path = Path::new(root).join(relative_path);
                let _ = create_dir_all(target_path.parent().unwrap());
                let _ = std::fs::rename(entry.path(), &target_path);
            }
        }

        // Remove all empty directories in root
        for entry in WalkDir::new(root).into_iter().filter_map(|e| e.ok()) {
            if entry.file_type().is_dir() {
                let _ = std::fs::remove_dir(entry.path());
            }
        }

        let _ = std::fs::remove_dir_all(processed_path);
    }
}