use std::{
    convert::TryInto,
    ops::Add,
    time::{Duration, SystemTime, UNIX_EPOCH},
};

use miette::{IntoDiagnostic, Result, WrapErr};

use crate::{
    external::Vcs,
    mit::{
        cmd::{errors::Error::NoAuthorsToSet, vcs::has_vcs_coauthor, CONFIG_KEY_EXPIRES},
        Author,
    },
};

/// # Errors
///
/// If writing to the git mit file fails for some reason.
/// This reason will be specific to VCS implementation
pub fn set_commit_authors(
    config: &mut dyn Vcs,
    authors: &[&Author<'_>],
    expires_in: Duration,
) -> Result<()> {
    let (first_author, others) = authors.split_first().ok_or(NoAuthorsToSet)?;

    remove_coauthors(config)?;
    set_vcs_user(config, first_author)?;
    set_vcs_coauthors(config, others)?;
    set_vcs_expires_time(config, expires_in)?;

    Ok(())
}

pub fn remove_coauthors(config: &mut dyn Vcs) -> Result<()> {
    get_defined_vcs_coauthor_keys(config)
        .into_iter()
        .try_for_each(|key| config.remove(&key))?;

    Ok(())
}

#[allow(clippy::maybe_infinite_iter)]
fn get_defined_vcs_coauthor_keys(config: &dyn Vcs) -> Vec<String> {
    (0..)
        .take_while(|index| has_vcs_coauthor(config, *index))
        .flat_map(|index| {
            [
                format!("mit.author.coauthors.{index}.name"),
                format!("mit.author.coauthors.{index}.email"),
            ]
        })
        .collect()
}

fn set_vcs_coauthors(config: &mut dyn Vcs, authors: &[&Author<'_>]) -> Result<()> {
    authors
        .iter()
        .enumerate()
        .try_for_each(|(index, author)| set_vcs_coauthor(config, index, author))
}

pub fn set_vcs_coauthor(config: &mut dyn Vcs, index: usize, author: &Author<'_>) -> Result<()> {
    set_vcs_coauthor_name(config, index, author)?;
    set_vcs_coauthor_email(config, index, author)?;

    Ok(())
}

fn set_vcs_coauthor_name(config: &mut dyn Vcs, index: usize, author: &Author<'_>) -> Result<()> {
    config.set_str(&format!("mit.author.coauthors.{index}.name"), author.name())?;
    Ok(())
}

fn set_vcs_coauthor_email(config: &mut dyn Vcs, index: usize, author: &Author<'_>) -> Result<()> {
    config.set_str(
        &format!("mit.author.coauthors.{index}.email"),
        author.email(),
    )?;
    Ok(())
}

pub fn set_vcs_user(config: &mut dyn Vcs, author: &Author<'_>) -> Result<()> {
    config.set_str("user.name", author.name())?;
    config.set_str("user.email", author.email())?;
    set_author_signing_key(config, author)?;

    Ok(())
}

fn set_author_signing_key(config: &mut dyn Vcs, author: &Author<'_>) -> Result<()> {
    if let Some(key) = author.signingkey() {
        config
            .set_str("user.signingkey", key)
            .wrap_err("failed to set git author's signing key ")
    } else {
        if config.get_str("user.signingkey")?.is_some() {
            config
                .remove("user.signingkey")
                .wrap_err("failed to remove git author's signing key")?;
        }
        Ok(())
    }
}

fn set_vcs_expires_time(config: &mut dyn Vcs, expires_in: Duration) -> Result<()> {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .into_diagnostic()?;
    let expiry_time = now.add(expires_in).as_secs().try_into().into_diagnostic()?;
    config
        .set_i64(CONFIG_KEY_EXPIRES, expiry_time)
        .wrap_err("failed to set author expiry time")
}
