use crate::info::utils::info_field::InfoField;
use anyhow::{Context, Result};
use gix::Repository;
use serde::Serialize;

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HeadRefs {
    short_commit_id: String,
    refs: Vec<String>,
}

impl HeadRefs {
    pub fn new(short_commit_id: String, refs: Vec<String>) -> HeadRefs {
        HeadRefs {
            short_commit_id,
            refs,
        }
    }
}

impl std::fmt::Display for HeadRefs {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        if self.refs.is_empty() {
            write!(f, "{}", self.short_commit_id)
        } else {
            let refs_str = self
                .refs
                .iter()
                .map(String::as_str)
                .collect::<Vec<&str>>()
                .join(", ");
            write!(f, "{} ({})", self.short_commit_id, refs_str)
        }
    }
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HeadInfo {
    pub head_refs: HeadRefs,
}

impl HeadInfo {
    pub fn new(repo: &Repository) -> Result<Self> {
        let head_refs = get_head_refs(repo)?;
        Ok(Self { head_refs })
    }
}

fn get_head_refs(repo: &Repository) -> Result<HeadRefs> {
    let head_id = repo.head_id().context("Failed to retrieve HEAD ID")?;

    let mut ref_names = Vec::new();

    if let Some(head_ref) = repo.head_ref()? {
        let head_ref_name = head_ref.name().shorten().to_string();
        ref_names.push(head_ref_name);

        if let Some(Ok(remote_tracking_ref)) =
            repo.branch_remote_tracking_ref_name(head_ref.name(), gix::remote::Direction::Push)
        {
            let remote_tracking_ref_name = remote_tracking_ref.shorten().to_string();
            ref_names.push(remote_tracking_ref_name);
        }
    }

    Ok(HeadRefs::new(head_id.shorten()?.to_string(), ref_names))
}

#[typetag::serialize]
impl InfoField for HeadInfo {
    fn value(&self) -> String {
        self.head_refs.to_string()
    }

    fn title(&self) -> String {
        "HEAD".into()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_display_head_refs() {
        let head = HeadRefs::new("be561d5".into(), vec!["main".into(), "origin/main".into()]);
        assert_eq!(head.to_string(), "be561d5 (main, origin/main)");
    }

    #[test]
    fn test_display_head_refs_with_no_refs() {
        let head = HeadRefs::new("be561d5".into(), vec![]);
        assert_eq!(head.to_string(), "be561d5");
    }
}
