mod adopt;
mod disc_id;
mod import;
mod musicbrainz;
mod tagging;
use clap::{
    Parser, Subcommand,
    builder::styling::{AnsiColor, Effects, Styles},
};
use lofty::{file::TaggedFileExt, probe::Probe, tag::Accessor};
use sha2::{Digest, Sha256};
use std::{
    fs::{self, File, create_dir_all, remove_dir},
    io::{BufReader, Read},
    path::{Path, PathBuf},
};

const CLAP_STYLING: Styles = Styles::styled()
    .header(AnsiColor::Green.on_default().effects(Effects::BOLD))
    .usage(AnsiColor::Green.on_default().effects(Effects::BOLD))
    .literal(AnsiColor::Cyan.on_default().effects(Effects::BOLD))
    .placeholder(AnsiColor::Cyan.on_default());

#[cfg(not(target_os = "linux"))]
compile_error!("kitty like linux more");

fn check_cat_hash(cat_path: &Path) -> bool {
    let file = File::open(cat_path).expect("how dare u! i want my cat back :(\n");
    let mut cat = BufReader::new(file);

    let mut hasher = Sha256::new();

    let mut buffer = [0u8; 4096];
    loop {
        let bytes_read = cat.read(&mut buffer).expect("what happened to my cat!?\n");
        if bytes_read == 0 {
            break;
        }
        hasher.update(&buffer[..bytes_read]);
    }

    let file_hash = hex::encode(hasher.finalize());

    file_hash
        .eq_ignore_ascii_case("45d4cf2fb11999c39ec5cd0cbedb436767421c8b8dab575c76f0b6ad9265d640")
}
fn hashcat(cat_path: &Path) {
    if !check_cat_hash(cat_path) {
        panic!("this is not my cat. where is my cat. what did you do!? KITTYYYYYYYYYY")
    }
}

#[derive(Parser)]
#[command(name = "timbre")]
#[command(about = "a music manager to finally solve this huge mess of music management")]
#[command(styles=CLAP_STYLING)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}
#[derive(Subcommand, PartialEq)]
enum Commands {
    /// give timbre a home so it can help you
    Init,
    /// look through your library and see what's there
    Scan,
    /// attempt to repair timbre's home, this won't restore lost tracks
    Repair,
    /// import music files
    Import,
}

fn is_audio(path: &Path) -> bool {
    match path.extension().and_then(|e| e.to_str()) {
        Some(ext) => {
            if matches!(
                ext.to_lowercase().as_str(),
                "flac" | "mp3" | "m4a" | "ogg" | "opus" | "wav"
            ) {
                true
            } else {
                println!(
                    "while looking through each of your songs, i found one that i don't understand.\nits named {}, and i don't understand its file extension.\nyou can try to help teach me by opening a git issue if it's a music file! :)\ni will continue, but ignore this file :(",
                    path.display()
                );
                false
            }
        }
        None => false,
    }
}
fn scan(dir: &Path, out: &mut Vec<PathBuf>) {
    for entry in fs::read_dir(dir)
        .expect("i want to help you, but i had trouble reading my music folder :( sorry.\ntry running timbre repair!\n")
    {
        let path=entry.expect(
            "i wanna help you, but, i can't do everything. i couldn't read a file, to the point where i can't tell you which :(\n").path();
        if path.is_dir() {
            scan(&path, out);
        } else if is_audio(&path) {
            out.push(path);
        }
    }
}
enum SafeName {
    Ok(String),
    Empty,
    Malicious,
}
fn sanitize(name: &str) -> SafeName {
    let trimmed = name.trim();

    if trimmed == ".." || trimmed == "." {
        return SafeName::Malicious;
    }

    let cleaned = trimmed.replace(['/', '\\'], "-");
    let cleaned = cleaned.trim();

    if cleaned.is_empty() {
        SafeName::Empty
    } else {
        SafeName::Ok(cleaned.to_string())
    }
}
fn remove_empty_dirs(dir: &Path) {
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.is_dir() {
                remove_empty_dirs(&path);
                let _ = fs::remove_dir(&path);
            }
        }
    }
}
fn main() {
    let cat = include_bytes!("../cat_01.jpg"); // my kitty!

    let cli = Cli::parse();

    let timbre_dir = Path::new(&std::env::var("HOME").expect("NERD!!!"))
        .join("Music")
        .join("Timbre");
    adopt::adopt(&timbre_dir);
    if cli.command != Commands::Init && cli.command != Commands::Repair {
        if timbre_dir.exists() {
            hashcat(&timbre_dir.join("cat_01.jpg")); // meow
        } else {
            println!(
                "i can't help you since i don't have a home :(\nyou can create one with the command timbre init."
            );
            return;
        }
    }

    match cli.command {
        Commands::Init => {
            if !timbre_dir.exists() {
                println!("making a home...");
                create_dir_all(&timbre_dir)
                    .expect("i couldn't create my house :( i don't know what to do :(\n");
                fs::write(timbre_dir.join("cat_01.jpg"), cat)
                    .expect("my cat! he can't come! what did you do!?!\n");
                for dir in ["Albums", "Singles", "Unidentified"] {
                    create_dir_all(timbre_dir.join(dir))
                        .expect("sorry, but i couldn't create a folder that i need.\n");
                }
                println!("i now have a home!");
            } else {
                println!("i already has a home!");
            }
        }
        Commands::Scan => {
            let mut tracks = Vec::new();
            scan(&timbre_dir.join("Singles"), &mut tracks);
            scan(&timbre_dir.join("Albums"), &mut tracks);
            if tracks.is_empty() {
                println!("i couldn't find any audio files :(");
            } else {
                println!("i found {} audio files :)", tracks.len());
            }
        }
        Commands::Import => {
            let import_dir = timbre_dir.join("Import");
            if !import_dir.exists() {
                create_dir_all(&import_dir)
                    .expect("i can't make the folder for you to put your music in :(\n");
                println!("i made an Import folder! put your music there and run this again.");
                return;
            }

            let mut to_import = Vec::new();
            scan(&import_dir, &mut to_import);

            if to_import.is_empty() {
                println!("the Import folder is empty :( put some music in it first!");
                return;
            }

            println!("found {} files to import!", to_import.len());

            for file in to_import {
                import::import_file(&file, &timbre_dir);
            }
            remove_empty_dirs(&import_dir);
            let _ = remove_dir(&import_dir);
            println!("all done! cleaned up the Import folder if it's empty :)");
        }
        Commands::Repair => {
            println!("repairing my home...");
            let mut fixed_something = false;

            if !timbre_dir.exists() {
                println!(
                    "it appears that my home is gone. your songs may have been deleted :(\nrebuilding my home..."
                );
                create_dir_all(&timbre_dir)
                    .expect("i couldn't create my house :( i don't know what to do :(\n");
                fs::write(timbre_dir.join("cat_01.jpg"), cat)
                    .expect("my cat! he can't come! what did you do!?!\n");
                for dir in ["Albums", "Singles", "Unidentified"] {
                    create_dir_all(timbre_dir.join(dir))
                        .expect("sorry, but i couldn't create a folder that i need.\n");
                }
                println!("i rebuilt my home! your audio won't be there though :(");
                fixed_something = true;
            }
            if !timbre_dir.join("Albums").exists()
                || !timbre_dir.join("Singles").exists()
                || !timbre_dir.join("Unidentified").exists()
            {
                println!("i found folders that don't exist! recreating them...");
                for dir in ["Albums", "Singles", "Unidentified"] {
                    create_dir_all(timbre_dir.join(dir))
                        .expect("sorry, but i couldn't create a folder that i need.\n");
                }
                println!("done!");
                fixed_something = true;
            }
            if !timbre_dir.join("cat_01.jpg").exists() {
                println!("where did my cat go!?!");
                fs::write(timbre_dir.join("cat_01.jpg"), cat)
                    .expect("my cat! he can't come! what did you do!?!\n");
                println!("he's back :)");
                fixed_something = true;
            }
            if !check_cat_hash(&timbre_dir.join("cat_01.jpg")) {
                println!("what happened to my cat :( let me bring my kitty back.");
                fs::write(timbre_dir.join("cat_01.jpg"), cat)
                    .expect("what did you do! i can't bring my cat back :(\n");
                println!("yay he's back!");
                fixed_something = true;
            }
            if !fixed_something {
                println!(
                    "i wasn't able to fix anything :(\neither there is no problem, or i couldn't fix it :("
                );
            }
        }
    }
}
