use std::io::Write;
use std::path::PathBuf;

use crate::repository::Repository;
use anyhow::anyhow;
use clap::Clap;
use libpijul::{MutTxnT, TxnT};

#[derive(Clap, Debug)]
pub struct Channel {
    /// 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>,
    #[clap(subcommand)]
    subcmd: Option<SubCommand>,
}

#[derive(Clap, Debug)]
pub enum SubCommand {
    /// Delete a channel.
    /// The channel must not be the current channel.
    #[clap(name = "delete")]
    Delete { delete: String },
    /// Rename a channel.
    #[clap(name = "rename")]
    Rename { from: String, to: Option<String> },
    /// Switch to a channel.
    /// There must not be unrecorded changes in the working copy.
    #[clap(name = "switch")]
    Switch { to: Option<String> },
}

impl Channel {
    pub fn run(self) -> Result<(), anyhow::Error> {
        let mut stdout = std::io::stdout();
        match self.subcmd {
            None => {
                let repo = Repository::find_root(self.repo_path)?;
                let current = repo.config.current_channel();
                let txn = repo.pristine.txn_begin()?;
                for channel in txn.iter_channels("") {
                    let channel = channel.borrow();
                    let name = channel.name();
                    if current == Some(name) {
                        writeln!(stdout, "* {}", name)?;
                    } else {
                        writeln!(stdout, "  {}", name)?;
                    }
                }
            }
            Some(SubCommand::Delete { ref delete }) => {
                let repo = Repository::find_root(self.repo_path)?;
                let current = repo.config.current_channel();
                if Some(delete.as_str()) == current {
                    return Err(crate::Error::CannotDeleteCurrentChannel.into());
                }
                let mut txn = repo.pristine.mut_txn_begin();
                if !txn.drop_channel(delete)? {
                    return Err(anyhow!("Channel {} not found", delete));
                }
                txn.commit()?;
            }
            Some(SubCommand::Switch { to }) => {
                (crate::commands::reset::Reset {
                    repo_path: self.repo_path,
                    channel: to,
                    dry_run: false,
                    files: Vec::new(),
                })
                .switch()?;
            }
            Some(SubCommand::Rename { ref from, ref to }) => {
                let repo = Repository::find_root(self.repo_path)?;
                let current = repo.config.current_channel();
                let mut txn = repo.pristine.mut_txn_begin();
                let (from, to) = if let Some(to) = to {
                    (from.as_str(), to.as_str())
                } else if let Some(current) = current {
                    (current, from.as_str())
                } else {
                    return Err(crate::Error::NoCurrentChannel.into());
                };
                let mut channel = if let Some(channel) = txn.load_channel(from) {
                    channel
                } else {
                    return Err((crate::Error::ChannelNotFound {
                        channel: from.to_string(),
                    })
                    .into());
                };
                txn.rename_channel(&mut channel, to)?;
                txn.commit()?;
            }
        }
        Ok(())
    }
}
