use std::path::Path;

use clap::Parser;
use pijul_core::changestore::ChangeStore;
use pijul_core::{Base32, Hash, TxnT};

use crate::commands::common_opts::RepoPath;

#[derive(Parser, Debug)]
pub struct Approve {
    #[clap(flatten)]
    base: RepoPath,
    /// The hash of the change to approve, or an unambiguous prefix thereof
    #[clap(value_name = "HASH")]
    hash: String,
    /// Identity to sign with (defaults to the configured identity)
    #[clap(long = "identity")]
    identity: Option<String>,
}

impl Approve {
    pub fn repository_path(&mut self) -> Option<&Path> {
        self.base.repo_path()
    }

    pub async fn run(mut self, config: &pijul_config::Config) -> Result<(), anyhow::Error> {
        let repo = self.base.find_root()?;
        let txn = repo.pristine.txn_begin()?;

        let hash = if let Some(h) = Hash::from_base32(self.hash.as_bytes()) {
            h
        } else {
            txn.hash_from_prefix(&self.hash)?.0
        };

        let mut change = repo.changes.get_change(&hash)?;

        let identity_name = self
            .identity
            .unwrap_or(pijul_identity::choose_identity_name(config).await?);
        let complete = pijul_identity::Complete::load(&identity_name)?;
        let secret = complete.skey();
        let public_key = complete.public_key.key.clone();

        let sig = pijul_identity::sign_pem(&secret, &hash.to_bytes()).await?;

        let entry = serde_json::json!({ "key": public_key, "signature": sig });
        match change.unhashed {
            None => {
                change.unhashed = Some(serde_json::json!({ "approvals": [entry] }));
            }
            Some(ref mut v) => {
                if let Some(approvals) = v.get_mut("approvals").and_then(|a| a.as_array_mut()) {
                    approvals.push(entry);
                } else {
                    v["approvals"] = serde_json::json!([entry]);
                }
            }
        }

        let new_hash = repo
            .changes
            .save_change(&mut change, |_, _| Ok::<_, anyhow::Error>(()))?;

        assert_eq!(
            new_hash, hash,
            "approve: hash changed after updating unhashed — this is a bug"
        );

        let mut stdout = std::io::stdout();
        use std::io::Write;
        writeln!(stdout, "Approved: {}", hash.to_base32())?;
        Ok(())
    }
}
