// SPDX-License-Identifier: LicenseRef-Commercial
// Git Context Injection — lightweight git CLI wrapper for agent session context.
// Full git2/libgit2 implementation deferred until git2 dep is added.

use serde::Serialize;
use std::process::Command;

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GitContext {
    pub branch: String,
    pub last_commits: Vec<CommitSummary>,
    pub modified_files: Vec<String>,
    pub has_unstaged: bool,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CommitSummary {
    pub hash: String,
    pub message: String,
    pub author: String,
    pub timestamp: i64,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BranchInfo {
    pub name: String,
    pub is_protected: bool,
    pub upstream: Option<String>,
    pub ahead: i64,
    pub behind: i64,
}

fn git_cmd(project_path: &str, args: &[&str]) -> Result<String, String> {
    if project_path.starts_with('-') {
        return Err("Invalid project path: cannot start with '-'".into());
    }
    let output = Command::new("git")
        .args(["-C", project_path])
        .args(args)
        .output()
        .map_err(|e| format!("Failed to run git: {e}"))?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
        Err(format!("git error: {stderr}"))
    }
}

fn parse_log_line(line: &str) -> Option<CommitSummary> {
    // Format: hash|author|timestamp|message
    let parts: Vec<&str> = line.splitn(4, '|').collect();
    if parts.len() < 4 { return None; }
    Some(CommitSummary {
        hash: parts[0].to_string(),
        author: parts[1].to_string(),
        timestamp: parts[2].parse().unwrap_or(0),
        message: parts[3].to_string(),
    })
}

#[tauri::command]
pub fn pro_git_context(project_path: String) -> Result<GitContext, String> {
    let branch = git_cmd(&project_path, &["branch", "--show-current"])
        .unwrap_or_else(|_| "unknown".into());

    let log_output = git_cmd(
        &project_path,
        &["log", "--format=%H|%an|%at|%s", "-10"],
    ).unwrap_or_default();

    let last_commits: Vec<CommitSummary> = log_output
        .lines()
        .filter_map(parse_log_line)
        .collect();

    let status_output = git_cmd(&project_path, &["status", "--porcelain"])
        .unwrap_or_default();

    let modified_files: Vec<String> = status_output
        .lines()
        .filter(|l| !l.is_empty())
        .map(|l| {
            // Format: XY filename (first 3 chars are status + space)
            if l.len() > 3 { l[3..].to_string() } else { l.to_string() }
        })
        .collect();

    let has_unstaged = status_output.lines().any(|l| {
        l.len() >= 2 && !l[1..2].eq(" ") && !l[1..2].eq("?")
    });

    Ok(GitContext { branch, last_commits, modified_files, has_unstaged })
}

#[tauri::command]
pub fn pro_git_inject(project_path: String, max_tokens: Option<i64>) -> Result<String, String> {
    let ctx = pro_git_context(project_path)?;
    let max_chars = (max_tokens.unwrap_or(1000) * 3) as usize;

    let mut md = String::new();
    md.push_str(&format!("## Git Context\n\n**Branch:** {}\n\n", ctx.branch));

    if !ctx.last_commits.is_empty() {
        md.push_str("**Recent commits:**\n");
        for c in &ctx.last_commits {
            let short_hash = if c.hash.len() >= 7 { &c.hash[..7] } else { &c.hash };
            let line = format!("- {} {}\n", short_hash, c.message);
            if md.len() + line.len() > max_chars { break; }
            md.push_str(&line);
        }
        md.push('\n');
    }

    if !ctx.modified_files.is_empty() {
        md.push_str("**Modified files:**\n");
        for f in &ctx.modified_files {
            let line = format!("- {f}\n");
            if md.len() + line.len() > max_chars { break; }
            md.push_str(&line);
        }
    }

    Ok(md)
}

#[tauri::command]
pub fn pro_git_branch_info(project_path: String) -> Result<BranchInfo, String> {
    let name = git_cmd(&project_path, &["branch", "--show-current"])
        .unwrap_or_else(|_| "unknown".into());

    let upstream = git_cmd(
        &project_path,
        &["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
    ).ok();

    let (ahead, behind) = if upstream.is_some() {
        let counts = git_cmd(
            &project_path,
            &["rev-list", "--left-right", "--count", "HEAD...@{u}"],
        ).unwrap_or_else(|_| "0\t0".into());
        let parts: Vec<&str> = counts.split('\t').collect();
        let a = parts.first().and_then(|s| s.parse().ok()).unwrap_or(0);
        let b = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
        (a, b)
    } else {
        (0, 0)
    };

    let is_protected = matches!(name.as_str(), "main" | "master")
        || name.starts_with("release/");

    Ok(BranchInfo { name, is_protected, upstream, ahead, behind })
}

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

    #[test]
    fn test_git_context_serializes_camel_case() {
        let ctx = GitContext {
            branch: "main".into(),
            last_commits: vec![],
            modified_files: vec!["src/lib.rs".into()],
            has_unstaged: true,
        };
        let json = serde_json::to_string(&ctx).unwrap();
        assert!(json.contains("lastCommits"));
        assert!(json.contains("modifiedFiles"));
        assert!(json.contains("hasUnstaged"));
    }

    #[test]
    fn test_commit_summary_serializes_camel_case() {
        let c = CommitSummary {
            hash: "abc1234".into(),
            message: "feat: add router".into(),
            author: "dev".into(),
            timestamp: 1710000000,
        };
        let json = serde_json::to_string(&c).unwrap();
        assert!(json.contains("\"hash\":\"abc1234\""));
        assert!(json.contains("\"timestamp\":1710000000"));
    }

    #[test]
    fn test_branch_info_serializes_camel_case() {
        let b = BranchInfo {
            name: "feature/test".into(),
            is_protected: false,
            upstream: Some("origin/feature/test".into()),
            ahead: 2,
            behind: 0,
        };
        let json = serde_json::to_string(&b).unwrap();
        assert!(json.contains("isProtected"));
    }

    #[test]
    fn test_parse_log_line() {
        let line = "abc123|Author Name|1710000000|feat: test commit";
        let c = parse_log_line(line).unwrap();
        assert_eq!(c.hash, "abc123");
        assert_eq!(c.author, "Author Name");
        assert_eq!(c.message, "feat: test commit");
    }
}
