//! Orchestrator Application State
//!
//! Defines the shared application state for the orchestrator service.
//! This is kept separate from builder for clarity and organization.

use std::sync::Arc;

use anyhow::{Context, Result};
use dashmap::DashMap;
#[cfg(feature = "nats")]
use platform_db::SurrealPool;
#[cfg(feature = "nats")]
use platform_nats::NatsBridge;
use tracing::info;

#[cfg(feature = "testing")]
use crate::test_orchestrator::TestOrchestrator;
use crate::{
    audit::AuditLogger,
    batch::BatchCoordinator,
    dependency::DependencyResolver,
    dns::DnsManager,
    extensions::ExtensionManager,
    monitor::{MonitoringConfig, MonitoringSystem, SystemResourceHealthCheck},
    oci::OciManager,
    rollback::{providers::*, RollbackConfig, RollbackStrategy, RollbackSystem},
    services::ServiceOrchestrator,
    state::{ProgressTracker, StateConfig, WorkflowStateManager},
    storage::{create_storage_from_args, TaskStorage},
    Args, ComplianceConfig, ComplianceService,
};

pub type SharedState = Arc<AppState>;

/// Application state for the orchestrator
pub struct AppState {
    pub task_storage: Arc<dyn TaskStorage>,
    pub batch_coordinator: BatchCoordinator,
    pub dependency_resolver: DependencyResolver,
    pub state_manager: Arc<WorkflowStateManager>,
    pub monitoring_system: Arc<MonitoringSystem>,
    pub progress_tracker: Arc<ProgressTracker>,
    pub rollback_system: Arc<RollbackSystem>,
    #[cfg(feature = "testing")]
    pub test_orchestrator: Arc<TestOrchestrator>,
    #[cfg(not(feature = "testing"))]
    pub test_orchestrator: (),
    pub dns_manager: Arc<DnsManager>,
    pub extension_manager: Arc<ExtensionManager>,
    pub oci_manager: Arc<OciManager>,
    pub service_orchestrator: Arc<ServiceOrchestrator>,
    pub audit_logger: Arc<AuditLogger>,
    pub compliance_service: Arc<ComplianceService>,
    /// NATS JetStream bridge — present when compiled with the `nats` feature.
    #[cfg(feature = "nats")]
    pub nats: Arc<NatsBridge>,
    /// Shared SurrealDB connection — present when compiled with the `nats`
    /// feature.
    #[cfg(feature = "nats")]
    pub db: Arc<SurrealPool>,
    /// Ephemeral VM pool for buildkit runners (ADR-039). `None` when RUNNER_SCRIPT is absent.
    pub vm_pool: Option<Arc<crate::vm_pool::VmPool>>,
    /// Build metrics accumulator — always present, independent of vm_pool availability.
    pub build_metrics: crate::vm_pool::BuildMetrics,
    pub args: Args,
}

/// Sanitize sensitive data from command output before logging
/// Removes: API tokens, passwords, SSH keys, etc.
fn sanitize_credentials(output: &str) -> String {
    let mut result = String::new();
    let lines: Vec<&str> = output.lines().collect();

    for line in lines {
        let sanitized_line = if line.contains("HCLOUD_TOKEN=")
            || line.contains("AWS_SECRET_ACCESS_KEY=")
            || line.contains("VAULT_TOKEN=")
            || line.contains("API_KEY=")
            || line.contains("SECRET_KEY=")
            || line.contains("PASSWORD=")
            || line.contains("PASSWD=")
            || line.contains("PWD=")
        {
            // Extract the key part and redact the value
            if let Some(eq_idx) = line.find('=') {
                let key_part = &line[..eq_idx];
                format!("{}=***REDACTED***", key_part)
            } else {
                line.to_string()
            }
        } else if line.contains("Bearer ") && !line.contains("***REDACTED***") {
            line.replace(
                line.split("Bearer ")
                    .nth(1)
                    .unwrap_or("")
                    .split_whitespace()
                    .next()
                    .unwrap_or(""),
                "***REDACTED***",
            )
        } else {
            line.to_string()
        };

        result.push_str(&sanitized_line);
        result.push('\n');
    }

    result
}

impl AppState {
    pub async fn new(args: Args) -> Result<Self> {
        // Create storage using the factory pattern
        let task_storage = create_storage_from_args(&args)
            .await
            .context("Failed to create storage backend")?;

        info!(
            "Successfully initialized {} storage backend",
            args.storage_type
        );

        // Create batch coordinator
        let batch_coordinator = crate::batch::BatchCoordinatorFactory::create_from_args(
            task_storage.clone(),
            args.clone(),
        )
        .await
        .context("Failed to create batch coordinator")?;

        info!("Successfully initialized batch coordinator");

        // Create dependency resolver with default configuration
        let dependency_resolver = DependencyResolver::new();

        info!("Successfully initialized dependency resolver");

        // Create state manager with default configuration
        let state_config = StateConfig::default();
        let state_manager = Arc::new(WorkflowStateManager::new(
            task_storage.clone(),
            state_config,
        ));

        // Initialize state manager
        state_manager
            .init()
            .await
            .context("Failed to initialize state manager")?;

        info!("Successfully initialized state manager");

        // Create monitoring system with default configuration
        let monitoring_config = MonitoringConfig::default();
        let monitoring_system = Arc::new(MonitoringSystem::new(
            monitoring_config,
            task_storage.clone(),
            state_manager.clone(),
        ));

        // Initialize monitoring system
        monitoring_system
            .init()
            .await
            .context("Failed to initialize monitoring system")?;

        info!("Successfully initialized monitoring system");

        // Register system resource health check
        let health_monitor = monitoring_system.health_monitor();
        health_monitor
            .register_health_check(
                "system_resources".to_string(),
                SystemResourceHealthCheck::new(1024, 80.0),
            )
            .await;

        // Create progress tracker
        let progress_tracker = Arc::new(ProgressTracker::new(state_manager.clone()));

        info!("Successfully initialized progress tracker");

        // Create rollback system with configuration-driven strategy
        let rollback_config = RollbackConfig {
            checkpoint_interval_seconds: 300,
            auto_checkpoint_enabled: true,
            strategy: RollbackStrategy::ConfigDriven,
            ..RollbackConfig::default()
        };

        let mut rollback_system = RollbackSystem::new(task_storage.clone(), rollback_config);

        // Register provider-specific rollback handlers
        rollback_system
            .resource_tracker
            .register_provider_handler(Arc::new(UpCloudRollbackHandler {}))
            .await;

        rollback_system
            .resource_tracker
            .register_provider_handler(Arc::new(AwsRollbackHandler {}))
            .await;

        rollback_system
            .resource_tracker
            .register_provider_handler(Arc::new(LocalRollbackHandler {}))
            .await;

        // Initialize rollback system
        rollback_system
            .init()
            .await
            .context("Failed to initialize rollback system")?;

        info!(
            "Successfully initialized rollback system with {} providers",
            3
        );

        let rollback_system = Arc::new(rollback_system);

        // Create test orchestrator
        #[cfg(feature = "testing")]
        let test_orchestrator =
            Arc::new(TestOrchestrator::new().context("Failed to initialize test orchestrator")?);

        #[cfg(feature = "testing")]
        info!("Successfully initialized test orchestrator");

        #[cfg(not(feature = "testing"))]
        let test_orchestrator = ();

        // Create DNS manager
        let dns_manager = Arc::new(DnsManager::new(
            "http://localhost:53".to_string(),
            true,
            300,
        ));

        info!("Successfully initialized DNS manager");

        // Create extension manager
        let extension_manager = Arc::new(ExtensionManager::new(
            args.nu_path.clone(),
            args.provisioning_path.clone(),
        ));

        info!("Successfully initialized extension manager");

        // Create OCI manager
        let oci_manager = Arc::new(OciManager::new(
            "http://localhost:5000".to_string(),
            "provisioning-extensions".to_string(),
            std::path::PathBuf::from(&args.data_dir).join("oci-cache"),
        ));

        info!("Successfully initialized OCI manager");

        // Create service orchestrator
        let service_orchestrator = Arc::new(ServiceOrchestrator::new(
            args.nu_path.clone(),
            args.provisioning_path.clone(),
            true,
        ));

        info!("Successfully initialized service orchestrator");

        // Create audit logger with file storage
        let audit_log_path = std::path::PathBuf::from(&args.data_dir).join("audit");
        let audit_storage = Arc::new(
            crate::audit::FileStorage::new(audit_log_path, 100 * 1024 * 1024)
                .await
                .context("Failed to create audit file storage")?,
        );

        let audit_config = crate::audit::AuditLoggerConfig {
            enabled: true,
            anonymize_pii: false,
            buffer_size: 1000,
            flush_interval_secs: 5,
            retention_policy: crate::audit::RetentionPolicy {
                max_age_days: 365,
                auto_apply: false,
                archive_before_delete: true,
                archive_path: Some(
                    std::path::PathBuf::from(&args.data_dir)
                        .join("audit-archive")
                        .to_string_lossy()
                        .to_string(),
                ),
            },
            debug: false,
        };

        let audit_logger = Arc::new(
            AuditLogger::new(audit_config, audit_storage)
                .await
                .context("Failed to create audit logger")?,
        );

        info!("Successfully initialized audit logger");

        // Create compliance service
        let compliance_config = ComplianceConfig::default();
        let compliance_service = Arc::new(
            ComplianceService::new(audit_logger.clone(), compliance_config)
                .await
                .context("Failed to create compliance service")?,
        );

        info!("Successfully initialized compliance service");

        #[cfg(feature = "nats")]
        let (nats, db) = {
            use platform_db::{DbConfig, SurrealPool};
            use platform_nats::{NatsBridge, NatsConfig};

            let nats_config = NatsConfig::default();
            let bridge = NatsBridge::connect(&nats_config)
                .await
                .context("Failed to connect to NATS")?;
            let bridge = Arc::new(bridge);

            let db_config = DbConfig::EmbeddedSurrealKV {
                path: format!("{}/surrealdb", args.data_dir),
            };
            let pool = SurrealPool::connect(&db_config)
                .await
                .context("Failed to connect to SurrealDB")?;

            (bridge, Arc::new(pool))
        };

        let build_metrics = Arc::new(DashMap::new());
        let vm_pool = crate::vm_pool::VmPool::try_new(build_metrics.clone()).map(Arc::new);
        if vm_pool.is_some() {
            info!("vm_pool initialized — ephemeral buildkit runner provisioning available");
        } else {
            info!("vm_pool disabled — RUNNER_SCRIPT not set; set it to enable ephemeral build runners");
        }

        Ok(Self {
            task_storage,
            batch_coordinator,
            dependency_resolver,
            state_manager,
            monitoring_system,
            progress_tracker,
            rollback_system,
            test_orchestrator,
            dns_manager,
            extension_manager,
            oci_manager,
            service_orchestrator,
            audit_logger,
            compliance_service,
            #[cfg(feature = "nats")]
            nats,
            #[cfg(feature = "nats")]
            db,
            vm_pool,
            build_metrics,
            args,
        })
    }

    /// Publish a component lifecycle event to NATS JetStream.
    ///
    /// Subject: `provisioning.component.lifecycle`
    /// Consumed by the nu_daemon subscriber to update `.provisioning-state.ncl`.
    #[cfg(feature = "nats")]
    pub async fn publish_component_lifecycle(
        &self,
        event: &crate::ComponentLifecycleEvent,
    ) {
        use tracing::warn;
        if let Err(e) = self
            .nats
            .publish_json("provisioning.component.lifecycle", event)
            .await
        {
            warn!(
                task_id = %event.task_id,
                component = %event.component,
                "Failed to publish component lifecycle event to NATS: {}",
                e
            );
        }
    }

    /// Publish a task status update to NATS JetStream.
    ///
    /// Subject: `provisioning.tasks.{task_id}.status`
    /// Compiled out entirely when the `nats` feature is disabled.
    #[cfg(feature = "nats")]
    pub async fn publish_task_status(
        &self,
        task_id: &str,
        status: &str,
        progress: Option<u32>,
        message: Option<&str>,
    ) {
        use tracing::warn;

        let subject = format!("tasks.{}.status", task_id);
        let payload = serde_json::json!({
            "task_id": task_id,
            "status": status,
            "progress": progress,
            "message": message,
        });
        if let Err(e) = self.nats.publish_json(&subject, &payload).await {
            warn!(task_id = %task_id, "Failed to publish task status to NATS: {}", e);
        }
    }

    pub async fn execute_nushell_command(&self, command: &str, args: &[String]) -> Result<String> {
        use std::process::Stdio;

        use tokio::process::Command;

        // Determine how to execute based on command type
        let mut cmd = if command == "bash" {
            // For bash commands: args[0] is "-c", args[1] is the full command to execute
            let mut c = Command::new("bash");
            if args.len() >= 2 && args[0] == "-c" {
                c.arg("-c").arg(&args[1]);
            } else {
                // Fallback: execute bash with all args
                c.args(args);
            }
            c
        } else {
            // For Nushell commands: execute via nu -c "command args"
            let mut c = Command::new(&self.args.nu_path);
            c.arg("-c").arg(format!("{} {}", command, args.join(" ")));
            c
        };

        cmd.stdout(Stdio::piped()).stderr(Stdio::piped());

        let output = cmd.output().await.context("Failed to execute command")?;

        if output.status.success() {
            Ok(String::from_utf8_lossy(&output.stdout).to_string())
        } else {
            let stderr = String::from_utf8_lossy(&output.stderr);
            let stdout = String::from_utf8_lossy(&output.stdout);

            // Include both stdout and stderr — ssh warnings land in stderr and mask real errors
            let raw_output = match (stderr.is_empty(), stdout.is_empty()) {
                (false, false) => format!("{}\n{}", stderr, stdout),
                (false, true)  => stderr.to_string(),
                (true,  false) => stdout.to_string(),
                (true,  true)  => format!("Command failed with exit code: {:?}", output.status.code()),
            };

            // Sanitize sensitive data from logs (tokens, keys, passwords)
            let sanitized_output = sanitize_credentials(&raw_output);

            Err(anyhow::anyhow!(
                "Command execution failed: {}",
                sanitized_output
            ))
        }
    }
}
