use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ManifestError {
    #[error("Invalid manifest: {0}")]
    Invalid(String),
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("YAML parse error: {0}")]
    Yaml(#[from] serde_yaml::Error),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppManifest {
    pub app: AppDefinition,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppDefinition {
    pub id: String,
    pub name: String,
    pub version: String,
    pub description: Option<String>,
    
    #[serde(default)]
    pub container: ContainerConfig,
    
    #[serde(default)]
    pub dependencies: Vec<Dependency>,
    
    #[serde(default)]
    pub resources: ResourceLimits,
    
    #[serde(default)]
    pub security: SecurityPolicy,
    
    #[serde(default)]
    pub ports: Vec<PortMapping>,
    
    #[serde(default)]
    pub volumes: Vec<Volume>,
    
    #[serde(default)]
    pub environment: Vec<String>,
    
    #[serde(default)]
    pub health_check: Option<HealthCheck>,
    
    #[serde(default)]
    pub devices: Vec<String>,
    
    #[serde(flatten)]
    pub extensions: HashMap<String, serde_yaml::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ContainerConfig {
    pub image: String,
    #[serde(default)]
    pub image_signature: Option<String>,
    #[serde(default = "default_pull_policy")]
    pub pull_policy: String,
}

fn default_pull_policy() -> String {
    "if-not-present".to_string()
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Dependency {
    Storage { storage: String },
    App { app_id: String, version: Option<String> },
    Simple(String),
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ResourceLimits {
    #[serde(default)]
    pub cpu_limit: Option<u32>,
    #[serde(default)]
    pub memory_limit: Option<String>,
    #[serde(default)]
    pub disk_limit: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SecurityPolicy {
    #[serde(default)]
    pub capabilities: Vec<String>,
    #[serde(default = "default_true")]
    pub readonly_root: bool,
    #[serde(default = "default_network_policy")]
    pub network_policy: String,
    #[serde(default)]
    pub apparmor_profile: Option<String>,
}

fn default_true() -> bool {
    true
}

fn default_network_policy() -> String {
    "isolated".to_string()
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PortMapping {
    pub host: u16,
    pub container: u16,
    #[serde(default)]
    pub protocol: String,
}

impl From<(u16, u16)> for PortMapping {
    fn from((host, container): (u16, u16)) -> Self {
        PortMapping {
            host,
            container,
            protocol: "tcp".to_string(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Volume {
    #[serde(rename = "type")]
    pub volume_type: String,
    pub source: String,
    pub target: String,
    #[serde(default)]
    pub options: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthCheck {
    #[serde(rename = "type")]
    pub check_type: String,
    pub endpoint: Option<String>,
    pub path: Option<String>,
    #[serde(default = "default_interval")]
    pub interval: String,
    #[serde(default = "default_timeout")]
    pub timeout: String,
    #[serde(default = "default_retries")]
    pub retries: u32,
}

fn default_interval() -> String {
    "30s".to_string()
}

fn default_timeout() -> String {
    "5s".to_string()
}

fn default_retries() -> u32 {
    3
}

impl AppManifest {
    pub fn from_file(path: &std::path::Path) -> Result<Self, ManifestError> {
        let content = std::fs::read_to_string(path)?;
        Self::from_str(&content)
    }
    
    pub fn from_str(content: &str) -> Result<Self, ManifestError> {
        let manifest: AppManifest = serde_yaml::from_str(content)?;
        manifest.validate()?;
        Ok(manifest)
    }
    
    pub fn validate(&self) -> Result<(), ManifestError> {
        if self.app.id.is_empty() {
            return Err(ManifestError::Invalid("app.id cannot be empty".to_string()));
        }
        
        if self.app.container.image.is_empty() {
            return Err(ManifestError::Invalid("container.image cannot be empty".to_string()));
        }
        
        // Validate version format (semantic versioning)
        if !self.app.version.chars().any(|c| c.is_ascii_digit()) {
            return Err(ManifestError::Invalid("app.version must contain at least one digit".to_string()));
        }
        
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_manifest_parse() {
        let yaml = r#"
app:
  id: test-app
  name: Test App
  version: 1.0.0
  container:
    image: test/image:latest
"#;
        
        let manifest = AppManifest::from_str(yaml).unwrap();
        assert_eq!(manifest.app.id, "test-app");
        assert_eq!(manifest.app.name, "Test App");
        assert_eq!(manifest.app.version, "1.0.0");
    }
    
    #[test]
    fn test_manifest_validation() {
        let yaml = r#"
app:
  id: ""
  name: Test
  version: 1.0.0
  container:
    image: test/image:latest
"#;
        
        let result = AppManifest::from_str(yaml);
        assert!(result.is_err());
    }
}
