use std::path::Path;

use crate::{
    bare::GitBare,
    cmd::{
        blame::{BlameOptions, CommitBlameHunk},
        oid::ObjectId,
    },
    errors::GitResult,
};

impl GitBare {
    pub fn blame_file(
        &self,
        oid: ObjectId,
        path: impl AsRef<Path>,
        opts: Option<BlameOptions>,
    ) -> GitResult<Vec<CommitBlameHunk>> {
        let repo = self.gix_repo()?;
        let gix_id: gix::hash::ObjectId = (&oid).try_into()?;

        let blame_opts = opts.unwrap_or_default();
        let ranges = if let (Some(min), Some(max)) =
            (blame_opts.min_line, blame_opts.max_line)
        {
            gix::blame::BlameRanges::from_one_based_inclusive_range(
                min as u32..=max as u32,
            )?
        } else if let Some(min) = blame_opts.min_line {
            gix::blame::BlameRanges::from_one_based_inclusive_range(
                min as u32..=min as u32,
            )?
        } else {
            gix::blame::BlameRanges::default()
        };

        let rewrites = if blame_opts.track_copies_same_file
            || blame_opts.track_copies_same_commit_moves
        {
            Some(gix::diff::Rewrites::default())
        } else {
            None
        };

        let gix_options = gix::repository::blame_file::Options {
            diff_algorithm: None,
            ranges,
            since: None,
            rewrites,
        };

        let outcome = repo.blame_file(
            path.as_ref()
                .as_os_str()
                .to_string_lossy()
                .as_bytes()
                .as_ref(),
            gix_id,
            gix_options,
        )?;

        let mut hunks = Vec::new();

        for entry in outcome.entries {
            let range_in_blamed = entry.range_in_blamed_file();
            let range_in_source = entry.range_in_source_file();

            let commit_oid =
                ObjectId::new(entry.commit_id.to_hex().to_string());
            let final_start_line = range_in_blamed.start as u32 + 1;
            let final_lines = range_in_blamed.len() as u32;
            let orig_start_line = range_in_source.start as u32 + 1;
            let orig_lines = range_in_source.len() as u32;

            let is_boundary = repo
                .find_commit(entry.commit_id)
                .ok()
                .is_some_and(|c| c.parent_ids().count() == 0);

            let orig_path =
                entry.source_file_name.as_ref().map(|p| p.to_string());

            hunks.push(CommitBlameHunk {
                commit_oid,
                final_start_line,
                final_lines,
                orig_start_line,
                orig_lines,
                boundary: is_boundary,
                orig_path,
            });
        }

        Ok(hunks)
    }
}
