//! An alternate client for the arch linux package repositories. Stores files in a
//! separate directory from everything else and keeps everything hashed.
#![warn(
    missing_docs,
    clippy::missing_docs_in_private_items,
    clippy::empty_docs
)]

use clap::Parser;

/// Parser for command line arguments
#[derive(Parser)]
#[command(version, about, long_about = None, name = "pacwoman")]
struct Cli {
    /// Whether to operate as a user or not. In most cases, running as system will
    /// require running as root.
    #[arg(short, long, default_value_t = false)]
    user: bool,
    /// Initalize the GPG keyring and create skeleton directories. This will also add
    /// a default configuration for the mirrors.
    #[arg(long, default_value_t = false)]
    initalize: bool,
    /// Download one or more packages. This WILL NOT install them to your bin directory!
    #[arg(id = "packages to download", short = 'D')]
    download: Vec<String>,
    /// Download and install one or more packages. This WILL NOT update the repositories if needed!
    #[arg(id = "packages to sync", short = 'S')]
    sync: Vec<String>,
    /// Remove a package. Because I'm lazy, this will only remove the store entry, leaving a bunch
    /// of dangling symlinks.
    #[arg(id = "packages to remove", short = 'R')]
    remove: Vec<String>,
    /// Sync repository indexes from mirrors.
    #[arg(id = "repositories to sync", short = 'Y')]
    sync_repos: Vec<String>,
}

/// Default mirrorlist configuration.
const DEFAULT_MIRRORLIST: &[u8] = include_bytes!("default_mirrorlist");

fn main() -> std::io::Result<()> {
    let args = Cli::parse();

    if args.initalize {
        pacwoman::create_directories(!args.user)?;
        if !args.user {
            pacwoman::init_gpg()?;
        }

        std::fs::write(
            pacwoman::config_directory(!args.user)?.join("mirrors"),
            DEFAULT_MIRRORLIST,
        )?;
    }

    let regex = regex::Regex::new(r"(?m)#.*$").unwrap();

    let mirrors: Vec<pacwoman::Mirror> = regex
        .replace_all(
            &(std::fs::read_to_string(pacwoman::config_directory(!args.user)?.join("mirrors"))?),
            "",
        )
        .split("\n")
        .filter(|item| item.trim() != "")
        .map(|item| pacwoman::Mirror::new(url::Url::parse(item).unwrap()))
        .collect();

    if !args.sync_repos.is_empty() {
        for repo in &args.sync_repos {
            let mut ok = false;
            for mirror in &mirrors {
                if pacwoman::populate_index(
                    mirror.clone(),
                    pacwoman::RepoDescriptor::new()
                        .set_repo(repo.clone())
                        .set_arch(std::env::consts::ARCH.to_string())
                        .clone(),
                )
                .is_ok()
                {
                    ok = true;
                    break;
                }
            }
            if !ok {
                println!(" [ERR] Cannot download repo {repo}!");
                println!(" [TIP] Check the spelling and your mirrors.");

                drop(mirrors);
                drop(args);
                std::process::exit(1);
            }
        }
    }

    if !args.download.is_empty() {
        for package in &args.download {
            let results = pacwoman::locate_package(package.clone())?;
            if results.is_empty() {
                println!(" [ERR] Cannot locate packages for {package}!");
                println!(" [TIP] Try adding a repository to the index.");

                drop(mirrors);
                drop(args);
                std::process::exit(1);
            }
            for result in results {
                let mut ok = false;
                for mirror in &mirrors {
                    if let Ok(paths) = pacwoman::recieve_package_and_dependencies(
                        mirror.clone(),
                        result.1.clone(),
                        result.0.clone(),
                        !args.user,
                    ) {
                        println!(
                            "[INFO] Successfully retrieved package! Path to it and dependencies are:\n[INFO] {}",
                            paths
                                .iter()
                                .map(|item| item.to_string_lossy())
                                .collect::<Vec<std::borrow::Cow<'_, str>>>()
                                .join("\n[INFO] ")
                        );
                        ok = true;
                        break;
                    }
                }
                if !ok {
                    println!(" [ERR] Cannot download package {}!", result.0);
                    println!(" [TIP] Check the spelling and try syncing repos.");

                    drop(mirrors);
                    drop(args);
                    std::process::exit(1);
                }
            }
        }
    }

    if !args.sync.is_empty() {
        for package in &args.sync {
            println!("[INFO] Syncing package {}.", package);

            let results = pacwoman::locate_package(package.clone())?;
            if results.is_empty() {
                println!(" [ERR] Cannot locate packages for {package}!");
                println!(" [TIP] Try adding a repository to the index.");

                drop(mirrors);
                drop(args);
                std::process::exit(1);
            }
            for result in results {
                let mut ok = false;
                for mirror in &mirrors {
                    println!(
                        "[INFO] Attempting to install package {} from {}",
                        result.0.clone(),
                        mirror.base()
                    );
                    let pack = pacwoman::install_package(
                        mirror.clone(),
                        result.1.clone(),
                        result.0.clone(),
                        !args.user,
                    );
                    if pack.is_ok() {
                        ok = true;
                        break;
                    } else {
                        println!(
                            "\n[WARN] Error \"{}\" syncing package from mirror {}",
                            pack.unwrap_err(),
                            mirror.base()
                        );
                        println!("[INFO] Trying next mirror");
                    }
                }
                if !ok {
                    println!(" [ERR] Out of mirrors!");
                    println!(" [ERR] Cannot sync package {}!", result.0);
                    println!(" [TIP] Check the spelling and try syncing repos.");

                    drop(mirrors);
                    drop(args);
                    std::process::exit(1);
                }
            }
        }
    }

    if !args.remove.is_empty() {
        for package in &args.remove {
            let results = pacwoman::locate_package(package.clone())?;
            if results.is_empty() {
                println!(" [ERR] Cannot locate packages for {package}!");
                println!(" [TIP] Try adding a repository to the index.");

                drop(mirrors);
                drop(args);
                std::process::exit(1);
            }
            for result in results {
                pacwoman::remove_package(result.0, !args.user, result.1)?;
            }
        }
    }

    Ok(())
}
