use std::collections::HashMap;
use std::path::PathBuf;
use std::process::Command;
use std::sync::Arc;

use tokio::sync::RwLock;
use tracing::{debug, error, info, warn};
use uuid::Uuid;

use crate::error::{Result, WorktreeError};
use crate::handle::WorktreeHandle;

/// Manages git worktree lifecycle for code-modifying agents
pub struct WorktreeManager {
    /// Path to the root repository
    repo_path: PathBuf,
    /// Base directory for creating worktrees
    worktree_base: PathBuf,
    /// Active worktrees indexed by ID
    active_worktrees: Arc<RwLock<HashMap<String, WorktreeHandle>>>,
}

impl WorktreeManager {
    /// Create a new worktree manager for a repository
    pub fn new(repo_path: PathBuf, worktree_base: PathBuf) -> Result<Self> {
        // Verify repository exists
        if !repo_path.exists() {
            return Err(WorktreeError::InvalidState(format!(
                "Repository path does not exist: {}",
                repo_path.display()
            )));
        }

        // Create worktree base directory if needed
        if !worktree_base.exists() {
            std::fs::create_dir_all(&worktree_base).map_err(|e| {
                WorktreeError::InvalidState(format!(
                    "Failed to create worktree base directory: {}",
                    e
                ))
            })?;
        }

        Ok(Self {
            repo_path,
            worktree_base,
            active_worktrees: Arc::new(RwLock::new(HashMap::new())),
        })
    }

    /// Create a new worktree for an agent
    pub async fn create_for_agent(&self, agent_id: &str) -> Result<WorktreeHandle> {
        let worktree_id = Uuid::new_v4().to_string();
        let branch_name = format!("agent/{}/{}", agent_id, worktree_id);
        let worktree_path = self.worktree_base.join(&worktree_id);

        debug!(
            "Creating worktree for agent {}: {}",
            agent_id,
            worktree_path.display()
        );

        // Create worktree with new branch
        let output = Command::new("git")
            .current_dir(&self.repo_path)
            .args([
                "worktree",
                "add",
                "-b",
                &branch_name,
                worktree_path.to_str().ok_or_else(|| {
                    WorktreeError::InvalidState("Invalid path encoding".to_string())
                })?,
            ])
            .output()
            .map_err(|e| WorktreeError::GitError(format!("Failed to create worktree: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(WorktreeError::CreationFailed(stderr.to_string()));
        }

        let handle = WorktreeHandle::new(agent_id.to_string(), branch_name, worktree_path);
        let handle_id = handle.id.clone();

        // Track in active worktrees
        let mut worktrees = self.active_worktrees.write().await;
        worktrees.insert(handle_id.clone(), handle.clone());

        info!(
            "Created worktree {} for agent {} on branch {}",
            handle_id, agent_id, handle.branch
        );

        Ok(handle)
    }

    /// Prepare worktree for merge: check for conflicts before attempting merge
    pub async fn prepare_merge(&self, worktree: &WorktreeHandle) -> Result<()> {
        worktree.can_modify()?;

        debug!("Preparing merge for worktree {}", worktree.id);

        // Fetch latest main branch
        let output = Command::new("git")
            .current_dir(&self.repo_path)
            .args(["fetch", "origin", "main:main"])
            .output()
            .map_err(|e| WorktreeError::GitError(format!("Failed to fetch: {}", e)))?;

        if !output.status.success() {
            warn!("Fetch returned non-zero status, continuing with merge check");
        }

        // Dry-run merge to detect conflicts
        let output = Command::new("git")
            .current_dir(&self.repo_path)
            .args([
                "merge",
                "--no-commit",
                "--no-ff",
                "--no-stat",
                &worktree.branch,
            ])
            .output()
            .map_err(|e| WorktreeError::GitError(format!("Failed to check merge: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            // Abort the dry-run merge
            let _ = Command::new("git")
                .current_dir(&self.repo_path)
                .args(["merge", "--abort"])
                .output();

            return Err(WorktreeError::MergeConflict(format!(
                "Merge would conflict: {}",
                stderr
            )));
        }

        // Abort dry-run merge
        let output = Command::new("git")
            .current_dir(&self.repo_path)
            .args(["merge", "--abort"])
            .output()
            .map_err(|e| WorktreeError::GitError(format!("Failed to abort dry-run: {}", e)))?;

        if !output.status.success() {
            warn!("Merge abort returned non-zero status");
        }

        info!("Merge preparation successful for worktree {}", worktree.id);
        Ok(())
    }

    /// Merge worktree changes back to main and remove worktree
    pub async fn merge_and_cleanup(&self, worktree: &WorktreeHandle) -> Result<()> {
        // Prepare merge (dry-run checks)
        self.prepare_merge(worktree).await?;

        debug!("Merging and cleaning up worktree {}", worktree.id);

        // Perform actual merge
        let output = Command::new("git")
            .current_dir(&self.repo_path)
            .args(["merge", "--no-edit", &worktree.branch])
            .output()
            .map_err(|e| WorktreeError::GitError(format!("Failed to merge: {}", e)))?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            error!("Merge failed: {}", stderr);
            return Err(WorktreeError::GitError(format!("Merge failed: {}", stderr)));
        }

        // Remove the worktree
        let output = Command::new("git")
            .current_dir(&self.repo_path)
            .args(["worktree", "remove", worktree.path.to_str().unwrap()])
            .output()
            .map_err(|e| {
                WorktreeError::RemovalFailed(format!("Failed to remove worktree: {}", e))
            })?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            error!("Worktree removal failed: {}", stderr);
            // Continue cleanup even if removal fails
        }

        // Remove branch
        let _ = Command::new("git")
            .current_dir(&self.repo_path)
            .args(["branch", "-D", &worktree.branch])
            .output();

        // Remove from tracking
        let mut worktrees = self.active_worktrees.write().await;
        worktrees.remove(&worktree.id);

        info!(
            "Merged and cleaned up worktree {} from branch {}",
            worktree.id, worktree.branch
        );

        Ok(())
    }

    /// Force cleanup of a worktree without merging (used for failed tasks)
    pub async fn cleanup_without_merge(&self, worktree: &WorktreeHandle) -> Result<()> {
        debug!("Cleaning up worktree {} without merge", worktree.id);

        // Remove the worktree forcefully
        let output = Command::new("git")
            .current_dir(&self.repo_path)
            .args(["worktree", "remove", "-f", worktree.path.to_str().unwrap()])
            .output()
            .map_err(|e| {
                WorktreeError::RemovalFailed(format!("Failed to remove worktree: {}", e))
            })?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            warn!("Worktree force removal had issues: {}", stderr);
        }

        // Remove branch forcefully
        let _ = Command::new("git")
            .current_dir(&self.repo_path)
            .args(["branch", "-D", "-f", &worktree.branch])
            .output();

        // Remove from tracking
        let mut worktrees = self.active_worktrees.write().await;
        worktrees.remove(&worktree.id);

        info!(
            "Force cleaned up worktree {} from branch {}",
            worktree.id, worktree.branch
        );

        Ok(())
    }

    /// Get an active worktree by ID
    pub async fn get_worktree(&self, id: &str) -> Result<Option<WorktreeHandle>> {
        let worktrees = self.active_worktrees.read().await;
        Ok(worktrees.get(id).cloned())
    }

    /// List all active worktrees
    pub async fn list_active(&self) -> Result<Vec<WorktreeHandle>> {
        let worktrees = self.active_worktrees.read().await;
        Ok(worktrees.values().cloned().collect())
    }

    /// Get count of active worktrees for an agent
    pub async fn count_for_agent(&self, agent_id: &str) -> Result<usize> {
        let worktrees = self.active_worktrees.read().await;
        Ok(worktrees
            .values()
            .filter(|w| w.agent_id == agent_id)
            .count())
    }

    /// Cleanup all orphaned worktrees (for startup recovery)
    pub async fn cleanup_orphaned(&self) -> Result<()> {
        debug!("Cleaning up orphaned worktrees");

        let output = Command::new("git")
            .current_dir(&self.repo_path)
            .args(["worktree", "list"])
            .output()
            .map_err(|e| WorktreeError::GitError(format!("Failed to list worktrees: {}", e)))?;

        let stdout = String::from_utf8_lossy(&output.stdout);

        for line in stdout.lines() {
            // Skip main worktree and empty lines
            if line.contains("(bare)") || line.contains("(detached)") || line.is_empty() {
                continue;
            }

            // Extract path from output like "/path/to/wt-abc detached"
            if let Some(path_str) = line.split_whitespace().next() {
                let path = PathBuf::from(path_str);
                if path.starts_with(&self.worktree_base) {
                    warn!("Removing orphaned worktree: {}", path.display());

                    // Remove forcefully
                    let _ = Command::new("git")
                        .current_dir(&self.repo_path)
                        .args(["worktree", "remove", "-f", path_str])
                        .output();
                }
            }
        }

        info!("Cleaned up orphaned worktrees");
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use tempfile::TempDir;

    use super::*;

    #[tokio::test]
    async fn test_manager_creation() -> Result<()> {
        let repo_dir = TempDir::new().map_err(WorktreeError::IoError)?;
        let wt_dir = TempDir::new().map_err(WorktreeError::IoError)?;

        // Initialize git repo
        Command::new("git")
            .current_dir(repo_dir.path())
            .args(["init"])
            .output()
            .map_err(|e| WorktreeError::GitError(e.to_string()))?;

        let manager =
            WorktreeManager::new(repo_dir.path().to_path_buf(), wt_dir.path().to_path_buf())?;

        assert!(manager.list_active().await?.is_empty());
        Ok(())
    }

    #[tokio::test]
    async fn test_worktree_creation() -> Result<()> {
        let repo_dir = TempDir::new().map_err(WorktreeError::IoError)?;
        let wt_dir = TempDir::new().map_err(WorktreeError::IoError)?;

        // Initialize git repo
        Command::new("git")
            .current_dir(repo_dir.path())
            .args(["init"])
            .output()
            .map_err(|e| WorktreeError::GitError(e.to_string()))?;

        let manager =
            WorktreeManager::new(repo_dir.path().to_path_buf(), wt_dir.path().to_path_buf())?;

        let handle = manager.create_for_agent("agent-001").await?;

        assert_eq!(handle.agent_id, "agent-001");
        assert!(handle.is_active);
        assert_eq!(manager.list_active().await?.len(), 1);
        assert_eq!(manager.count_for_agent("agent-001").await?, 1);

        Ok(())
    }
}
