use std::path::PathBuf;

use anyhow::bail;
use clap::Clap;
use libpijul::changestore::ChangeStore;
use libpijul::{Base32, MutTxnT, MutTxnTExt, TxnT};
use log::debug;

use crate::repository::Repository;

#[derive(Clap, Debug)]
pub struct Unrecord {
    /// Set the repository where this command should run. Defaults to the first ancestor of the current directory that contains a `.pijul` directory.
    #[clap(long = "repository")]
    repo_path: Option<PathBuf>,
    /// Unrecord changes from this channel instead of the current channel
    #[clap(long = "channel")]
    channel: Option<String>,
    /// Undo the change in the working copy (preserving unrecorded changes if there are any)
    #[clap(long = "reset")]
    reset: bool,
    /// Identifier of the change (unambiguous prefixes are accepted)
    #[clap(multiple = true)]
    change_id: Vec<String>,
}

impl Unrecord {
    pub fn run(self) -> Result<(), anyhow::Error> {
        let mut repo = Repository::find_root(self.repo_path)?;
        debug!("{:?}", repo.config);
        let channel_name = repo.config.get_current_channel(self.channel.as_ref());
        let mut txn = repo.pristine.mut_txn_begin();
        if let Some(mut channel) = txn.load_channel(channel_name) {
            let pending_hash = if self.reset {
                super::pending(&mut txn, &mut channel, &mut repo)?
            } else {
                None
            };

            let mut changes = Vec::new();
            let channel_ = channel.borrow();
            for c in self.change_id {
                let (hash, change_id) = txn.hash_from_prefix(&c)?;
                let n = txn
                    .get_changeset(&channel_.changes, change_id, None)
                    .unwrap();
                changes.push((hash, change_id, n, c))
            }
            std::mem::drop(channel_);
            changes.sort_by(|a, b| b.2.cmp(&a.2));
            for (hash, change_id, _, c) in changes {
                let channel_ = channel.borrow();
                for (p, d) in txn.iter_revdep(change_id) {
                    if p < change_id {
                        continue;
                    } else if p > change_id {
                        break;
                    }
                    if txn.get_changeset(&channel_.changes, d, None).is_some() {
                        let dep = txn.get_external(d).unwrap();
                        if Some(dep) == pending_hash {
                            bail!(
                                "Cannot unrecord change {} because unrecorded changes depend on it",
                                c
                            );
                        } else {
                            bail!(
                                "Cannot unrecord change {} because {} depend on it",
                                c,
                                dep.to_base32()
                            );
                        }
                    }
                }
                std::mem::drop(channel_);
                txn.unrecord(&repo.changes, &mut channel, &hash)?;
            }
            if let Some(h) = pending_hash {
                txn.output_repository_no_pending(
                    &mut repo.working_copy,
                    &repo.changes,
                    &mut channel,
                    "",
                    true,
                )?;
                txn.unrecord(&repo.changes, &mut channel, &h)?;
                if cfg!(feature = "keep-changes") {
                    repo.changes.del_change(&h)?;
                }
            }
        }
        txn.commit()?;
        Ok(())
    }
}
