use std::sync::Arc;

use chrono::Utc;
use dashmap::DashMap;
use futures::StreamExt;
use serde_json::Value;
use tracing::{debug, error, info, warn};
use vapora_agents::messages::{AgentMessage, TaskCompleted, TaskFailed};
use vapora_knowledge_graph::persistence::KGPersistence;
use vapora_swarm::coordinator::SwarmCoordinator;

use crate::artifact::{Artifact, ArtifactType};
use crate::config::{StageConfig, WorkflowsConfig};
use crate::error::{Result, WorkflowError};
use crate::instance::{WorkflowInstance, WorkflowStatus};
use crate::metrics::WorkflowMetrics;
use crate::stage::{StageStatus, TaskState};

pub struct WorkflowOrchestrator {
    config: WorkflowsConfig,
    swarm: Arc<SwarmCoordinator>,
    #[allow(dead_code)]
    kg: Arc<KGPersistence>,
    nats: Arc<async_nats::Client>,
    active_workflows: DashMap<String, WorkflowInstance>,
    metrics: Arc<WorkflowMetrics>,
}

impl WorkflowOrchestrator {
    pub async fn new(
        config_path: &str,
        swarm: Arc<SwarmCoordinator>,
        kg: Arc<KGPersistence>,
        nats: Arc<async_nats::Client>,
    ) -> Result<Self> {
        let config = WorkflowsConfig::load(config_path)?;
        let metrics =
            Arc::new(WorkflowMetrics::new().map_err(|e| {
                WorkflowError::Internal(format!("Failed to create metrics: {}", e))
            })?);

        Ok(Self {
            config,
            swarm,
            kg,
            nats,
            active_workflows: DashMap::new(),
            metrics,
        })
    }

    pub fn metrics(&self) -> Arc<WorkflowMetrics> {
        self.metrics.clone()
    }

    pub async fn start_workflow(
        &self,
        workflow_name: &str,
        initial_context: Value,
    ) -> Result<String> {
        let template = self
            .config
            .get_workflow(workflow_name)
            .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_name.to_string()))?;

        let instance = WorkflowInstance::new(template, initial_context);
        let workflow_id = instance.id.clone();

        self.active_workflows.insert(workflow_id.clone(), instance);
        self.metrics.active_workflows.inc();

        self.execute_current_stage(&workflow_id).await?;

        info!(workflow_id = %workflow_id, "Workflow started: {}", workflow_name);
        self.metrics.workflows_started.inc();

        Ok(workflow_id)
    }

    async fn execute_current_stage(&self, workflow_id: &str) -> Result<()> {
        let (stage_config, approval_required, context) = {
            let mut instance = self
                .active_workflows
                .get_mut(workflow_id)
                .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.to_string()))?;

            let stage_config = instance
                .current_stage()
                .ok_or(WorkflowError::NoCurrentStage)?
                .config
                .clone();

            let stage_name = stage_config.name.clone();

            if stage_config.approval_required && !instance.is_waiting_approval() {
                instance.status = WorkflowStatus::WaitingApproval(instance.current_stage_idx);
                drop(instance);
                return self
                    .publish_approval_request(workflow_id, &stage_name)
                    .await;
            }

            let context = instance.build_stage_context();

            (
                stage_config.clone(),
                stage_config.approval_required,
                context,
            )
        };

        if approval_required {
            return Ok(());
        }

        if stage_config.parallel {
            self.assign_parallel_tasks(workflow_id, &stage_config, context)
                .await?;
        } else {
            self.assign_sequential_task(workflow_id, &stage_config, context)
                .await?;
        }

        {
            let mut instance = self
                .active_workflows
                .get_mut(workflow_id)
                .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.to_string()))?;

            if let Some(stage) = instance.current_stage_mut() {
                stage.transition(StageStatus::Running)?;
                stage.mark_started();
            }
        }

        Ok(())
    }

    async fn assign_parallel_tasks(
        &self,
        workflow_id: &str,
        stage_config: &StageConfig,
        _context: Value,
    ) -> Result<()> {
        let max_parallel = stage_config
            .max_parallel
            .unwrap_or(stage_config.agents.len());

        for (i, agent_role) in stage_config.agents.iter().take(max_parallel).enumerate() {
            let task_description = format!("{} - task {}", stage_config.name, i);

            let assigned_agent = self
                .swarm
                .submit_task_for_bidding(
                    format!("wf-{}-stage-{}-{}", workflow_id, stage_config.name, i),
                    task_description.clone(),
                    vec![agent_role.clone()],
                )
                .await
                .map_err(|e| WorkflowError::SwarmError(e.to_string()))?;

            if let Some(agent_id) = assigned_agent {
                let task_id = format!("wf-{}-stage-{}-{}", workflow_id, stage_config.name, i);

                let mut instance = self
                    .active_workflows
                    .get_mut(workflow_id)
                    .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.to_string()))?;

                if let Some(stage) = instance.current_stage_mut() {
                    stage.assigned_tasks.insert(
                        task_id.clone(),
                        TaskState::new(task_id.clone(), agent_id.clone(), agent_role.clone()),
                    );
                }

                debug!(
                    workflow_id = %workflow_id,
                    stage = %stage_config.name,
                    task_id = %task_id,
                    agent_id = %agent_id,
                    "Task assigned in parallel mode"
                );
            }
        }

        Ok(())
    }

    async fn assign_sequential_task(
        &self,
        workflow_id: &str,
        stage_config: &StageConfig,
        _context: Value,
    ) -> Result<()> {
        let agent_role = stage_config
            .agents
            .first()
            .ok_or(WorkflowError::NoAgentsInStage)?;

        let task_description = format!("Workflow: {} - Stage: {}", workflow_id, stage_config.name);

        let assigned_agent = self
            .swarm
            .submit_task_for_bidding(
                format!("wf-{}-stage-{}", workflow_id, stage_config.name),
                task_description,
                vec![agent_role.clone()],
            )
            .await
            .map_err(|e| WorkflowError::SwarmError(e.to_string()))?;

        if let Some(agent_id) = assigned_agent {
            let task_id = format!("wf-{}-stage-{}", workflow_id, stage_config.name);

            let mut instance = self
                .active_workflows
                .get_mut(workflow_id)
                .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.to_string()))?;

            if let Some(stage) = instance.current_stage_mut() {
                stage.assigned_tasks.insert(
                    task_id.clone(),
                    TaskState::new(task_id.clone(), agent_id.clone(), agent_role.clone()),
                );
            }

            debug!(
                workflow_id = %workflow_id,
                stage = %stage_config.name,
                task_id = %task_id,
                agent_id = %agent_id,
                "Task assigned in sequential mode"
            );
        }

        Ok(())
    }

    pub async fn on_task_completed(&self, msg: TaskCompleted) -> Result<()> {
        let workflow_id = self.find_workflow_for_task(&msg.task_id)?;

        let should_advance = {
            let mut instance = self
                .active_workflows
                .get_mut(&workflow_id)
                .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.clone()))?;

            let Some(stage) = instance.current_stage_mut() else {
                return Ok(());
            };

            let stage_name = stage.name.clone();

            if let Some(task) = stage.assigned_tasks.get_mut(&msg.task_id) {
                task.mark_completed(msg.result.clone(), msg.artifacts.clone());
            }

            info!(
                workflow_id = %workflow_id,
                stage = %stage_name,
                task_id = %msg.task_id,
                agent_id = %msg.agent_id,
                "Task completed"
            );

            let all_completed = stage.all_tasks_completed();

            for artifact_id in &msg.artifacts {
                let artifact = Artifact::new(
                    ArtifactType::Custom("agent_output".to_string()),
                    stage_name.clone(),
                    msg.agent_id.clone(),
                    artifact_id.clone(),
                );
                instance.add_artifact(artifact);
            }

            if all_completed {
                let stage = instance.current_stage_mut().expect("stage exists");

                let duration = stage
                    .started_at
                    .map(|start| (Utc::now() - start).num_seconds() as f64)
                    .unwrap_or(0.0);

                self.metrics.stage_duration_seconds.observe(duration);

                stage.transition(StageStatus::Completed)?;
                stage.mark_completed();

                self.metrics.stages_completed.inc();

                info!(
                    workflow_id = %workflow_id,
                    stage = %stage_name,
                    duration_sec = duration,
                    "Stage completed"
                );
            }

            all_completed
        };

        if should_advance {
            self.advance_to_next_stage(&workflow_id).await?;
        }

        Ok(())
    }

    async fn advance_to_next_stage(&self, workflow_id: &str) -> Result<()> {
        let should_continue = {
            let mut instance = self
                .active_workflows
                .get_mut(workflow_id)
                .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.to_string()))?;
            instance.advance_stage()?
        };

        if should_continue {
            self.execute_current_stage(workflow_id).await?;
        } else {
            let duration = {
                let instance = self
                    .active_workflows
                    .get(workflow_id)
                    .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.to_string()))?;

                (Utc::now() - instance.created_at).num_seconds() as f64
            };

            self.metrics.workflow_duration_seconds.observe(duration);
            self.metrics.workflows_completed.inc();
            self.metrics.active_workflows.dec();

            info!(
                workflow_id = %workflow_id,
                duration_sec = duration,
                "Workflow completed"
            );

            self.publish_workflow_completed(workflow_id).await?;
        }

        Ok(())
    }

    pub async fn approve_stage(&self, workflow_id: &str, approver: &str) -> Result<()> {
        {
            let mut instance = self
                .active_workflows
                .get_mut(workflow_id)
                .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.to_string()))?;

            match instance.status {
                WorkflowStatus::WaitingApproval(stage_idx)
                    if stage_idx == instance.current_stage_idx =>
                {
                    instance.status = WorkflowStatus::Running;
                    info!(
                        workflow_id = %workflow_id,
                        stage_idx = stage_idx,
                        approver = %approver,
                        "Stage approved"
                    );
                }
                _ => return Err(WorkflowError::NotWaitingApproval),
            }
        }

        self.execute_current_stage(workflow_id).await
    }

    pub async fn cancel_workflow(&self, workflow_id: &str, reason: String) -> Result<()> {
        let mut instance = self
            .active_workflows
            .get_mut(workflow_id)
            .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.to_string()))?;

        instance.cancel(reason.clone());
        self.metrics.active_workflows.dec();

        info!(
            workflow_id = %workflow_id,
            reason = %reason,
            "Workflow cancelled"
        );

        Ok(())
    }

    pub fn get_workflow(&self, workflow_id: &str) -> Result<WorkflowInstance> {
        self.active_workflows
            .get(workflow_id)
            .map(|entry| entry.value().clone())
            .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.to_string()))
    }

    pub fn list_workflows(&self) -> Vec<WorkflowInstance> {
        self.active_workflows
            .iter()
            .map(|entry| entry.value().clone())
            .collect()
    }

    fn find_workflow_for_task(&self, task_id: &str) -> Result<String> {
        for entry in self.active_workflows.iter() {
            if let Some(stage) = entry.value().current_stage() {
                if stage.assigned_tasks.contains_key(task_id) {
                    return Ok(entry.key().clone());
                }
            }
        }
        Err(WorkflowError::TaskNotFound(task_id.to_string()))
    }

    #[allow(clippy::excessive_nesting)]
    pub async fn start_event_listener(self: Arc<Self>) -> Result<()> {
        let subscriber = self
            .nats
            .subscribe("vapora.tasks.completed")
            .await
            .map_err(|e| WorkflowError::NatsError(Box::new(e)))?;

        let orchestrator = self.clone();
        tokio::spawn(async move {
            let mut subscriber = subscriber;
            while let Some(msg) = subscriber.next().await {
                if let Ok(AgentMessage::TaskCompleted(task_completed)) =
                    serde_json::from_slice::<AgentMessage>(&msg.payload)
                {
                    if let Err(e) = orchestrator.on_task_completed(task_completed).await {
                        error!(error = %e, "Failed to handle task completion");
                    }
                }
            }
        });

        let subscriber_failed = self
            .nats
            .subscribe("vapora.tasks.failed")
            .await
            .map_err(|e| WorkflowError::NatsError(Box::new(e)))?;

        let orchestrator = self.clone();
        tokio::spawn(async move {
            let mut subscriber = subscriber_failed;
            while let Some(msg) = subscriber.next().await {
                if let Ok(AgentMessage::TaskFailed(task_failed)) =
                    serde_json::from_slice::<AgentMessage>(&msg.payload)
                {
                    if let Err(e) = orchestrator.on_task_failed(task_failed).await {
                        error!(error = %e, "Failed to handle task failure");
                    }
                }
            }
        });

        info!("Workflow orchestrator event listener started");
        Ok(())
    }

    async fn on_task_failed(&self, msg: TaskFailed) -> Result<()> {
        let workflow_id = self.find_workflow_for_task(&msg.task_id)?;

        {
            let mut instance = self
                .active_workflows
                .get_mut(&workflow_id)
                .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.clone()))?;

            if let Some(stage) = instance.current_stage_mut() {
                if let Some(task) = stage.assigned_tasks.get_mut(&msg.task_id) {
                    task.mark_failed(msg.error.clone());
                }

                if msg.can_retry {
                    warn!(
                        workflow_id = %workflow_id,
                        task_id = %msg.task_id,
                        retry_count = msg.retry_count,
                        "Task failed, will retry"
                    );
                } else {
                    let stage_name = stage.name.clone();
                    stage.transition(StageStatus::Failed(msg.error.clone()))?;
                    instance.fail(format!("Stage {} failed: {}", stage_name, msg.error));

                    self.metrics.workflows_failed.inc();
                    self.metrics.active_workflows.dec();

                    error!(
                        workflow_id = %workflow_id,
                        stage = %stage_name,
                        error = %msg.error,
                        "Workflow failed"
                    );
                }
            }
        }

        Ok(())
    }

    async fn publish_approval_request(&self, workflow_id: &str, stage_name: &str) -> Result<()> {
        let event = serde_json::json!({
            "type": "approval_required",
            "workflow_id": workflow_id,
            "stage": stage_name,
            "timestamp": Utc::now().to_rfc3339(),
        });

        self.nats
            .publish(
                "vapora.workflow.approval_required",
                event.to_string().into(),
            )
            .await
            .map_err(|e| WorkflowError::NatsError(Box::new(e)))?;

        info!(
            workflow_id = %workflow_id,
            stage = %stage_name,
            "Approval request published"
        );

        Ok(())
    }

    async fn publish_workflow_completed(&self, workflow_id: &str) -> Result<()> {
        let instance = self
            .active_workflows
            .get(workflow_id)
            .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.to_string()))?;

        let event = serde_json::json!({
            "type": "workflow_completed",
            "workflow_id": workflow_id,
            "template": instance.template_name,
            "stages_completed": instance.stages.len(),
            "artifacts": instance.accumulated_artifacts.keys().collect::<Vec<_>>(),
            "timestamp": Utc::now().to_rfc3339(),
        });

        self.nats
            .publish("vapora.workflow.completed", event.to_string().into())
            .await
            .map_err(|e| WorkflowError::NatsError(Box::new(e)))?;

        Ok(())
    }

    pub fn list_templates(&self) -> Vec<String> {
        self.config
            .workflows
            .iter()
            .map(|w| w.name.clone())
            .collect()
    }

    /// Enrich initial context with Kogral knowledge (guidelines, patterns,
    /// ADRs)
    pub async fn enrich_context_from_kogral(
        &self,
        context: &mut Value,
        workflow_name: &str,
    ) -> Result<()> {
        let guidelines = self.query_kogral_guidelines(workflow_name).await?;
        let patterns = self.query_kogral_patterns(workflow_name).await?;
        let decisions = self.query_kogral_decisions(workflow_name).await?;

        if let Some(obj) = context.as_object_mut() {
            obj.insert("kogral_guidelines".to_string(), guidelines);
            obj.insert("kogral_patterns".to_string(), patterns);
            obj.insert("kogral_decisions".to_string(), decisions);
        }

        info!(
            workflow_name = %workflow_name,
            "Context enriched with Kogral knowledge"
        );

        Ok(())
    }

    async fn query_kogral_guidelines(&self, workflow_name: &str) -> Result<Value> {
        let kogral_path =
            std::env::var("KOGRAL_PATH").unwrap_or_else(|_| "../kogral/.kogral".to_string());

        let guidelines_path = format!("{}/guidelines/{}.md", kogral_path, workflow_name);

        if let Ok(content) = tokio::fs::read_to_string(&guidelines_path).await {
            Ok(serde_json::json!({
                "source": guidelines_path,
                "content": content,
            }))
        } else {
            warn!(
                workflow_name = %workflow_name,
                "No Kogral guidelines found, using empty"
            );
            Ok(serde_json::json!({
                "source": "none",
                "content": "",
            }))
        }
    }

    #[allow(clippy::excessive_nesting)]
    async fn query_kogral_patterns(&self, workflow_name: &str) -> Result<Value> {
        let kogral_path =
            std::env::var("KOGRAL_PATH").unwrap_or_else(|_| "../kogral/.kogral".to_string());

        let patterns_path = format!("{}/patterns", kogral_path);

        let mut patterns = Vec::new();

        if let Ok(mut entries) = tokio::fs::read_dir(&patterns_path).await {
            while let Ok(Some(entry)) = entries.next_entry().await {
                if let Some(ext) = entry.path().extension() {
                    if ext == "md" && entry.path().to_string_lossy().contains(workflow_name) {
                        if let Ok(content) = tokio::fs::read_to_string(entry.path()).await {
                            patterns.push(serde_json::json!({
                                "file": entry.file_name().to_string_lossy().to_string(),
                                "content": content,
                            }));
                        }
                    }
                }
            }
        }

        if patterns.is_empty() {
            warn!(
                workflow_name = %workflow_name,
                "No Kogral patterns found"
            );
        }

        Ok(serde_json::json!(patterns))
    }

    #[allow(clippy::excessive_nesting)]
    async fn query_kogral_decisions(&self, workflow_name: &str) -> Result<Value> {
        let kogral_path =
            std::env::var("KOGRAL_PATH").unwrap_or_else(|_| "../kogral/.kogral".to_string());

        let adrs_path = format!("{}/adrs", kogral_path);

        let mut adrs = Vec::new();

        if let Ok(mut entries) = tokio::fs::read_dir(&adrs_path).await {
            while let Ok(Some(entry)) = entries.next_entry().await {
                if let Some(ext) = entry.path().extension() {
                    if ext == "md" {
                        if let Ok(content) = tokio::fs::read_to_string(entry.path()).await {
                            if content.to_lowercase().contains(workflow_name)
                                || content
                                    .to_lowercase()
                                    .contains(&workflow_name.replace('_', " "))
                            {
                                adrs.push(serde_json::json!({
                                    "file": entry.file_name().to_string_lossy().to_string(),
                                    "content": content,
                                }));
                            }
                        }
                    }
                }
            }
        }

        adrs.sort_by(|a, b| {
            let a_file = a["file"].as_str().unwrap_or("");
            let b_file = b["file"].as_str().unwrap_or("");
            b_file.cmp(a_file)
        });

        let recent_adrs = adrs.into_iter().take(5).collect::<Vec<_>>();

        if recent_adrs.is_empty() {
            warn!(
                workflow_name = %workflow_name,
                "No relevant Kogral ADRs found"
            );
        } else {
            info!(
                workflow_name = %workflow_name,
                count = recent_adrs.len(),
                "Loaded recent ADRs from Kogral"
            );
        }

        Ok(serde_json::json!(recent_adrs))
    }
}

#[cfg(test)]
mod tests {
    // Orchestrator integration tests are located in tests/ directory
}
