use std::path::PathBuf;

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

use crate::repository::Repository;
use crate::Error;

#[derive(Clap, Debug)]
pub struct Reset {
    /// 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")]
    pub repo_path: Option<PathBuf>,
    /// Reset the working copy to this channel, and change the current channel to this channel.
    #[clap(long = "channel")]
    pub channel: Option<String>,
    /// Print this file to the standard output, without modifying the repository (works for a single file only).
    #[clap(long = "dry-run")]
    pub dry_run: bool,
    /// Only reset these files
    pub files: Vec<PathBuf>,
}

impl Reset {
    pub fn run(self) -> Result<(), anyhow::Error> {
        self.reset(true)
    }

    pub fn switch(self) -> Result<(), anyhow::Error> {
        self.reset(false)
    }

    fn reset(self, overwrite_changes: bool) -> Result<(), anyhow::Error> {
        let has_repo_path = self.repo_path.is_some();
        let mut repo = Repository::find_root(self.repo_path)?;
        let mut txn = repo.pristine.mut_txn_begin();
        let channel_name = repo.config.get_current_channel(self.channel.as_ref());
        let mut channel = if let Some(channel) = txn.load_channel(&channel_name) {
            channel
        } else {
            return Err((Error::NoSuchChannel {
                channel: channel_name.to_string(),
            })
            .into());
        };
        if self.dry_run {
            if self.files.len() != 1 {
                bail!("reset --dry-run needs exactly one file");
            }
            let (pos, _ambiguous) = if has_repo_path {
                let root = std::fs::canonicalize(repo.path.join(&self.files[0]))?;
                let path = root.strip_prefix(&repo.path)?.to_str().unwrap();
                txn.follow_oldest_path(&repo.changes, &channel, &path)?
            } else {
                let mut root = crate::current_dir()?;
                root.push(&self.files[0]);
                let root = std::fs::canonicalize(&root)?;
                let path = root.strip_prefix(&repo.path)?.to_str().unwrap();
                txn.follow_oldest_path(&repo.changes, &channel, &path)?
            };
            txn.output_file(
                &repo.changes,
                &channel,
                pos,
                &mut libpijul::vertex_buffer::Writer::new(std::io::stdout()),
            )?;
        } else {
            let current_channel = repo.config.get_current_channel(None);
            if self.channel.as_deref() == Some(current_channel) {
                if !overwrite_changes {
                    // No need to change channel, no need to reset.
                    return Ok(());
                }
            } else if self.channel.is_some() {
                if let Some(mut channel) = txn.load_channel(current_channel) {
                    let mut state = libpijul::RecordBuilder::new();
                    txn.record(
                        &mut state,
                        libpijul::Algorithm::default(),
                        &mut channel,
                        &mut repo.working_copy,
                        &repo.changes,
                        "",
                    )?;
                    let rec = state.finish();
                    debug!("actions = {:?}", rec.actions);
                    if !rec.actions.is_empty() {
                        return Err(Error::UnrecordedChanges.into());
                    }
                }
            }

            if self.channel.is_some() {
                repo.config.current_channel = self.channel;
                repo.save_config()?;
            }

            if self.files.is_empty() {
                txn.output_repository_no_pending(
                    &mut repo.working_copy,
                    &repo.changes,
                    &mut channel,
                    "",
                    true,
                )?;
            } else {
                for root in self.files.iter() {
                    let root = std::fs::canonicalize(&root)?;
                    let path = root.strip_prefix(&repo.path)?.to_str().unwrap();
                    txn.output_repository_no_pending(
                        &mut repo.working_copy,
                        &repo.changes,
                        &mut channel,
                        &path,
                        true,
                    )?;
                }
            }
            txn.commit()?
        }
        Ok(())
    }
}
