use anyhow::{Context, Result, bail};
use std::{
    env::set_current_dir,
    fs::{self, create_dir},
    path::Path,
    process::Command,
};

use crate::{CURRENT_FORMAT_VERSION, init::RUST_ANALYZER_TOML};

// Create a directory relative to the current directory and print its path.
fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> {
    create_dir(dir_name)
        .with_context(|| format!("Failed to create the directory {current_dir}/{dir_name}"))?;
    println!("Created the directory {current_dir}/{dir_name}");
    Ok(())
}

// Write a file relative to the current directory and print its path.
fn write_rel_file<C>(file_name: &str, current_dir: &str, content: C) -> Result<()>
where
    C: AsRef<[u8]>,
{
    fs::write(file_name, content)
        .with_context(|| format!("Failed to create the file {current_dir}/{file_name}"))?;
    // Space to align with `create_rel_dir`.
    println!("Created the file      {current_dir}/{file_name}");
    Ok(())
}

pub fn new(path: &Path, no_git: bool) -> Result<()> {
    let dir_path_str = path.to_string_lossy();

    create_dir(path).with_context(|| format!("Failed to create the directory {dir_path_str}"))?;
    println!("Created the directory {dir_path_str}");

    set_current_dir(path)
        .with_context(|| format!("Failed to set {dir_path_str} as the current directory"))?;

    if !no_git
        && !Command::new("git")
            .arg("init")
            .status()
            .context("Failed to run `git init`")?
            .success()
    {
        bail!("`git init` didn't run successfully. See the possible error message above");
    }

    write_rel_file(".gitignore", &dir_path_str, GITIGNORE)?;

    create_rel_dir("exercises", &dir_path_str)?;
    create_rel_dir("solutions", &dir_path_str)?;

    write_rel_file(
        "info.toml",
        &dir_path_str,
        format!(
            "{INFO_FILE_BEFORE_FORMAT_VERSION}{CURRENT_FORMAT_VERSION}{INFO_FILE_AFTER_FORMAT_VERSION}"
        ),
    )?;

    write_rel_file("Cargo.toml", &dir_path_str, CARGO_TOML)?;

    write_rel_file("README.md", &dir_path_str, README)?;

    write_rel_file("rust-analyzer.toml", &dir_path_str, RUST_ANALYZER_TOML)?;

    create_rel_dir(".vscode", &dir_path_str)?;
    write_rel_file(
        ".vscode/extensions.json",
        &dir_path_str,
        crate::init::VS_CODE_EXTENSIONS_JSON,
    )?;

    println!("\nInitialization done ✓");

    Ok(())
}

pub const GITIGNORE: &[u8] = b"Cargo.lock
target/
.vscode/
!.vscode/extensions.json
";

const INFO_FILE_BEFORE_FORMAT_VERSION: &str =
    "# The format version is an indicator of the compatibility of community exercises with the
# Rustlings program.
# The format version is not the same as the version of the Rustlings program.
# In case Rustlings makes an unavoidable breaking change to the expected format of community
# exercises, you would need to raise this version and adapt to the new format.
# Otherwise, the newest version of the Rustlings program won't be able to run these exercises.
format_version = ";

const INFO_FILE_AFTER_FORMAT_VERSION: &str = r#"

# Optional multi-line message to be shown to users when just starting with the exercises.
welcome_message = """Welcome to these community Rustlings exercises."""

# Optional multi-line message to be shown to users after finishing all exercises.
final_message = """We hope that you found the exercises helpful :D"""

# Repeat this section for every exercise.
[[exercises]]
# Exercise name which is the exercise file name without the `.rs` extension.
name = "???"

# Optional directory name to be provided if you want to organize exercises in directories.
# If `dir` is specified, the exercise path is `exercises/DIR/NAME.rs`
# Otherwise, the path is `exercises/NAME.rs`
# dir = "???"

# Rustlings expects the exercise to contain tests and run them.
# You can optionally disable testing by setting `test` to `false` (the default is `true`).
# In that case, the exercise will be considered done when it just successfully compiles.
# test = true

# Rustlings will always run Clippy on exercises.
# You can optionally set `strict_clippy` to `true` (the default is `false`) to only consider
# the exercise as done when there are no warnings left.
# strict_clippy = false

# A multi-line hint to be shown to users on request.
hint = """???"""
"#;

const CARGO_TOML: &[u8] =
    br#"# Don't edit the `bin` list manually! It is updated by `rustlings dev update`
bin = []

[package]
name = "exercises"
edition = "2024"
# Don't publish the exercises on crates.io!
publish = false

[dependencies]
"#;

const README: &str = "# Rustlings 🦀

Welcome to these community Rustlings exercises 😃

First, [install Rustlings using the official instructions](https://github.com/rust-lang/rustlings) ✅

Then, clone this repository, open a terminal in this directory and run `rustlings` to get started with the exercises 🚀
";
