//! Test Orchestrator
//!
//! Coordinates test environment creation, execution, and cleanup.
//! Integrates container management with test execution workflows.

use std::{collections::HashMap, sync::Arc};

use anyhow::{anyhow, Result};
use tokio::sync::RwLock;
use tracing::{error, info};

use crate::{
    container_manager::ContainerManager,
    test_environment::{
        ClusterTopologyConfig, CreateTestEnvironmentRequest, NetworkConfig, RunTestRequest,
        ServerSimulationConfig, SingleTaskservConfig, TestEnvironment, TestEnvironmentConfig,
        TestEnvironmentResponse, TestEnvironmentStatus, TestResult,
    },
};

/// Test orchestrator manages test environments
pub struct TestOrchestrator {
    /// Container manager
    container_manager: Arc<ContainerManager>,
    /// Active test environments
    environments: Arc<RwLock<HashMap<String, TestEnvironment>>>,
}

impl TestOrchestrator {
    /// Create new test orchestrator
    pub fn new() -> Result<Self> {
        let container_manager = Arc::new(ContainerManager::new()?);

        Ok(Self {
            container_manager,
            environments: Arc::new(RwLock::new(HashMap::new())),
        })
    }

    /// Create test environment
    pub async fn create_environment(
        &self,
        request: CreateTestEnvironmentRequest,
    ) -> Result<TestEnvironmentResponse> {
        let mut environment = TestEnvironment::new(request.config.clone(), request.infra.clone());

        info!(
            "Creating test environment {} ({:?})",
            environment.id, environment.env_type
        );

        // Provision based on environment type
        match &request.config {
            TestEnvironmentConfig::SingleTaskserv(config) => {
                self.provision_single_taskserv(&mut environment, config)
                    .await?;
            }
            TestEnvironmentConfig::ServerSimulation(config) => {
                self.provision_server_simulation(&mut environment, config)
                    .await?;
            }
            TestEnvironmentConfig::ClusterTopology(config) => {
                self.provision_cluster_topology(&mut environment, config)
                    .await?;
            }
        }

        let environment_id = environment.id.clone();

        // Store environment
        {
            let mut envs = self.environments.write().await;
            envs.insert(environment_id.clone(), environment);
        }

        // Auto-start if requested
        if request.auto_start {
            // Run tests in background
            let env_id = environment_id.clone();
            let orchestrator = self.clone();
            tokio::spawn(async move {
                if let Err(e) = orchestrator
                    .run_tests(
                        &env_id,
                        RunTestRequest {
                            tests: vec![],
                            timeout_seconds: None,
                        },
                    )
                    .await
                {
                    error!("Failed to run tests for {}: {}", env_id, e);
                }

                // Auto-cleanup if requested
                if request.auto_cleanup {
                    if let Err(e) = orchestrator.cleanup_environment(&env_id, false).await {
                        error!("Failed to cleanup {}: {}", env_id, e);
                    }
                }
            });
        }

        Ok(TestEnvironmentResponse {
            success: true,
            environment_id: Some(environment_id),
            message: "Test environment created successfully".to_string(),
        })
    }

    /// Provision single taskserv test
    async fn provision_single_taskserv(
        &self,
        environment: &mut TestEnvironment,
        config: &SingleTaskservConfig,
    ) -> Result<()> {
        environment.add_log(format!("Provisioning single taskserv: {}", config.taskserv));

        // Create network
        let network_config = NetworkConfig::default();
        let network_id = self
            .container_manager
            .create_network(&network_config)
            .await?;
        environment.network_id = Some(network_id.clone());

        // Create container
        let container_name = format!("test-{}-{}", config.taskserv, &environment.id[..8]);
        let container = self
            .container_manager
            .create_container(
                &container_name,
                &config.base_image,
                Some(&network_id),
                &config.resources,
                &config.environment,
                Some(vec!["sleep".to_string(), "infinity".to_string()]),
            )
            .await?;

        // Start container
        self.container_manager
            .start_container(&container.container_id)
            .await?;

        // Get container IP
        let ip = self
            .container_manager
            .get_container_ip(&container.container_id, &network_id)
            .await?;

        let mut container_instance = container;
        container_instance.ip_address = ip;
        container_instance.status = "running".to_string();

        environment.containers.push(container_instance);
        environment.update_status(TestEnvironmentStatus::Ready);

        environment.add_log("Single taskserv environment ready".to_string());

        Ok(())
    }

    /// Provision server simulation
    async fn provision_server_simulation(
        &self,
        environment: &mut TestEnvironment,
        config: &ServerSimulationConfig,
    ) -> Result<()> {
        environment.add_log(format!(
            "Provisioning server simulation: {}",
            config.server_name
        ));

        // Create network
        let network_config = NetworkConfig::default();
        let network_id = self
            .container_manager
            .create_network(&network_config)
            .await?;
        environment.network_id = Some(network_id.clone());

        // Create single container simulating server
        let container_name = format!("test-srv-{}-{}", config.server_name, &environment.id[..8]);

        // Merge environment variables with taskservs info
        let mut env_vars = config.environment.clone();
        env_vars.insert("TASKSERVS".to_string(), config.taskservs.join(","));

        let container = self
            .container_manager
            .create_container(
                &container_name,
                &config.base_image,
                Some(&network_id),
                &config.resources,
                &env_vars,
                Some(vec!["sleep".to_string(), "infinity".to_string()]),
            )
            .await?;

        // Start container
        self.container_manager
            .start_container(&container.container_id)
            .await?;

        // Get container IP
        let ip = self
            .container_manager
            .get_container_ip(&container.container_id, &network_id)
            .await?;

        let mut container_instance = container;
        container_instance.ip_address = ip;
        container_instance.status = "running".to_string();

        environment.containers.push(container_instance);
        environment.update_status(TestEnvironmentStatus::Ready);

        environment.add_log(format!(
            "Server simulation ready with {} taskservs",
            config.taskservs.len()
        ));

        Ok(())
    }

    /// Provision cluster topology
    async fn provision_cluster_topology(
        &self,
        environment: &mut TestEnvironment,
        config: &ClusterTopologyConfig,
    ) -> Result<()> {
        environment.add_log(format!(
            "Provisioning cluster topology: {}",
            config.cluster_type
        ));

        // Create network
        let network_id = self
            .container_manager
            .create_network(&config.network)
            .await?;
        environment.network_id = Some(network_id.clone());

        // Create containers for each node
        for node_config in &config.nodes {
            environment.add_log(format!(
                "Creating node: {} ({})",
                node_config.name, node_config.role
            ));

            let container_name = format!(
                "test-{}-{}-{}",
                config.cluster_type,
                node_config.name,
                &environment.id[..8]
            );

            // Merge environment variables
            let mut env_vars = node_config.environment.clone();
            env_vars.insert("NODE_ROLE".to_string(), node_config.role.clone());
            env_vars.insert("NODE_NAME".to_string(), node_config.name.clone());
            env_vars.insert("TASKSERVS".to_string(), node_config.taskservs.join(","));

            // Use ubuntu:22.04 as default base image for clusters
            let base_image = "ubuntu:22.04";

            let container = self
                .container_manager
                .create_container(
                    &container_name,
                    base_image,
                    Some(&network_id),
                    &node_config.resources,
                    &env_vars,
                    Some(vec!["sleep".to_string(), "infinity".to_string()]),
                )
                .await?;

            // Start container
            self.container_manager
                .start_container(&container.container_id)
                .await?;

            // Get container IP
            let ip = self
                .container_manager
                .get_container_ip(&container.container_id, &network_id)
                .await?;

            let mut container_instance = container;
            container_instance.node_name = Some(node_config.name.clone());
            container_instance.ip_address = ip;
            container_instance.status = "running".to_string();

            environment.containers.push(container_instance);
        }

        environment.update_status(TestEnvironmentStatus::Ready);
        environment.add_log(format!(
            "Cluster topology ready with {} nodes",
            config.nodes.len()
        ));

        Ok(())
    }

    /// Run tests in environment
    pub async fn run_tests(
        &self,
        environment_id: &str,
        _request: RunTestRequest,
    ) -> Result<Vec<TestResult>> {
        let mut environment = {
            let envs = self.environments.read().await;
            envs.get(environment_id)
                .ok_or_else(|| anyhow!("Environment {} not found", environment_id))?
                .clone()
        };

        environment.update_status(TestEnvironmentStatus::Running);
        environment.add_log("Starting tests".to_string());

        let mut test_results = vec![];

        // Run basic connectivity test
        let start = std::time::Instant::now();
        for container in &environment.containers {
            let test_name = format!("connectivity_{}", container.container_name);

            match self
                .container_manager
                .exec_in_container(
                    &container.container_id,
                    vec!["echo".to_string(), "test".to_string()],
                )
                .await
            {
                Ok(output) => {
                    test_results.push(TestResult {
                        name: test_name,
                        passed: output.contains("test"),
                        output,
                        error: None,
                        duration_ms: start.elapsed().as_millis() as u64,
                    });
                }
                Err(e) => {
                    test_results.push(TestResult {
                        name: test_name,
                        passed: false,
                        output: String::new(),
                        error: Some(e.to_string()),
                        duration_ms: start.elapsed().as_millis() as u64,
                    });
                }
            }
        }

        // Update environment with results
        {
            let mut envs = self.environments.write().await;
            if let Some(env) = envs.get_mut(environment_id) {
                for result in &test_results {
                    env.add_test_result(result.clone());
                }
                env.update_status(TestEnvironmentStatus::Completed);
                env.add_log(format!(
                    "Tests completed: {}/{} passed",
                    test_results.iter().filter(|r| r.passed).count(),
                    test_results.len()
                ));
            }
        }

        Ok(test_results)
    }

    /// Get environment status
    pub async fn get_environment(&self, environment_id: &str) -> Option<TestEnvironment> {
        let envs = self.environments.read().await;
        envs.get(environment_id).cloned()
    }

    /// List all environments
    pub async fn list_environments(&self) -> Vec<TestEnvironment> {
        let envs = self.environments.read().await;
        envs.values().cloned().collect()
    }

    /// Cleanup environment
    pub async fn cleanup_environment(&self, environment_id: &str, force: bool) -> Result<()> {
        let mut environment = {
            let envs = self.environments.read().await;
            envs.get(environment_id)
                .ok_or_else(|| anyhow!("Environment {} not found", environment_id))?
                .clone()
        };

        self.container_manager
            .cleanup_environment(&mut environment, force)
            .await?;

        // Update environment
        {
            let mut envs = self.environments.write().await;
            envs.insert(environment_id.to_string(), environment);
        }

        Ok(())
    }

    /// Get container logs
    pub async fn get_environment_logs(&self, environment_id: &str) -> Result<Vec<String>> {
        let environment = self
            .get_environment(environment_id)
            .await
            .ok_or_else(|| anyhow!("Environment {} not found", environment_id))?;

        Ok(environment.logs)
    }
}

impl Clone for TestOrchestrator {
    fn clone(&self) -> Self {
        Self {
            container_manager: self.container_manager.clone(),
            environments: self.environments.clone(),
        }
    }
}

impl Default for TestOrchestrator {
    fn default() -> Self {
        Self::new().expect("Failed to create TestOrchestrator")
    }
}
