use std::sync::Arc;
use std::time::Instant;

use dashmap::DashMap;
use tracing::{debug, info, warn};

use crate::error::{Result, SwarmError};
use crate::messages::*;
use crate::metrics::SwarmMetrics;

/// Swarm coordinator manages agent negotiation and task assignment
pub struct SwarmCoordinator {
    agents: Arc<DashMap<String, AgentProfile>>,
    active_bids: Arc<DashMap<String, Vec<Bid>>>,
    coalitions: Arc<DashMap<String, Coalition>>,
    active_assignments: Arc<DashMap<String, String>>,
    nats_client: Option<Arc<async_nats::Client>>,
    metrics: Option<Arc<SwarmMetrics>>,
}

impl SwarmCoordinator {
    /// Create a new swarm coordinator
    pub fn new() -> Self {
        Self {
            agents: Arc::new(DashMap::new()),
            active_bids: Arc::new(DashMap::new()),
            coalitions: Arc::new(DashMap::new()),
            active_assignments: Arc::new(DashMap::new()),
            nats_client: None,
            metrics: None,
        }
    }

    /// Create a swarm coordinator with metrics enabled
    pub fn with_metrics(metrics: Arc<SwarmMetrics>) -> Result<Self> {
        Ok(Self {
            agents: Arc::new(DashMap::new()),
            active_bids: Arc::new(DashMap::new()),
            coalitions: Arc::new(DashMap::new()),
            active_assignments: Arc::new(DashMap::new()),
            nats_client: None,
            metrics: Some(metrics),
        })
    }

    /// Create a swarm coordinator with NATS binding (Phase 5.2)
    pub fn with_nats(nats_client: Arc<async_nats::Client>) -> Self {
        let mut coordinator = Self::new();
        coordinator.nats_client = Some(nats_client);
        coordinator
    }

    /// Add metrics to an existing coordinator
    pub fn set_metrics(&mut self, metrics: Arc<SwarmMetrics>) {
        self.metrics = Some(metrics);
    }

    /// Register an agent in the swarm
    pub fn register_agent(&self, profile: AgentProfile) -> Result<()> {
        debug!("Registering agent: {}", profile.id);
        self.agents.insert(profile.id.clone(), profile);
        Ok(())
    }

    /// Unregister an agent from the swarm
    pub fn unregister_agent(&self, agent_id: &str) -> Result<()> {
        self.agents.remove(agent_id);
        debug!("Unregistered agent: {}", agent_id);
        Ok(())
    }

    /// Submit a task to the swarm for bidding
    pub async fn submit_task_for_bidding(
        &self,
        task_id: String,
        _task_description: String,
        required_capabilities: Vec<String>,
    ) -> Result<Option<String>> {
        let start_time = Instant::now();
        debug!("Task {} submitted for bidding", task_id);

        // Find agents with matching capabilities
        let candidates: Vec<_> = self
            .agents
            .iter()
            .filter(|entry| {
                let agent = entry.value();
                agent.availability
                    && agent.current_load < 0.9
                    && required_capabilities
                        .iter()
                        .any(|cap| agent.capabilities.contains(cap))
            })
            .map(|entry| entry.value().clone())
            .collect();

        if candidates.is_empty() {
            warn!("No suitable agents found for task {}", task_id);
            if let Some(metrics) = &self.metrics {
                metrics.record_assignment_failure();
            }
            return Ok(None);
        }

        // Automatic assignment to best agent (highest success rate + lowest load)
        let best_agent = candidates
            .iter()
            .max_by(|a, b| {
                let a_score = a.success_rate / (1.0 + a.current_load);
                let b_score = b.success_rate / (1.0 + b.current_load);
                a_score
                    .partial_cmp(&b_score)
                    .unwrap_or(std::cmp::Ordering::Equal)
            })
            .ok_or_else(|| {
                if let Some(metrics) = &self.metrics {
                    metrics.record_assignment_failure();
                }
                SwarmError::AssignmentError("No best agent found".to_string())
            })?;

        // Assign task
        self.active_assignments
            .insert(task_id.clone(), best_agent.id.clone());

        info!(
            "Task {} assigned to agent {} (success_rate: {:.2}%, load: {:.2}%)",
            task_id,
            best_agent.id,
            best_agent.success_rate * 100.0,
            best_agent.current_load * 100.0
        );

        // Record assignment latency
        let latency_secs = start_time.elapsed().as_secs_f64();
        let complexity = if required_capabilities.len() > 2 {
            "complex"
        } else {
            "simple"
        };
        if let Some(metrics) = &self.metrics {
            metrics.record_assignment_success(latency_secs, complexity);
        }

        // Phase 5.2: Publish assignment event to NATS
        if let Some(nats) = &self.nats_client {
            let msg = SwarmMessage::TaskAssignment {
                task_id: task_id.clone(),
                assigned_to: best_agent.id.clone(),
                priority: 50,
            };
            if let Ok(payload) = serde_json::to_vec(&msg) {
                nats.publish("vapora.swarm.assignments", payload.into())
                    .await
                    .ok();
            }
        }

        Ok(Some(best_agent.id.clone()))
    }

    /// Create a coalition of agents for complex tasks
    pub async fn create_coalition(
        &self,
        coordinator_id: String,
        required_roles: Vec<String>,
    ) -> Result<Coalition> {
        let mut coalition = Coalition::new(coordinator_id, required_roles.clone());

        // Find agents for each required role
        for required_role in &required_roles {
            if let Some(agent) = self.find_agent_by_role(required_role) {
                coalition.members.push(agent.id);
            } else {
                warn!("No agent found for role: {}", required_role);
            }
        }

        if coalition.members.len() < required_roles.len() / 2 {
            return Err(SwarmError::CoalitionError(
                "Could not recruit enough agents for coalition".to_string(),
            ));
        }

        coalition.status = CoalitionStatus::Active;
        let coalition_id = coalition.id.clone();

        self.coalitions.insert(coalition_id, coalition.clone());

        // Record coalition formation
        if let Some(metrics) = &self.metrics {
            metrics.record_coalition_formed();
        }

        // Phase 5.2: Publish coalition event to NATS
        if let Some(nats) = &self.nats_client {
            let msg = SwarmMessage::CoalitionInvite {
                coalition_id: coalition.id.clone(),
                coordinator_id: coalition.coordinator_id.clone(),
                required_roles: coalition.required_roles.clone(),
            };
            if let Ok(payload) = serde_json::to_vec(&msg) {
                nats.publish("vapora.swarm.coalitions", payload.into())
                    .await
                    .ok();
            }
        }

        info!(
            "Coalition {} created with {} members",
            coalition.id,
            coalition.members.len()
        );

        Ok(coalition)
    }

    /// Get coalition by ID
    pub fn get_coalition(&self, coalition_id: &str) -> Result<Coalition> {
        self.coalitions
            .get(coalition_id)
            .map(|entry| entry.value().clone())
            .ok_or_else(|| {
                SwarmError::CoalitionError(format!("Coalition not found: {}", coalition_id))
            })
    }

    /// Update agent status/load
    pub fn update_agent_status(
        &self,
        agent_id: &str,
        current_load: f64,
        available: bool,
    ) -> Result<()> {
        if let Some(mut agent) = self.agents.get_mut(agent_id) {
            agent.current_load = current_load;
            agent.availability = available;
            debug!(
                "Updated agent {} status: load={:.2}%, available={}",
                agent_id,
                current_load * 100.0,
                available
            );
        }
        Ok(())
    }

    /// Execute consensus vote on a decision
    pub async fn consensus_vote(
        &self,
        proposal_id: &str,
        votes: Vec<(String, Vote)>,
    ) -> Result<bool> {
        let start_time = Instant::now();
        let agree_count = votes.iter().filter(|(_, v)| *v == Vote::Agree).count();
        let disagree_count = votes.iter().filter(|(_, v)| *v == Vote::Disagree).count();
        let total = votes.len();

        let consensus_reached = agree_count > total / 2;

        // Record vote metrics
        let latency_secs = start_time.elapsed().as_secs_f64();
        if let Some(metrics) = &self.metrics {
            metrics.record_vote(latency_secs, total, consensus_reached);
        }

        info!(
            "Consensus vote on {}: {} agree, {} disagree ({})",
            proposal_id,
            agree_count,
            disagree_count,
            if consensus_reached {
                "PASSED"
            } else {
                "FAILED"
            }
        );

        Ok(consensus_reached)
    }

    /// Get swarm statistics
    pub fn get_swarm_stats(&self) -> SwarmStats {
        let total_agents = self.agents.len();
        let available_agents = self
            .agents
            .iter()
            .filter(|entry| entry.value().availability)
            .count();
        let avg_load: f64 = self
            .agents
            .iter()
            .map(|entry| entry.value().current_load)
            .sum::<f64>()
            / self.agents.len().max(1) as f64;
        let active_tasks = self.active_assignments.len();
        let active_coalitions = self.coalitions.len();

        // Update metrics with current stats
        if let Some(metrics) = &self.metrics {
            metrics.update_agent_metrics(total_agents as u32, available_agents as u32, avg_load);
        }

        SwarmStats {
            total_agents: total_agents as u32,
            available_agents: available_agents as u32,
            avg_load,
            active_tasks: active_tasks as u32,
            active_coalitions: active_coalitions as u32,
        }
    }

    /// Find best agent for a role
    fn find_agent_by_role(&self, role: &str) -> Option<AgentProfile> {
        self.agents
            .iter()
            .filter(|entry| entry.value().roles.contains(&role.to_string()))
            .max_by(|a, b| {
                let a_score = a.value().success_rate / (1.0 + a.value().current_load);
                let b_score = b.value().success_rate / (1.0 + b.value().current_load);
                a_score
                    .partial_cmp(&b_score)
                    .unwrap_or(std::cmp::Ordering::Equal)
            })
            .map(|entry| entry.value().clone())
    }

    /// Get all bids for a task (for monitoring and API endpoints)
    pub fn get_bids_for_task(&self, task_id: &str) -> Option<Vec<Bid>> {
        self.active_bids
            .get(task_id)
            .map(|entry| entry.value().clone())
    }
}

/// Swarm statistics
#[derive(Debug, Clone)]
pub struct SwarmStats {
    pub total_agents: u32,
    pub available_agents: u32,
    pub avg_load: f64,
    pub active_tasks: u32,
    pub active_coalitions: u32,
}

impl Default for SwarmCoordinator {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_coordinator_creation() {
        let coord = SwarmCoordinator::new();
        let stats = coord.get_swarm_stats();
        assert_eq!(stats.total_agents, 0);
    }

    #[test]
    fn test_register_agent() {
        let coord = SwarmCoordinator::new();
        let profile = AgentProfile {
            id: "agent-1".to_string(),
            roles: vec!["developer".to_string()],
            capabilities: vec!["coding".to_string()],
            current_load: 0.5,
            success_rate: 0.9,
            availability: true,
        };

        assert!(coord.register_agent(profile).is_ok());
        assert_eq!(coord.get_swarm_stats().total_agents, 1);
    }

    #[tokio::test]
    async fn test_coalition_creation() {
        let coord = SwarmCoordinator::new();

        let profile = AgentProfile {
            id: "agent-1".to_string(),
            roles: vec!["developer".to_string()],
            capabilities: vec!["coding".to_string()],
            current_load: 0.3,
            success_rate: 0.95,
            availability: true,
        };

        coord.register_agent(profile).ok();

        let coal = coord
            .create_coalition("agent-1".to_string(), vec!["developer".to_string()])
            .await;

        assert!(coal.is_ok());
    }
}
