//! Git worktree management

use anyhow::{Context, Result, bail};
use git2::Repository;
use std::fs;
use std::path::{Path, PathBuf};
use tracing::{debug, info, warn};

#[cfg(windows)]
#[expect(
    clippy::permissions_set_readonly_false,
    reason = "Windows worktree cleanup needs to clear the read-only attribute"
)]
fn clear_readonly_recursive(root: &Path) {
    let mut stack = vec![root.to_path_buf()];
    while let Some(path) = stack.pop() {
        if let Ok(metadata) = fs::metadata(&path) {
            let mut permissions = metadata.permissions();
            if permissions.readonly() {
                permissions.set_readonly(false);
                let _ = fs::set_permissions(&path, permissions);
            }
        }

        if let Ok(entries) = fs::read_dir(&path) {
            for entry in entries.flatten() {
                let child = entry.path();
                if child.is_dir() {
                    stack.push(child);
                } else if let Ok(metadata) = entry.metadata() {
                    let mut permissions = metadata.permissions();
                    if permissions.readonly() {
                        permissions.set_readonly(false);
                        let _ = fs::set_permissions(&child, permissions);
                    }
                }
            }
        }
    }
}

fn remove_dir_all_with_retries(path: &Path) -> Result<()> {
    if !path.exists() {
        return Ok(());
    }

    let mut last_err = None;
    for attempt in 0u64..10 {
        match fs::remove_dir_all(path) {
            Ok(()) => {
                last_err = None;
                break;
            }
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
                last_err = None;
                break;
            }
            Err(e) => {
                last_err = Some(e);
                #[cfg(windows)]
                clear_readonly_recursive(path);
            }
        }

        std::thread::sleep(std::time::Duration::from_millis(100 * (attempt + 1)));

        if !path.exists() {
            last_err = None;
            break;
        }
    }

    if path.exists() {
        let Some(e) = last_err else {
            bail!("Failed to remove directory at {}", path.display());
        };
        bail!("Failed to remove directory at {}: {e}", path.display());
    }

    Ok(())
}

/// Manager for git worktree operations
pub struct Manager<'a> {
    repo: &'a Repository,
}

impl std::fmt::Debug for Manager<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Manager").finish_non_exhaustive()
    }
}

impl<'a> Manager<'a> {
    /// Create a new worktree manager for the given repository
    #[must_use]
    pub const fn new(repo: &'a Repository) -> Self {
        Self { repo }
    }

    /// Create a new worktree for a branch
    ///
    /// # Errors
    ///
    /// Returns an error if the worktree cannot be created
    pub fn create(&self, path: &Path, branch: &str) -> Result<()> {
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent).with_context(|| {
                format!("Failed to create parent directory {}", parent.display())
            })?;
        }

        let branch_ref = self
            .repo
            .find_branch(branch, git2::BranchType::Local)
            .with_context(|| format!("Branch not found: {branch}"))?;

        let reference = branch_ref.into_reference();

        // Worktree name cannot contain slashes (it becomes a directory name in .git/worktrees/)
        let worktree_name = branch.replace('/', "-");

        self.repo
            .worktree(
                &worktree_name,
                path,
                Some(git2::WorktreeAddOptions::new().reference(Some(&reference))),
            )
            .with_context(|| format!("Failed to create worktree at {}", path.display()))?;

        Ok(())
    }

    /// Create a worktree with a new branch from HEAD
    ///
    /// If the branch already exists (e.g., from a previous run), it will be deleted
    /// and recreated from HEAD.
    ///
    /// # Errors
    ///
    /// Returns an error if the worktree or branch cannot be created
    pub fn create_with_new_branch(&self, path: &Path, branch: &str) -> Result<()> {
        debug!(branch, ?path, "Creating worktree with new branch");

        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent).with_context(|| {
                format!("Failed to create parent directory {}", parent.display())
            })?;
        }

        let head = self.repo.head().context("Failed to get HEAD")?;
        let commit = head.peel_to_commit().context("Failed to get HEAD commit")?;

        // Clean up any existing worktree/branch with this name (from a previous run)
        // Must remove worktree first, as the branch can't be deleted while it's
        // the HEAD of a linked worktree
        let worktree_name = branch.replace('/', "-");
        if let Ok(worktree) = self.repo.find_worktree(&worktree_name) {
            debug!(branch, "Removing existing worktree before recreation");
            let wt_path = worktree.path().to_path_buf();
            let _ = worktree.prune(Some(
                git2::WorktreePruneOptions::new()
                    .valid(true)
                    .working_tree(true),
            ));
            if wt_path.exists() {
                let _ = fs::remove_dir_all(&wt_path);
            }
        }

        // Now delete the branch if it exists
        let branch_mgr = super::BranchManager::new(self.repo);
        if branch_mgr.exists(branch) {
            debug!(branch, "Deleting existing branch before recreation");
            let _ = branch_mgr.delete(branch);
        }

        let branch_ref = self
            .repo
            .branch(branch, &commit, false)
            .with_context(|| format!("Failed to create branch '{branch}'"))?;

        let reference = branch_ref.into_reference();

        // Worktree name cannot contain slashes (it becomes a directory name in .git/worktrees/)
        let worktree_name = branch.replace('/', "-");

        self.repo
            .worktree(
                &worktree_name,
                path,
                Some(git2::WorktreeAddOptions::new().reference(Some(&reference))),
            )
            .with_context(|| format!("Failed to create worktree at {}", path.display()))?;

        info!(branch, ?path, "Worktree created");
        Ok(())
    }

    /// Remove a worktree and its associated branch
    ///
    /// Always attempts to delete the branch, even if the worktree is missing.
    /// This ensures cleanup works even if the worktree was manually removed.
    ///
    /// # Errors
    ///
    /// Returns an error if the worktree exists but cannot be removed after retries.
    /// Does not return errors for missing worktrees or branches.
    pub fn remove(&self, name: &str) -> Result<()> {
        debug!(name, "Removing worktree and branch");

        // Worktree name has slashes replaced with dashes
        let worktree_name = name.replace('/', "-");

        // Try to remove the worktree with retries (may already be gone)
        if let Ok(worktree) = self.repo.find_worktree(&worktree_name) {
            let wt_path = worktree.path().to_path_buf();

            // Retry prune up to 3 times with increasing delays
            // This handles race conditions where processes are still terminating
            let mut prune_succeeded = false;
            for attempt in 0u64..3 {
                if attempt > 0 {
                    debug!(name, attempt, "Retrying worktree prune");
                    std::thread::sleep(std::time::Duration::from_millis(100 * attempt));
                }

                match worktree.prune(Some(
                    git2::WorktreePruneOptions::new()
                        .valid(true)
                        .working_tree(true),
                )) {
                    Ok(()) => {
                        prune_succeeded = true;
                        break;
                    }
                    Err(e) => {
                        debug!(name, error = %e, attempt, "Worktree prune failed");
                    }
                }
            }

            // Ensure libgit2 doesn't keep the worktree alive on platforms with strict file locking.
            drop(worktree);

            // Always try to remove the directory even if prune failed (may take time for processes
            // to release handles, especially on Windows).
            if let Err(e) = remove_dir_all_with_retries(&wt_path) {
                warn!(name, path = ?wt_path, error = %e, "Failed to remove worktree directory");
                return Err(e);
            }

            // Verify the worktree is actually gone from git's perspective
            if !prune_succeeded {
                // Check if it's still registered with git
                if self.repo.find_worktree(&worktree_name).is_ok() {
                    warn!(name, "Worktree still exists in git after prune attempts");
                    return Err(anyhow::anyhow!(
                        "Failed to remove worktree '{name}' - it may still be in use"
                    ));
                }
            }

            debug!(name, "Worktree pruned");
        }

        // Always try to delete the branch (critical for cleanup)
        // Ignore errors - branch may already be deleted or checked out elsewhere
        let branch_mgr = super::BranchManager::new(self.repo);
        let _ = branch_mgr.delete(name);

        info!(name, "Worktree removed");
        Ok(())
    }

    /// List all worktrees
    ///
    /// # Errors
    ///
    /// Returns an error if worktrees cannot be listed
    pub fn list(&self) -> Result<Vec<Info>> {
        let worktrees = self.repo.worktrees().context("Failed to list worktrees")?;

        let mut infos = Vec::new();
        for name in worktrees.iter().flatten() {
            if let Ok(wt) = self.repo.find_worktree(name) {
                let is_locked = matches!(wt.is_locked(), Ok(git2::WorktreeLockStatus::Locked(_)));
                infos.push(Info {
                    name: name.to_string(),
                    path: wt.path().to_path_buf(),
                    is_locked,
                });
            }
        }

        Ok(infos)
    }

    /// Check if a worktree exists
    #[must_use]
    pub fn exists(&self, name: &str) -> bool {
        let worktree_name = name.replace('/', "-");
        self.repo.find_worktree(&worktree_name).is_ok()
    }

    /// Lock a worktree to prevent it from being pruned
    ///
    /// # Errors
    ///
    /// Returns an error if the worktree cannot be locked
    pub fn lock(&self, name: &str, reason: Option<&str>) -> Result<()> {
        let worktree_name = name.replace('/', "-");
        let worktree = self
            .repo
            .find_worktree(&worktree_name)
            .with_context(|| format!("Worktree not found: {name}"))?;

        worktree
            .lock(reason)
            .with_context(|| format!("Failed to lock worktree '{name}'"))?;

        Ok(())
    }

    /// Unlock a worktree
    ///
    /// # Errors
    ///
    /// Returns an error if the worktree cannot be unlocked
    pub fn unlock(&self, name: &str) -> Result<()> {
        let worktree_name = name.replace('/', "-");
        let worktree = self
            .repo
            .find_worktree(&worktree_name)
            .with_context(|| format!("Worktree not found: {name}"))?;

        let is_locked = matches!(
            worktree.is_locked(),
            Ok(git2::WorktreeLockStatus::Locked(_))
        );
        if !is_locked {
            bail!("Worktree '{name}' is not locked");
        }

        worktree
            .unlock()
            .with_context(|| format!("Failed to unlock worktree '{name}'"))?;

        Ok(())
    }

    /// Validate a worktree (check if it's valid)
    ///
    /// # Errors
    ///
    /// Returns an error if the worktree is invalid
    pub fn validate(&self, name: &str) -> Result<()> {
        let worktree_name = name.replace('/', "-");
        let worktree = self
            .repo
            .find_worktree(&worktree_name)
            .with_context(|| format!("Worktree not found: {name}"))?;

        worktree
            .validate()
            .with_context(|| format!("Worktree '{name}' is invalid"))?;

        Ok(())
    }

    /// Get the HEAD commit information for the main repository
    ///
    /// Returns (`branch_name`, `short_commit_hash`)
    ///
    /// # Errors
    ///
    /// Returns an error if HEAD cannot be read
    pub fn head_info(&self) -> Result<(String, String)> {
        let head = self.repo.head().context("Failed to get HEAD")?;
        let commit = head.peel_to_commit().context("Failed to get HEAD commit")?;

        let branch_name = if head.is_branch() {
            head.shorthand()
                .map_or_else(|| "HEAD".to_string(), String::from)
        } else {
            "HEAD (detached)".to_string()
        };

        let short_hash = commit.id().to_string()[..7].to_string();

        Ok((branch_name, short_hash))
    }

    /// Get the HEAD commit information for an existing worktree
    ///
    /// Returns (`branch_name`, `short_commit_hash`) if the worktree exists and has a valid HEAD
    ///
    /// # Errors
    ///
    /// Returns an error if the worktree or its HEAD cannot be read
    pub fn worktree_head_info(&self, name: &str) -> Result<(String, String)> {
        let worktree_name = name.replace('/', "-");
        let worktree = self
            .repo
            .find_worktree(&worktree_name)
            .with_context(|| format!("Worktree not found: {name}"))?;

        // Open the worktree as a repository to get its HEAD
        let wt_repo = git2::Repository::open(worktree.path()).with_context(|| {
            format!(
                "Failed to open worktree repository at {}",
                worktree.path().display()
            )
        })?;

        let head = wt_repo.head().context("Failed to get worktree HEAD")?;
        let commit = head
            .peel_to_commit()
            .context("Failed to get worktree HEAD commit")?;

        let branch_name = if head.is_branch() {
            head.shorthand()
                .map_or_else(|| "HEAD".to_string(), String::from)
        } else {
            "HEAD (detached)".to_string()
        };

        let short_hash = commit.id().to_string()[..7].to_string();

        Ok((branch_name, short_hash))
    }
}

/// Information about a worktree
#[derive(Debug, Clone)]
pub struct Info {
    /// Name of the worktree (usually branch name)
    pub name: String,
    /// Path to the worktree directory
    pub path: PathBuf,
    /// Whether the worktree is locked
    pub is_locked: bool,
}

#[cfg(test)]
mod tests {
    use super::*;
    use git2::Signature;
    use tempfile::TempDir;

    fn init_test_repo_with_commit() -> Result<(TempDir, Repository), Box<dyn std::error::Error>> {
        let temp_dir = TempDir::new()?;
        let repo = Repository::init(temp_dir.path())?;

        let sig = Signature::now("Test", "test@test.com")?;
        let file_path = temp_dir.path().join("README.md");
        fs::write(&file_path, "# Test")?;

        let mut index = repo.index()?;
        index.add_path(Path::new("README.md"))?;
        index.write()?;

        let tree_id = index.write_tree()?;

        {
            let tree = repo.find_tree(tree_id)?;
            repo.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[])?;
        }

        Ok((temp_dir, repo))
    }

    #[test]
    fn test_create_with_new_branch() -> Result<(), Box<dyn std::error::Error>> {
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let wt_path = temp_dir.path().join("worktrees").join("feature-test");
        manager.create_with_new_branch(&wt_path, "feature-test")?;

        assert!(wt_path.exists());
        assert!(manager.exists("feature-test"));
        Ok(())
    }

    #[test]
    fn test_create_existing_branch() -> Result<(), Box<dyn std::error::Error>> {
        let (temp_dir, repo) = init_test_repo_with_commit()?;

        let head = repo.head()?;
        let commit = head.peel_to_commit()?;
        repo.branch("existing-branch", &commit, false)?;

        let manager = Manager::new(&repo);
        let wt_path = temp_dir.path().join("worktrees").join("existing");
        manager.create(&wt_path, "existing-branch")?;

        assert!(wt_path.exists());
        Ok(())
    }

    #[test]
    fn test_create_nonexistent_branch() -> Result<(), Box<dyn std::error::Error>> {
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let wt_path = temp_dir.path().join("worktrees").join("test");
        let result = manager.create(&wt_path, "nonexistent");
        assert!(result.is_err());
        Ok(())
    }

    #[test]
    fn test_remove_worktree() -> Result<(), Box<dyn std::error::Error>> {
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let wt_path = temp_dir.path().join("worktrees").join("feature-remove");
        manager.create_with_new_branch(&wt_path, "feature-remove-test")?;
        assert!(manager.exists("feature-remove-test"));

        manager.remove("feature-remove-test")?;
        assert!(!manager.exists("feature-remove-test"));
        assert!(!wt_path.exists());
        Ok(())
    }

    #[test]
    fn test_remove_nonexistent() -> Result<(), Box<dyn std::error::Error>> {
        // Removing a non-existent worktree/branch should succeed (idempotent cleanup)
        let (_temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let result = manager.remove("nonexistent");
        assert!(result.is_ok());
        Ok(())
    }

    #[test]
    fn test_remove_with_missing_worktree_but_existing_branch()
    -> Result<(), Box<dyn std::error::Error>> {
        // When worktree is manually removed but branch exists, cleanup should still delete branch
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let wt_path = temp_dir.path().join("worktrees").join("orphan-branch");
        manager.create_with_new_branch(&wt_path, "orphan-branch-test")?;

        // Verify branch exists
        assert!(
            repo.find_branch("orphan-branch-test", git2::BranchType::Local)
                .is_ok()
        );

        // Manually remove the worktree directory (simulating manual cleanup)
        fs::remove_dir_all(&wt_path)?;

        // Prune the worktree reference so git doesn't track it
        let worktree_name = "orphan-branch-test";
        if let Ok(wt) = repo.find_worktree(worktree_name) {
            let _ = wt.prune(Some(
                git2::WorktreePruneOptions::new()
                    .valid(true)
                    .working_tree(true),
            ));
        }

        // Now remove should still clean up the branch
        manager.remove("orphan-branch-test")?;

        // Branch should be deleted
        assert!(
            repo.find_branch("orphan-branch-test", git2::BranchType::Local)
                .is_err()
        );
        Ok(())
    }

    #[test]
    fn test_list_worktrees() -> Result<(), Box<dyn std::error::Error>> {
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let wt_path = temp_dir.path().join("worktrees").join("feature-list");
        manager.create_with_new_branch(&wt_path, "feature-list-test")?;

        let worktrees = manager.list()?;
        assert!(worktrees.iter().any(|wt| wt.name == "feature-list-test"));
        Ok(())
    }

    #[test]
    fn test_exists() -> Result<(), Box<dyn std::error::Error>> {
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        assert!(!manager.exists("nonexistent"));

        let wt_path = temp_dir.path().join("worktrees").join("feature-exists");
        manager.create_with_new_branch(&wt_path, "feature-exists-test")?;

        assert!(manager.exists("feature-exists-test"));
        Ok(())
    }

    #[test]
    fn test_lock_and_unlock() -> Result<(), Box<dyn std::error::Error>> {
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let wt_path = temp_dir.path().join("worktrees").join("feature-lock");
        manager.create_with_new_branch(&wt_path, "feature-lock-test")?;

        manager.lock("feature-lock-test", Some("Testing lock"))?;

        let worktrees = manager.list()?;
        let locked_wt = worktrees
            .iter()
            .find(|wt| wt.name == "feature-lock-test")
            .ok_or("Expected worktree")?;
        assert!(locked_wt.is_locked);

        manager.unlock("feature-lock-test")?;

        let worktrees = manager.list()?;
        let unlocked_wt = worktrees
            .iter()
            .find(|wt| wt.name == "feature-lock-test")
            .ok_or("Expected worktree")?;
        assert!(!unlocked_wt.is_locked);
        Ok(())
    }

    #[test]
    fn test_unlock_not_locked() -> Result<(), Box<dyn std::error::Error>> {
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let wt_path = temp_dir.path().join("worktrees").join("feature-unlock");
        manager.create_with_new_branch(&wt_path, "feature-unlock-test")?;

        let result = manager.unlock("feature-unlock-test");
        assert!(result.is_err());
        Ok(())
    }

    #[test]
    fn test_validate() -> Result<(), Box<dyn std::error::Error>> {
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let wt_path = temp_dir.path().join("worktrees").join("feature-validate");
        manager.create_with_new_branch(&wt_path, "feature-validate-test")?;

        manager.validate("feature-validate-test")?;
        Ok(())
    }

    #[test]
    fn test_branch_name_with_slashes() -> Result<(), Box<dyn std::error::Error>> {
        // Integration test: branch names with slashes (like "tenex/feature-name")
        // should work correctly. The worktree name internally replaces slashes with dashes.
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        // Use a branch name with a slash (like tenex generates)
        let branch_name = "tenex/my-feature";
        let wt_path = temp_dir
            .path()
            .join("worktrees")
            .join("tenex")
            .join("my-feature");

        // Create worktree with slashed branch name
        manager.create_with_new_branch(&wt_path, branch_name)?;

        // Verify worktree directory exists
        assert!(wt_path.exists());

        // Verify worktree can be found using original branch name
        assert!(manager.exists(branch_name));

        // Verify the worktree is a valid git worktree (has .git file)
        assert!(wt_path.join(".git").exists());

        // Verify the branch was created in the repository
        assert!(
            repo.find_branch(branch_name, git2::BranchType::Local)
                .is_ok()
        );

        // Verify we can validate the worktree using the branch name
        manager.validate(branch_name)?;

        // Verify we can remove the worktree using the branch name
        manager.remove(branch_name)?;
        assert!(!manager.exists(branch_name));
        assert!(!wt_path.exists());
        Ok(())
    }

    #[test]
    fn test_worktree_info() {
        let info = Info {
            name: "test".to_string(),
            path: PathBuf::from("/tmp/test"),
            is_locked: false,
        };

        assert_eq!(info.name, "test");
        assert_eq!(info.path, PathBuf::from("/tmp/test"));
        assert!(!info.is_locked);
    }

    #[test]
    fn test_create_with_new_branch_cleanup_existing() -> Result<(), Box<dyn std::error::Error>> {
        // Test that create_with_new_branch cleans up existing worktree/branch
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let wt_path = temp_dir.path().join("worktrees").join("feature-recreate");

        // Create initial worktree
        manager.create_with_new_branch(&wt_path, "feature-recreate-test")?;
        assert!(wt_path.exists());
        assert!(manager.exists("feature-recreate-test"));

        // Create a file in the worktree to verify it's a new worktree after recreation
        let marker_file = wt_path.join("marker.txt");
        fs::write(&marker_file, "original")?;
        assert!(marker_file.exists());

        // Remove the worktree first (simulate user cleanup without branch deletion)
        manager.remove("feature-recreate-test")?;

        // Re-create with the same name - should clean up existing branch and succeed
        manager.create_with_new_branch(&wt_path, "feature-recreate-test")?;

        assert!(wt_path.exists());
        assert!(manager.exists("feature-recreate-test"));
        // The marker file should not exist (fresh worktree)
        assert!(!marker_file.exists());

        Ok(())
    }

    #[test]
    fn test_create_with_new_branch_recreate_with_existing_worktree()
    -> Result<(), Box<dyn std::error::Error>> {
        // Test that create_with_new_branch cleans up when worktree still exists
        // (exercises the cleanup path at lines 85-94)
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let wt_path = temp_dir.path().join("worktrees").join("feature-reuse");

        // Create initial worktree
        manager.create_with_new_branch(&wt_path, "feature-reuse-test")?;
        assert!(wt_path.exists());
        assert!(manager.exists("feature-reuse-test"));

        // Create a marker file
        let marker_file = wt_path.join("marker.txt");
        fs::write(&marker_file, "original")?;

        // DO NOT remove the worktree - call create_with_new_branch again
        // This should prune the existing worktree and recreate it
        manager.create_with_new_branch(&wt_path, "feature-reuse-test")?;

        assert!(wt_path.exists());
        assert!(manager.exists("feature-reuse-test"));
        // The marker file should not exist (fresh worktree)
        assert!(!marker_file.exists());

        Ok(())
    }

    #[test]
    fn test_head_info() -> Result<(), Box<dyn std::error::Error>> {
        let (_temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let (branch, commit) = manager.head_info()?;

        // Should be on master/main branch
        assert!(branch == "master" || branch == "main");
        // Commit hash should be 7 characters
        assert_eq!(commit.len(), 7);
        Ok(())
    }

    #[test]
    fn test_worktree_head_info() -> Result<(), Box<dyn std::error::Error>> {
        let (temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        // Create a worktree first
        let wt_path = temp_dir.path().join("worktrees").join("feature-info");
        manager.create_with_new_branch(&wt_path, "feature-info-test")?;

        let (branch, commit) = manager.worktree_head_info("feature-info-test")?;

        // Should be on the feature branch
        assert_eq!(branch, "feature-info-test");
        // Commit hash should be 7 characters
        assert_eq!(commit.len(), 7);
        Ok(())
    }

    #[test]
    fn test_worktree_head_info_not_found() -> Result<(), Box<dyn std::error::Error>> {
        let (_temp_dir, repo) = init_test_repo_with_commit()?;
        let manager = Manager::new(&repo);

        let result = manager.worktree_head_info("nonexistent-worktree");
        assert!(result.is_err());
        Ok(())
    }
}
