use std::borrow::Cow;
use std::collections::BTreeSet;
use std::io;

use gix_features::progress::Progress;
use gix_protocol::handshake::Ref;
use gix_protocol::ls_refs::RefPrefixes;
use gix_protocol::transport::Protocol;
use gix_protocol::{Handshake, ls_refs};
use gix_transport::bstr::BString;

use crate::stage::RefPrefix;

use super::{Connection, agent_name};

/// Configuration for running an ls-refs process.
///
/// See [`run`].
pub struct Config {
    /// The repository name, i.e. `/<rid>`.
    #[allow(dead_code)]
    pub repo: BString,
    /// Ref prefixes for filtering the output of the ls-refs process.
    pub prefixes: BTreeSet<RefPrefix>,
}

/// Run the ls-refs process using the provided `config`.
///
/// It is expected that the `handshake` was run outside of this
/// process, since it should be reused across fetch processes.
///
/// The resulting set of references are the ones returned by the
/// ls-refs process, filtered by any prefixes that were provided by
/// the `config`.
pub(crate) fn run<R, W>(
    config: Config,
    handshake: &Handshake,
    mut conn: Connection<R, W>,
    progress: &mut impl Progress,
) -> Result<Vec<Ref>, ls_refs::Error>
where
    R: io::Read,
    W: io::Write,
{
    log::trace!("Performing ls-refs: {:?}", config.prefixes);
    let Handshake {
        server_protocol_version: protocol,
        capabilities,
        ..
    } = handshake;

    if protocol != &Protocol::V2 {
        return Err(ls_refs::Error::Io(io::Error::other(
            "expected protocol version 2",
        )));
    }

    let prefixes = config
        .prefixes
        .iter()
        .map(|prefix| prefix.into_bstring())
        .collect::<Vec<_>>();

    let mut refspecs = RefPrefixes::new();
    refspecs.extend(prefixes.clone());

    let ls_refs = gix_protocol::LsRefsCommand::new(
        Some(refspecs),
        capabilities,
        ("agent", Some(Cow::Owned(agent_name()))),
    );

    // According to [1], in the section on `ls-refs`, we must still filter on
    // this side, since `ref-prefix` is simply an optimization.
    //
    // [1]: https://mirrors.edge.kernel.org/pub/software/scm/git/docs/gitprotocol-v2.html
    let refs = ls_refs
        .invoke_blocking(&mut conn, progress, false)?
        .into_iter()
        .filter(|r| {
            let (refname, _, _) = r.unpack();
            prefixes.iter().any(|prefix| refname.starts_with(prefix))
        })
        .collect();

    log::trace!("ls-refs received: {refs:#?}");
    Ok(refs)
}
