use crate::manifest::{AppManifest, Dependency};
use indexmap::IndexMap;
use std::collections::{HashMap, HashSet};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum DependencyError {
    #[error("Circular dependency detected: {0}")]
    CircularDependency(String),
    #[error("Missing dependency: {0}")]
    MissingDependency(String),
    #[error("Version conflict: {0}")]
    VersionConflict(String),
}

pub struct DependencyResolver {
    manifests: IndexMap<String, AppManifest>,
}

impl DependencyResolver {
    pub fn new() -> Self {
        Self {
            manifests: IndexMap::new(),
        }
    }
    
    pub fn add_manifest(&mut self, manifest: AppManifest) {
        self.manifests.insert(manifest.app.id.clone(), manifest);
    }
    
    pub fn resolve_dependencies(&self, app_id: &str) -> Result<Vec<String>, DependencyError> {
        let mut visited = HashSet::new();
        let mut visiting = HashSet::new();
        let mut result = Vec::new();
        
        self.resolve_recursive(app_id, &mut visited, &mut visiting, &mut result)?;
        
        // Reverse to get installation order (dependencies first)
        result.reverse();
        Ok(result)
    }
    
    fn resolve_recursive(
        &self,
        app_id: &str,
        visited: &mut HashSet<String>,
        visiting: &mut HashSet<String>,
        result: &mut Vec<String>,
    ) -> Result<(), DependencyError> {
        if visited.contains(app_id) {
            return Ok(());
        }
        
        if visiting.contains(app_id) {
            return Err(DependencyError::CircularDependency(
                format!("Circular dependency detected involving: {}", app_id)
            ));
        }
        
        visiting.insert(app_id.to_string());
        
        let manifest = self.manifests.get(app_id)
            .ok_or_else(|| DependencyError::MissingDependency(
                format!("App not found: {}", app_id)
            ))?;
        
        // Resolve all dependencies first
        for dep in &manifest.app.dependencies {
            match dep {
                Dependency::App { app_id: dep_id, version: _ } => {
                    self.resolve_recursive(dep_id, visited, visiting, result)?;
                }
                Dependency::Storage { storage: _ } => {
                    // Storage dependencies are checked but don't require other apps
                }
                Dependency::Simple(dep_id) => {
                    self.resolve_recursive(dep_id, visited, visiting, result)?;
                }
            }
        }
        
        visiting.remove(app_id);
        visited.insert(app_id.to_string());
        
        if !result.contains(&app_id.to_string()) {
            result.push(app_id.to_string());
        }
        
        Ok(())
    }
    
    pub fn check_conflicts(&self, app_id: &str) -> Result<(), DependencyError> {
        let manifest = self.manifests.get(app_id)
            .ok_or_else(|| DependencyError::MissingDependency(
                format!("App not found: {}", app_id)
            ))?;
        
        // Check for port conflicts
        let mut port_usage: HashMap<u16, String> = HashMap::new();
        
        for (id, m) in &self.manifests {
            if id == app_id {
                continue;
            }
            
            for port in &m.app.ports {
                if let Some(existing) = port_usage.get(&port.host) {
                    return Err(DependencyError::VersionConflict(
                        format!("Port {} already used by {}", port.host, existing)
                    ));
                }
                port_usage.insert(port.host, id.clone());
            }
        }
        
        // Check for new app's ports
        for port in &manifest.app.ports {
            if let Some(existing) = port_usage.get(&port.host) {
                return Err(DependencyError::VersionConflict(
                    format!("Port {} already used by {}", port.host, existing)
                ));
            }
        }
        
        Ok(())
    }
    
    pub fn calculate_resources(&self, app_ids: &[String]) -> ResourceRequirements {
        let mut total = ResourceRequirements {
            cpu: 0,
            memory_mb: 0,
            disk_gb: 0,
        };
        
        for app_id in app_ids {
            if let Some(manifest) = self.manifests.get(app_id) {
                if let Some(cpu) = manifest.app.resources.cpu_limit {
                    total.cpu += cpu;
                }
                
                if let Some(memory) = &manifest.app.resources.memory_limit {
                    // Parse memory string (e.g., "1Gi", "512Mi")
                    if let Ok(mb) = parse_memory(memory) {
                        total.memory_mb += mb;
                    }
                }
                
                if let Some(disk) = &manifest.app.resources.disk_limit {
                    // Parse disk string (e.g., "10Gi", "500Mi")
                    if let Ok(gb) = parse_disk(disk) {
                        total.disk_gb += gb;
                    }
                }
            }
        }
        
        total
    }
}

fn parse_memory(s: &str) -> Result<u32, ()> {
    let s = s.trim().to_lowercase();
    if s.ends_with("gi") {
        let num: f64 = s.trim_end_matches("gi").parse().map_err(|_| ())?;
        Ok((num * 1024.0) as u32)
    } else if s.ends_with("mi") {
        let num: f64 = s.trim_end_matches("mi").parse().map_err(|_| ())?;
        Ok(num as u32)
    } else {
        Err(())
    }
}

fn parse_disk(s: &str) -> Result<u32, ()> {
    let s = s.trim().to_lowercase();
    if s.ends_with("gi") {
        let num: f64 = s.trim_end_matches("gi").parse().map_err(|_| ())?;
        Ok(num as u32)
    } else if s.ends_with("ti") {
        let num: f64 = s.trim_end_matches("ti").parse().map_err(|_| ())?;
        Ok((num * 1024.0) as u32)
    } else {
        Err(())
    }
}

#[derive(Debug, Clone)]
pub struct ResourceRequirements {
    pub cpu: u32,
    pub memory_mb: u32,
    pub disk_gb: u32,
}

impl Default for DependencyResolver {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::manifest::{AppManifest, AppDefinition, ContainerConfig};
    
    fn create_test_manifest(id: &str, deps: Vec<Dependency>) -> AppManifest {
        AppManifest {
            app: AppDefinition {
                id: id.to_string(),
                name: format!("Test {}", id),
                version: "1.0.0".to_string(),
                description: None,
                container: ContainerConfig {
                    image: format!("test/{}:latest", id),
                    image_signature: None,
                    pull_policy: "if-not-present".to_string(),
                },
                dependencies: deps,
                resources: Default::default(),
                security: Default::default(),
                ports: vec![],
                volumes: vec![],
                environment: vec![],
                health_check: None,
                devices: vec![],
                extensions: Default::default(),
            },
        }
    }
    
    #[test]
    fn test_simple_dependency() {
        let mut resolver = DependencyResolver::new();
        resolver.add_manifest(create_test_manifest("app1", vec![]));
        resolver.add_manifest(create_test_manifest("app2", vec![
            Dependency::Simple("app1".to_string())
        ]));
        
        let deps = resolver.resolve_dependencies("app2").unwrap();
        assert_eq!(deps, vec!["app1", "app2"]);
    }
    
    #[test]
    fn test_circular_dependency() {
        let mut resolver = DependencyResolver::new();
        resolver.add_manifest(create_test_manifest("app1", vec![
            Dependency::Simple("app2".to_string())
        ]));
        resolver.add_manifest(create_test_manifest("app2", vec![
            Dependency::Simple("app1".to_string())
        ]));
        
        let result = resolver.resolve_dependencies("app1");
        assert!(result.is_err());
    }
}
