use archipelago_container::{
    AppManifest, BitcoinSimulator, BitcoinSimulationMode, ContainerRuntime as ContainerRuntimeTrait,
    ContainerStatus, PortManager,
};
use anyhow::{Context, Result};
use std::sync::Arc;

use crate::config::{Config, ContainerRuntime, BitcoinSimulation};
use crate::container::data_manager::DevDataManager;

pub struct DevContainerOrchestrator {
    runtime: Arc<dyn ContainerRuntimeTrait>,
    port_manager: Arc<PortManager>,
    bitcoin_simulator: Arc<BitcoinSimulator>,
    data_manager: Arc<DevDataManager>,
    config: Config,
}

impl DevContainerOrchestrator {
    pub async fn new(config: Config) -> Result<Self> {
        let user = std::env::var("USER").unwrap_or_else(|_| "archipelago".to_string());

        // Create runtime based on config
        let runtime: Arc<dyn ContainerRuntimeTrait> = match &config.container_runtime {
            ContainerRuntime::Podman => {
                Arc::new(archipelago_container::PodmanRuntime::new(user.clone()))
            }
            ContainerRuntime::Docker => {
                Arc::new(archipelago_container::DockerRuntime::new(user.clone()))
            }
            ContainerRuntime::Auto => {
                Arc::new(
                    archipelago_container::AutoRuntime::new(user.clone())
                        .await
                        .context("Failed to create auto runtime")?,
                )
            }
        };

        let port_manager = Arc::new(PortManager::new(config.port_offset));
        let bitcoin_simulator = Arc::new(BitcoinSimulator::new(
            BitcoinSimulationMode::from(
                match &config.bitcoin_simulation {
                    BitcoinSimulation::Mock => "mock",
                    BitcoinSimulation::Testnet => "testnet",
                    BitcoinSimulation::Mainnet => "mainnet",
                    BitcoinSimulation::None => "none",
                }
            ),
        ));
        let data_manager = Arc::new(DevDataManager::new(config.dev_data_dir.clone()));

        Ok(Self {
            runtime,
            port_manager,
            bitcoin_simulator,
            data_manager,
            config,
        })
    }

    /// Install a container from a manifest
    pub async fn install_container(
        &self,
        manifest: &AppManifest,
        _manifest_path: &str,
    ) -> Result<String> {
        let app_id = &manifest.app.id;
        let container_name = format!("archipelago-{}-dev", app_id);

        // Check dependencies
        if self.config.dev_mode {
            // In dev mode, check if Bitcoin dependency can be satisfied
            for dep in &manifest.app.dependencies {
                if let archipelago_container::Dependency::App {
                    app_id: dep_id,
                    version: _,
                } = dep
                {
                    if dep_id == "bitcoin-core" {
                        if !self.bitcoin_simulator.is_bitcoin_available() {
                            return Err(anyhow::anyhow!(
                                "Bitcoin Core dependency not satisfied (simulation: {:?})",
                                self.bitcoin_simulator.mode()
                            ));
                        }
                    }
                }
            }
        }

        // Allocate ports
        let base_ports: Vec<u16> = manifest.app.ports.iter().map(|p| p.host).collect();
        let _dev_ports = self
            .port_manager
            .allocate_ports(app_id, &base_ports)
            .context("Failed to allocate ports")?;

        // Create app data directory
        self.data_manager
            .create_app_data_dir(app_id)
            .await
            .context("Failed to create app data directory")?;

        // Map volumes to dev paths
        let mut dev_manifest = manifest.clone();
        for volume in &mut dev_manifest.app.volumes {
            let dev_path = self.data_manager.map_volume_path(app_id, &volume.source);
            volume.source = dev_path.to_string_lossy().to_string();
        }

        // Pull image
        self.runtime
            .pull_image(
                &manifest.app.container.image,
                manifest.app.container.image_signature.as_deref(),
            )
            .await
            .context("Failed to pull image")?;

        // Create container with port offset
        let port_offset = if self.config.dev_mode {
            self.config.port_offset
        } else {
            0
        };

        self.runtime
            .create_container(&dev_manifest, &container_name, port_offset)
            .await
            .context("Failed to create container")?;

        Ok(container_name)
    }

    /// Start a container
    pub async fn start_container(&self, app_id: &str) -> Result<()> {
        let container_name = format!("archipelago-{}-dev", app_id);
        self.runtime
            .start_container(&container_name)
            .await
            .context("Failed to start container")?;
        Ok(())
    }

    /// Stop a container
    pub async fn stop_container(&self, app_id: &str) -> Result<()> {
        let container_name = format!("archipelago-{}-dev", app_id);
        self.runtime
            .stop_container(&container_name)
            .await
            .context("Failed to stop container")?;
        Ok(())
    }

    /// Remove a container
    pub async fn remove_container(&self, app_id: &str, preserve_data: bool) -> Result<()> {
        let container_name = format!("archipelago-{}-dev", app_id);

        // Stop container first
        let _ = self.runtime.stop_container(&container_name).await;

        // Remove container
        self.runtime
            .remove_container(&container_name)
            .await
            .context("Failed to remove container")?;

        // Release ports
        let _ = self.port_manager.release_ports(app_id);

        // Clean up or preserve data
        if preserve_data {
            self.data_manager.preserve_app_data(app_id).await?;
        } else {
            self.data_manager.cleanup_app_data(app_id).await?;
        }

        Ok(())
    }

    /// Get container status with dev port info
    pub async fn get_container_status(&self, app_id: &str) -> Result<ContainerStatus> {
        let container_name = format!("archipelago-{}-dev", app_id);
        let mut status = self
            .runtime
            .get_container_status(&container_name)
            .await
            .context("Failed to get container status")?;

        // Add dev port information
        if let Ok(Some(ports)) = self.port_manager.get_port_mapping(app_id) {
            status.ports = ports.iter().map(|p| p.to_string()).collect();
        }

        Ok(status)
    }

    /// List all containers with dev info
    pub async fn list_containers(&self) -> Result<Vec<ContainerStatus>> {
        let containers = self
            .runtime
            .list_containers()
            .await
            .context("Failed to list containers")?;

        // Filter to only archipelago containers and add port info
        let mut result = Vec::new();
        for container in containers {
            if container.name.contains("archipelago-") {
                // Extract app_id from container name
                if let Some(app_id) = container.name.strip_prefix("archipelago-") {
                    if let Some(app_id) = app_id.strip_suffix("-dev") {
                        if let Ok(Some(ports)) = self.port_manager.get_port_mapping(app_id) {
                            let mut container_with_ports = container.clone();
                            container_with_ports.ports = ports.iter().map(|p| p.to_string()).collect();
                            result.push(container_with_ports);
                        } else {
                            result.push(container);
                        }
                    }
                }
            }
        }

        Ok(result)
    }

    /// Get container logs
    pub async fn get_container_logs(&self, app_id: &str, lines: u32) -> Result<Vec<String>> {
        let container_name = format!("archipelago-{}-dev", app_id);
        self.runtime
            .get_container_logs(&container_name, lines)
            .await
            .context("Failed to get container logs")
    }

    /// Get health status
    pub async fn get_health_status(&self, app_id: &str) -> Result<String> {
        let status = self.get_container_status(app_id).await?;
        match status.state {
            archipelago_container::ContainerState::Running => Ok("healthy".to_string()),
            archipelago_container::ContainerState::Stopped
            | archipelago_container::ContainerState::Exited => Ok("unhealthy".to_string()),
            archipelago_container::ContainerState::Created => Ok("starting".to_string()),
            archipelago_container::ContainerState::Paused => Ok("paused".to_string()),
            archipelago_container::ContainerState::Unknown(_) => Ok("unknown".to_string()),
        }
    }

    /// Get port mapping for an app
    #[allow(dead_code)]
    pub fn get_port_mapping(&self, app_id: &str) -> Option<Vec<u16>> {
        self.port_manager.get_port_mapping(app_id).ok().flatten()
    }

    /// Get Bitcoin simulator
    #[allow(dead_code)]
    pub fn bitcoin_simulator(&self) -> &Arc<BitcoinSimulator> {
        &self.bitcoin_simulator
    }
}
