use std::sync::Arc;

use chrono::Utc;
use dashmap::DashMap;
use futures::StreamExt;
use serde_json::Value;
use surrealdb::engine::remote::ws::Client;
use surrealdb::Surreal;
use tokio::sync::watch;
use tracing::{debug, error, info, warn};
use vapora_agents::messages::{AgentMessage, TaskCompleted, TaskFailed};
use vapora_channels::{ChannelRegistry, Message};
use vapora_knowledge_graph::persistence::KGPersistence;
use vapora_swarm::coordinator::SwarmCoordinator;

use crate::artifact::{Artifact, ArtifactType};
use crate::auth::CedarAuthorizer;
use crate::config::{StageConfig, WorkflowsConfig};
use crate::error::{Result, WorkflowError};
use crate::instance::{WorkflowInstance, WorkflowStatus};
use crate::metrics::WorkflowMetrics;
use crate::persistence::SurrealWorkflowStore;
use crate::saga::SagaCompensator;
use crate::schedule::ScheduledWorkflow;
use crate::schedule_store::ScheduleStore;
use crate::scheduler::WorkflowScheduler;
use crate::stage::{StageState, 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>,
    store: Arc<SurrealWorkflowStore>,
    saga: SagaCompensator,
    cedar: Option<Arc<CedarAuthorizer>>,
    /// Outbound notification registry. `None` when no channels are configured.
    channels: Option<Arc<ChannelRegistry>>,
}

impl WorkflowOrchestrator {
    pub async fn new(
        config_path: &str,
        swarm: Arc<SwarmCoordinator>,
        kg: Arc<KGPersistence>,
        nats: Arc<async_nats::Client>,
        db: Surreal<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))
            })?);

        let db = Arc::new(db);
        let store = Arc::new(SurrealWorkflowStore::new(db));
        let saga = SagaCompensator::new(swarm.clone());

        let cedar = config
            .engine
            .cedar_policy_dir
            .as_deref()
            .map(|dir| CedarAuthorizer::load_from_dir(std::path::Path::new(dir)).map(Arc::new))
            .transpose()
            .map_err(|e| WorkflowError::Internal(format!("Cedar init: {e}")))?;

        if cedar.is_some() {
            info!("Cedar authorization enabled for workflow stages");
        }

        let channels = if config.channels.is_empty() {
            None
        } else {
            let count = config.channels.len();
            let registry = ChannelRegistry::from_map(config.channels.clone())
                .map_err(|e| WorkflowError::Internal(format!("Channel registry init: {e}")))?;
            info!(count, "Notification channels registered");
            Some(Arc::new(registry))
        };

        // Crash recovery: restore active workflows from DB
        let active_workflows = DashMap::new();
        match store.load_active().await {
            Ok(instances) => {
                let count = instances.len();
                for instance in instances {
                    active_workflows.insert(instance.id.clone(), instance);
                }
                if count > 0 {
                    info!(count = count, "Restored active workflows from DB");
                    metrics.active_workflows.add(count as i64);
                }
            }
            Err(e) => {
                warn!(error = %e, "Failed to restore active workflows from DB (starting empty)");
            }
        }

        Ok(Self {
            config,
            swarm,
            kg,
            nats,
            active_workflows,
            metrics,
            store,
            saga,
            cedar,
            channels,
        })
    }

    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.store.save(&instance).await?;
        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(());
        }

        // Cedar authorization check before dispatch
        if let Some(cedar) = &self.cedar {
            let resource = format!("Stage::\"{}\"", stage_config.name);
            cedar.authorize("vapora-orchestrator", "execute-stage", &resource)?;
        }

        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();
            }
        }

        // Persist after stage state change
        if let Some(instance) = self.active_workflows.get(workflow_id) {
            self.store.save(instance.value()).await?;
        }

        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
        };

        // Persist after each state change
        if let Some(instance) = self.active_workflows.get(&workflow_id) {
            self.store.save(instance.value()).await?;
        }

        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, template_name) = {
                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,
                    instance.template_name.clone(),
                )
            };

            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?;
            self.notify_workflow_completed(workflow_id, &template_name, duration)
                .await;

            // Remove from DB — terminal state is cleaned up
            if let Err(e) = self.store.delete(workflow_id).await {
                warn!(
                    workflow_id = %workflow_id,
                    error = %e,
                    "Failed to delete completed workflow from store"
                );
            }
        }

        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"
            );
        }

        // Persist cancelled state, then clean up
        if let Some(instance) = self.active_workflows.get(workflow_id) {
            self.store.save(instance.value()).await?;
        }

        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)?;

        // `mark_current_task_failed` encapsulates the mutable stage borrow so
        // the DashMap entry can be re-accessed without nesting or borrow
        // conflicts.
        let compensation_data: Option<(Vec<StageState>, Value, String, String)> = {
            let mut instance = self
                .active_workflows
                .get_mut(&workflow_id)
                .ok_or_else(|| WorkflowError::WorkflowNotFound(workflow_id.clone()))?;

            let stage_result =
                instance.mark_current_task_failed(&msg.task_id, &msg.error, msg.can_retry);

            match stage_result {
                None => {
                    if msg.can_retry {
                        warn!(
                            workflow_id = %workflow_id,
                            task_id = %msg.task_id,
                            retry_count = msg.retry_count,
                            "Task failed, will retry"
                        );
                    }
                    None
                }
                Some(Err(e)) => return Err(e),
                Some(Ok(stage_name)) => {
                    let current_idx = instance.current_stage_idx;
                    let executed_stages = instance.stages[..current_idx].to_vec();
                    let context = instance.initial_context.clone();
                    let template_name = instance.template_name.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"
                    );

                    Some((executed_stages, context, stage_name, template_name))
                }
            }
        }; // DashMap lock released here

        if let Some((executed_stages, context, stage_name, template_name)) = compensation_data {
            // Saga compensation: dispatch rollback tasks in reverse order (best-effort)
            self.saga
                .compensate(&workflow_id, &executed_stages, &context)
                .await?;

            // Persist the failed state
            if let Some(instance) = self.active_workflows.get(&workflow_id) {
                self.store.save(instance.value()).await?;
            }

            self.notify_workflow_failed(&workflow_id, &template_name, &stage_name, &msg.error)
                .await;
        }

        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"
        );

        let template_name = self
            .active_workflows
            .get(workflow_id)
            .map(|e| e.template_name.clone())
            .unwrap_or_default();

        self.notify_approval_required(workflow_id, &template_name, stage_name)
            .await;

        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(())
    }

    /// Returns true if any non-terminal workflow instance is running for the
    /// given template name. Used by `WorkflowScheduler` to enforce
    /// `allow_concurrent = false`.
    pub fn has_active_workflow_for_template(&self, template_name: &str) -> bool {
        self.active_workflows
            .iter()
            .any(|e| e.value().template_name == template_name)
    }

    /// Build a `WorkflowScheduler` seeded from the TOML-configured scheduled
    /// workflows.
    ///
    /// TOML is the source of truth for static config; the DB owns runtime state
    /// (`last_fired_at`, `runs_count`, `next_fire_at`). The returned
    /// `watch::Sender<bool>` drives graceful shutdown: `sender.send(true)`
    /// terminates the scheduler loop.
    pub async fn build_scheduler(
        self: Arc<Self>,
        db: Arc<Surreal<Client>>,
    ) -> Result<(WorkflowScheduler, watch::Sender<bool>)> {
        let store = Arc::new(ScheduleStore::new(Arc::clone(&db)));

        for wf in self
            .config
            .workflows
            .iter()
            .filter(|w| w.trigger == "schedule")
        {
            if let Some(sc) = &wf.schedule {
                let entry = ScheduledWorkflow::from_config(&wf.name, sc);
                store.upsert(&entry).await?;
            }
        }

        let (shutdown_tx, shutdown_rx) = watch::channel(false);
        let nats = Some(Arc::clone(&self.nats));
        let metrics = Arc::clone(&self.metrics);
        let orchestrator = Arc::clone(&self);
        let scheduler = WorkflowScheduler::new(store, orchestrator, nats, metrics, shutdown_rx);

        Ok((scheduler, shutdown_tx))
    }

    /// Sends a completion notification to every channel listed in
    /// `workflow.notifications.on_completed`. Never propagates errors —
    /// a channel timeout must not abort the workflow record.
    async fn notify_workflow_completed(
        &self,
        workflow_id: &str,
        template: &str,
        duration_secs: f64,
    ) {
        let Some(registry) = &self.channels else {
            return;
        };

        let targets = self
            .config
            .get_workflow(template)
            .map(|w| w.notifications.on_completed.clone())
            .unwrap_or_default();

        if targets.is_empty() {
            return;
        }

        let msg = Message::success(
            format!("Workflow completed: {}", template),
            format!("All stages finished in {:.0}s", duration_secs),
        )
        .with_metadata("workflow_id", workflow_id)
        .with_metadata("template", template)
        .with_metadata("duration", format!("{:.0}s", duration_secs));

        for target in &targets {
            if let Err(e) = registry.send(target, msg.clone()).await {
                warn!(channel = %target, error = %e, "Completion notification failed");
            }
        }
    }

    async fn notify_workflow_failed(
        &self,
        workflow_id: &str,
        template: &str,
        stage: &str,
        error: &str,
    ) {
        let Some(registry) = &self.channels else {
            return;
        };

        let targets = self
            .config
            .get_workflow(template)
            .map(|w| w.notifications.on_failed.clone())
            .unwrap_or_default();

        if targets.is_empty() {
            return;
        }

        let msg = Message::error(
            format!("Workflow failed: {}", template),
            format!("Stage `{}` failed: {}", stage, error),
        )
        .with_metadata("workflow_id", workflow_id)
        .with_metadata("template", template)
        .with_metadata("failed_stage", stage);

        for target in &targets {
            if let Err(e) = registry.send(target, msg.clone()).await {
                warn!(channel = %target, error = %e, "Failure notification failed");
            }
        }
    }

    async fn notify_approval_required(&self, workflow_id: &str, template: &str, stage_name: &str) {
        let Some(registry) = &self.channels else {
            return;
        };

        let targets = self
            .config
            .get_workflow(template)
            .map(|w| w.notifications.on_approval_required.clone())
            .unwrap_or_default();

        if targets.is_empty() {
            return;
        }

        let msg = Message::warning(
            format!("Approval required: {}", stage_name),
            format!(
                "Workflow `{}` is waiting for human approval to proceed with stage `{}`.",
                template, stage_name
            ),
        )
        .with_metadata("workflow_id", workflow_id)
        .with_metadata("template", template)
        .with_metadata("stage", stage_name);

        for target in &targets {
            if let Err(e) = registry.send(target, msg.clone()).await {
                warn!(channel = %target, error = %e, "Approval notification failed");
            }
        }
    }

    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
}
