use crate::prelude::pijul;
use crate::repo;

use derivative::Derivative;
use jiff::Timestamp;
use libpijul::key::SKey;
use pijul_identity::Credentials;
use pijul_repository::Repository;
use tempfile::{tempdir, TempDir};

use std::path::{Path, PathBuf};
use std::sync::Arc;

pub const INITIAL_LOG_LEN: usize = 2;
pub const DEFAULT_IGNORE_FILE: &str = ".ignore";

/// Get the path to the test repo
pub fn repo_path(repo: &TestRepo) -> PathBuf {
    let TestRepo {
        rootdir: dir,
        subdir,
    } = repo;
    if let Some(subdir) = subdir {
        dir.path().join(subdir)
    } else {
        dir.path().to_path_buf()
    }
}

/// Setup a Pijul user ID in a temp dir. Sets `PIJUL_CONFIG_DIR` env var, which
/// is used by pijul when loading ID
pub fn setup_test_user_id() -> TestUserId {
    let dir = tempdir().unwrap();
    unsafe { std::env::set_var("PIJUL_CONFIG_DIR", dir.path().as_os_str()) };

    let skey = SKey::generate(None);
    let secret_key = skey.save(None);
    let public_key = skey.public_key();

    let id = pijul_identity::Complete {
        name: "Fandan".to_string(),
        config: pijul_identity::IdentityConfig {
            author: pijul_config::author::Author {
                username: "fandan".to_string(),
                display_name: "Fan Dan".to_string(),
                email: "fan@dan.com".to_string(),
                origin: "ssh.pijul.com".to_string(),
                key_path: None,
            },
            key_path: None,
        },
        last_modified: Timestamp::now(),
        public_key,
        // TODO what happens when this is None? Currently `identity::load` fails
        // as it needs to be able to read the key
        credentials: Some(Credentials::new(secret_key, None)),
    };
    id.write().unwrap();

    TestUserId { dir, skey }
}

/// Setup a Pijul repo in a temp dir.
///
/// This repo has 2 initial changes recorded.
pub fn setup_test_repo() -> TestRepo {
    setup_test_repo_in_subdir("")
}

/// Setup a Pijul repo inside a given subdir in a temp dir.
///
/// This repo has 2 initial changes recorded.
pub fn setup_test_repo_in_subdir<P: AsRef<Path>>(subdir: P) -> TestRepo {
    let rootdir = tempdir().unwrap();
    let repo_dir = rootdir.path().join(&subdir);
    let mut repo = init_pijul_repo(repo_dir.clone());

    // Select and record the default ".ignore" file
    let recursive = false;
    repo::add(&mut repo, DEFAULT_IGNORE_FILE, recursive).unwrap();

    let (internal, _state) = repo::load(&repo_dir).unwrap();
    let skey = Arc::new(SKey::generate(None));
    // This creates 2 records - one to add root, other is the actual ".ignore"
    // addition with the given message
    let to_record = crate::to_record::State::default();
    repo::record(
        &internal,
        "Initialized".to_string(),
        None,
        skey,
        "ID".to_string(),
        to_record,
    )
    .unwrap();

    let log = repo::get_log(&internal.repo, None, None, None).unwrap();
    assert_eq!(log.len(), INITIAL_LOG_LEN);

    TestRepo {
        rootdir,
        subdir: if subdir.as_ref().as_os_str().is_empty() {
            None
        } else {
            Some(subdir.as_ref().to_path_buf())
        },
    }
}

// Init a new pijul repo inside the given dir. This repo will have recorded
// changes and an untracked default ".ignore" file.
pub fn init_pijul_repo(dir: PathBuf) -> pijul::Repository {
    let config = pijul_config::Config::default();
    let repo = Repository::init(&config, Some(&dir), None, None).unwrap();
    let mut txn = repo.pristine.mut_txn_begin().unwrap();
    let channel_name = libpijul::DEFAULT_CHANNEL.to_string();
    libpijul::MutTxnT::open_or_create_channel(&mut txn, &channel_name).unwrap();
    libpijul::MutTxnT::set_current_channel(&mut txn, &channel_name).unwrap();
    libpijul::MutTxnT::commit(txn).unwrap();

    repo
}

#[derive(Derivative)]
#[derivative(Debug)]
pub struct TestUserId {
    pub dir: TempDir,
    #[derivative(Debug = "ignore")]
    pub skey: SKey,
}

#[derive(Debug)]
pub struct TestRepo {
    pub rootdir: TempDir,
    pub subdir: Option<PathBuf>,
}