//! Nushell plugin for orchestrator operations.
//!
//! This plugin provides orchestrator commands for the provisioning platform:
//! - `orch status` - Get orchestrator status from local files
//! - `orch tasks` - List tasks from local queue
//! - `orch validate` - Validate KCL workflow locally
//! - `orch submit` - Submit workflow to queue
//! - `orch monitor` - Monitor task progress

use nu_plugin::{
    serve_plugin, EngineInterface, EvaluatedCall, MsgPackSerializer, Plugin, PluginCommand,
    SimplePluginCommand,
};
use nu_protocol::{record, Category, Example, LabeledError, Signature, SyntaxShape, Type, Value};

pub mod error;
mod helpers;

#[cfg(test)]
mod tests;

/// Nushell plugin for orchestrator operations.
#[derive(Debug)]
pub struct OrchestratorPlugin;

impl Plugin for OrchestratorPlugin {
    fn version(&self) -> String {
        env!("CARGO_PKG_VERSION").into()
    }

    fn commands(&self) -> Vec<Box<dyn PluginCommand<Plugin = Self>>> {
        vec![
            Box::new(OrchStatus),
            Box::new(OrchValidate),
            Box::new(OrchTasks),
            Box::new(OrchSubmit),
            Box::new(OrchMonitor),
        ]
    }
}

// =============================================================================
// Status Command
// =============================================================================

/// Orchestrator status command (reads local state, no HTTP).
#[derive(Debug)]
pub struct OrchStatus;

impl SimplePluginCommand for OrchStatus {
    type Plugin = OrchestratorPlugin;

    fn name(&self) -> &str {
        "orch status"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(Type::Nothing, Type::Record(vec![].into()))
            .named(
                "data-dir",
                SyntaxShape::String,
                "Orchestrator data directory",
                Some('d'),
            )
            .category(Category::Custom("provisioning".into()))
    }

    fn description(&self) -> &str {
        "Get orchestrator status from local state (no HTTP)"
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: "orch status",
                description: "Check orchestrator status from local files",
                result: None,
            },
            Example {
                example: "orch status --data-dir ./data",
                description: "Check status from custom data directory",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &OrchestratorPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let data_dir = if let Some(dir) = call.get_flag::<String>("data-dir")? {
            std::path::PathBuf::from(dir)
        } else {
            helpers::get_orchestrator_data_dir()
        };

        let status = helpers::read_local_status(&data_dir)
            .map_err(|e| LabeledError::new(format!("Failed to read status: {}", e)))?;

        let is_running = helpers::is_orchestrator_running();

        Ok(Value::record(
            record! {
                "running" => Value::bool(is_running, call.head),
                "tasks_pending" => Value::int(status.tasks_pending as i64, call.head),
                "tasks_running" => Value::int(status.tasks_running as i64, call.head),
                "tasks_completed" => Value::int(status.tasks_completed as i64, call.head),
                "last_check" => Value::string(&status.last_check, call.head),
                "data_dir" => Value::string(data_dir.to_string_lossy(), call.head),
            },
            call.head,
        ))
    }
}

// =============================================================================
// Validate Command
// =============================================================================

/// Validate workflow command (KCL validation, no HTTP).
#[derive(Debug)]
pub struct OrchValidate;

impl SimplePluginCommand for OrchValidate {
    type Plugin = OrchestratorPlugin;

    fn name(&self) -> &str {
        "orch validate"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(Type::String, Type::Record(vec![].into()))
            .required("workflow", SyntaxShape::Filepath, "Workflow KCL file")
            .switch("strict", "Strict validation mode", Some('s'))
            .named(
                "data-dir",
                SyntaxShape::String,
                "Orchestrator data directory",
                Some('d'),
            )
            .category(Category::Custom("provisioning".into()))
    }

    fn description(&self) -> &str {
        "Validate workflow KCL file locally (no HTTP)"
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: "orch validate workflow.k",
                description: "Validate workflow configuration",
                result: None,
            },
            Example {
                example: "orch validate workflow.k --strict",
                description: "Strict validation with all checks",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &OrchestratorPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let workflow: String = call.req(0)?;
        let strict = call.has_flag("strict")?;

        let result = helpers::validate_kcl_workflow(&workflow, strict)
            .map_err(|e| LabeledError::new(format!("Validation failed: {}", e)))?;

        Ok(Value::record(
            record! {
                "valid" => Value::bool(result.valid, call.head),
                "errors" => Value::list(
                    result.errors.iter()
                        .map(|e| Value::string(e, call.head))
                        .collect(),
                    call.head
                ),
                "warnings" => Value::list(
                    result.warnings.iter()
                        .map(|w| Value::string(w, call.head))
                        .collect(),
                    call.head
                ),
            },
            call.head,
        ))
    }
}

// =============================================================================
// Tasks Command
// =============================================================================

/// List tasks command (reads local task queue).
#[derive(Debug)]
pub struct OrchTasks;

impl SimplePluginCommand for OrchTasks {
    type Plugin = OrchestratorPlugin;

    fn name(&self) -> &str {
        "orch tasks"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(
                Type::Nothing,
                Type::List(Box::new(Type::Record(vec![].into()))),
            )
            .named("status", SyntaxShape::String, "Filter by status", Some('s'))
            .named("limit", SyntaxShape::Int, "Limit results", Some('l'))
            .named(
                "data-dir",
                SyntaxShape::String,
                "Orchestrator data directory",
                Some('d'),
            )
            .category(Category::Custom("provisioning".into()))
    }

    fn description(&self) -> &str {
        "List orchestrator tasks from local queue (no HTTP)"
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: "orch tasks",
                description: "List all tasks",
                result: None,
            },
            Example {
                example: "orch tasks --status pending --limit 10",
                description: "List 10 pending tasks",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &OrchestratorPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let data_dir = if let Some(dir) = call.get_flag::<String>("data-dir")? {
            std::path::PathBuf::from(dir)
        } else {
            helpers::get_orchestrator_data_dir()
        };
        let status_filter = call.get_flag::<String>("status")?;
        let limit = call.get_flag::<i64>("limit")?;

        let tasks = helpers::read_task_queue(&data_dir, status_filter, limit)
            .map_err(|e| LabeledError::new(format!("Failed to read tasks: {}", e)))?;

        let task_values: Vec<Value> = tasks
            .iter()
            .map(|task| {
                Value::record(
                    record! {
                        "id" => Value::string(&task.id, call.head),
                        "status" => Value::string(&task.status, call.head),
                        "priority" => Value::int(task.priority as i64, call.head),
                        "created_at" => Value::string(&task.created_at, call.head),
                        "workflow_id" => task.workflow_id.as_ref()
                            .map(|w| Value::string(w, call.head))
                            .unwrap_or(Value::nothing(call.head)),
                    },
                    call.head,
                )
            })
            .collect();

        Ok(Value::list(task_values, call.head))
    }
}

// =============================================================================
// Submit Command
// =============================================================================

/// Submit workflow command.
#[derive(Debug)]
pub struct OrchSubmit;

impl SimplePluginCommand for OrchSubmit {
    type Plugin = OrchestratorPlugin;

    fn name(&self) -> &str {
        "orch submit"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(Type::Nothing, Type::Record(vec![].into()))
            .required("workflow", SyntaxShape::Filepath, "Workflow KCL file to submit")
            .switch("check", "Dry-run mode (validate but don't submit)", Some('c'))
            .named(
                "priority",
                SyntaxShape::Int,
                "Task priority (0-100, default 50)",
                Some('p'),
            )
            .named(
                "data-dir",
                SyntaxShape::String,
                "Orchestrator data directory",
                Some('d'),
            )
            .category(Category::Custom("provisioning".into()))
    }

    fn description(&self) -> &str {
        "Submit workflow to orchestrator queue"
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: "orch submit workflow.k",
                description: "Submit workflow for execution",
                result: None,
            },
            Example {
                example: "orch submit workflow.k --check",
                description: "Validate workflow without submitting",
                result: None,
            },
            Example {
                example: "orch submit workflow.k --priority 80",
                description: "Submit with high priority",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &OrchestratorPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let workflow: String = call.req(0)?;
        let check_only = call.has_flag("check")?;
        let priority = call.get_flag::<i64>("priority")?.unwrap_or(50) as u8;
        let data_dir = if let Some(dir) = call.get_flag::<String>("data-dir")? {
            std::path::PathBuf::from(dir)
        } else {
            helpers::get_orchestrator_data_dir()
        };

        // Validate workflow first
        let validation = helpers::validate_kcl_workflow(&workflow, true)
            .map_err(|e| LabeledError::new(format!("Validation failed: {}", e)))?;

        if !validation.valid {
            return Ok(Value::record(
                record! {
                    "success" => Value::bool(false, call.head),
                    "submitted" => Value::bool(false, call.head),
                    "errors" => Value::list(
                        validation.errors.iter()
                            .map(|e| Value::string(e, call.head))
                            .collect(),
                        call.head
                    ),
                },
                call.head,
            ));
        }

        if check_only {
            return Ok(Value::record(
                record! {
                    "success" => Value::bool(true, call.head),
                    "submitted" => Value::bool(false, call.head),
                    "message" => Value::string("Workflow is valid (dry-run mode)", call.head),
                },
                call.head,
            ));
        }

        // Submit the workflow
        let task_id = helpers::submit_workflow(&data_dir, &workflow, priority)
            .map_err(|e| LabeledError::new(format!("Failed to submit workflow: {}", e)))?;

        Ok(Value::record(
            record! {
                "success" => Value::bool(true, call.head),
                "submitted" => Value::bool(true, call.head),
                "task_id" => Value::string(&task_id, call.head),
                "workflow" => Value::string(&workflow, call.head),
                "priority" => Value::int(priority as i64, call.head),
            },
            call.head,
        ))
    }
}

// =============================================================================
// Monitor Command
// =============================================================================

/// Monitor task progress command.
#[derive(Debug)]
pub struct OrchMonitor;

impl SimplePluginCommand for OrchMonitor {
    type Plugin = OrchestratorPlugin;

    fn name(&self) -> &str {
        "orch monitor"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(Type::Nothing, Type::Record(vec![].into()))
            .required("task_id", SyntaxShape::String, "Task ID to monitor")
            .named(
                "interval",
                SyntaxShape::Int,
                "Polling interval in milliseconds (default 1000)",
                Some('i'),
            )
            .named(
                "timeout",
                SyntaxShape::Int,
                "Timeout in seconds (default 300)",
                Some('t'),
            )
            .named(
                "data-dir",
                SyntaxShape::String,
                "Orchestrator data directory",
                Some('d'),
            )
            .switch("once", "Check once and return (no polling)", Some('1'))
            .category(Category::Custom("provisioning".into()))
    }

    fn description(&self) -> &str {
        "Monitor task progress in real-time"
    }

    fn examples(&self) -> Vec<Example<'_>> {
        vec![
            Example {
                example: "orch monitor task-123",
                description: "Monitor task until completion",
                result: None,
            },
            Example {
                example: "orch monitor task-123 --once",
                description: "Check task status once",
                result: None,
            },
            Example {
                example: "orch monitor task-123 --interval 500 --timeout 60",
                description: "Monitor with 500ms interval and 60s timeout",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &OrchestratorPlugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let task_id: String = call.req(0)?;
        let once = call.has_flag("once")?;
        let interval_ms = call.get_flag::<i64>("interval")?.unwrap_or(1000);
        let timeout_secs = call.get_flag::<i64>("timeout")?.unwrap_or(300);
        let data_dir = if let Some(dir) = call.get_flag::<String>("data-dir")? {
            std::path::PathBuf::from(dir)
        } else {
            helpers::get_orchestrator_data_dir()
        };

        if once {
            // Single check mode
            let task = helpers::get_task_by_id(&data_dir, &task_id)
                .map_err(|e| LabeledError::new(format!("Failed to get task: {}", e)))?;

            return Ok(Value::record(
                record! {
                    "id" => Value::string(&task.id, call.head),
                    "status" => Value::string(&task.status, call.head),
                    "priority" => Value::int(task.priority as i64, call.head),
                    "created_at" => Value::string(&task.created_at, call.head),
                    "workflow_id" => task.workflow_id.as_ref()
                        .map(|w| Value::string(w, call.head))
                        .unwrap_or(Value::nothing(call.head)),
                },
                call.head,
            ));
        }

        // Polling mode
        let result = helpers::monitor_task(&data_dir, &task_id, interval_ms as u64, timeout_secs as u64)
            .map_err(|e| LabeledError::new(format!("Monitor failed: {}", e)))?;

        Ok(Value::record(
            record! {
                "id" => Value::string(&result.id, call.head),
                "status" => Value::string(&result.status, call.head),
                "completed" => Value::bool(result.status == "completed" || result.status == "failed", call.head),
                "success" => Value::bool(result.status == "completed", call.head),
                "duration_ms" => Value::int(result.duration_ms as i64, call.head),
                "message" => result.message.as_ref()
                    .map(|m| Value::string(m, call.head))
                    .unwrap_or(Value::nothing(call.head)),
            },
            call.head,
        ))
    }
}

/// Entry point for the plugin binary.
fn main() {
    serve_plugin(&OrchestratorPlugin, MsgPackSerializer);
}
