use crate::slog;
use crate::worktree::WorktreeInfo;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use tokio::sync::broadcast;

use super::super::{AgentEvent, AgentInfo, AgentStatus, CompletionReport};

/// Build the composite key used to track agents in the pool.
pub(super) fn composite_key(story_id: &str, agent_name: &str) -> String {
    format!("{story_id}:{agent_name}")
}

/// RAII guard that removes a pending agent entry from the pool on drop.
///
/// Created after inserting a `Pending` entry into the agent HashMap.
/// If `start_agent` succeeds (the agent process is spawned and status
/// transitions to `Running`), call [`disarm`](Self::disarm) to prevent
/// cleanup.  If any intermediate step fails and the guard is dropped
/// without being disarmed, the pending entry is removed so it cannot
/// block future auto-assign dispatches.
pub(super) struct PendingGuard {
    pub(super) agents: Arc<Mutex<HashMap<String, StoryAgent>>>,
    pub(super) key: String,
    pub(super) armed: bool,
}

impl PendingGuard {
    pub(super) fn new(agents: Arc<Mutex<HashMap<String, StoryAgent>>>, key: String) -> Self {
        Self {
            agents,
            key,
            armed: true,
        }
    }

    /// Prevent the guard from cleaning up the entry (call after
    /// successful spawn).
    pub(super) fn disarm(&mut self) {
        self.armed = false;
    }
}

impl Drop for PendingGuard {
    fn drop(&mut self) {
        if self.armed
            && let Ok(mut agents) = self.agents.lock()
            && agents
                .get(&self.key)
                .is_some_and(|a| a.status == AgentStatus::Pending)
        {
            agents.remove(&self.key);
            slog!(
                "[agents] Cleaned up leaked Pending entry for '{}'",
                self.key
            );
        }
    }
}

pub(super) struct StoryAgent {
    pub(super) agent_name: String,
    pub(super) status: AgentStatus,
    pub(super) worktree_info: Option<WorktreeInfo>,
    pub(super) session_id: Option<String>,
    pub(super) tx: broadcast::Sender<AgentEvent>,
    pub(super) task_handle: Option<tokio::task::JoinHandle<()>>,
    /// Accumulated events for polling via get_agent_output.
    pub(super) event_log: Arc<Mutex<Vec<AgentEvent>>>,
    /// Set when the agent calls report_completion.
    pub(super) completion: Option<CompletionReport>,
    /// Project root, stored for pipeline advancement after completion.
    pub(super) project_root: Option<PathBuf>,
    /// UUID identifying the log file for this session.
    pub(super) log_session_id: Option<String>,
    /// Set to `true` when the agent calls `report_merge_failure`.
    /// Prevents the pipeline from blindly advancing to `5_done/` after a
    /// failed merge: the server-owned gate check runs in the feature-branch
    /// worktree (which compiles fine) and returns `gates_passed=true` even
    /// though the code was never squash-merged onto master.
    pub(super) merge_failure_reported: bool,
    /// Set to `true` when a rate-limit throttle warning was received for this agent.
    /// True when a rate-limit throttle warning was received for this agent.
    pub(super) throttled: bool,
}

/// Build an `AgentInfo` snapshot from a `StoryAgent` map entry.
pub(super) fn agent_info_from_entry(story_id: &str, agent: &StoryAgent) -> AgentInfo {
    AgentInfo {
        story_id: story_id.to_string(),
        agent_name: agent.agent_name.clone(),
        status: agent.status.clone(),
        session_id: agent.session_id.clone(),
        worktree_path: agent
            .worktree_info
            .as_ref()
            .map(|wt| wt.path.to_string_lossy().to_string()),
        base_branch: agent
            .worktree_info
            .as_ref()
            .map(|wt| wt.base_branch.clone()),
        completion: agent.completion.clone(),
        log_session_id: agent.log_session_id.clone(),
        throttled: agent.throttled,
    }
}
