use std::marker::PhantomData;
use std::process::Command;

// ===== Capability Traits =====

/// Base trait for all capabilities
pub trait Capability {}

/// Capability for executing shell commands on a host
pub trait CommandCapability: Capability {
    fn execute_command(&self, command: &str, args: &[&str]) -> Result<String, String>;
}

/// Capability for interacting with a Kubernetes cluster
pub trait KubernetesCapability: Capability {
    fn apply_manifest(&self, manifest: &str) -> Result<(), String>;
    fn get_resource(&self, resource_type: &str, name: &str) -> Result<String, String>;
}

// ===== Topology Traits =====

/// Base trait for all topologies
pub trait Topology {
    // Base topology methods that don't depend on capabilities
    fn name(&self) -> &str;
}

// ===== Score Traits =====

/// Generic Score trait with an associated Capability type
pub trait Score<T: Topology> {
    fn apply(&self, topology: &T) -> Result<(), String>;
    fn name(&self) -> &str;
}

// ===== Concrete Topologies =====

/// A topology representing a Linux host
pub struct LinuxHostTopology {
    name: String,
    host: String,
}

impl LinuxHostTopology {
    pub fn new(name: String, host: String) -> Self {
        Self { name, host }
    }
}

impl Topology for LinuxHostTopology {
    fn name(&self) -> &str {
        &self.name
    }
}

impl CommandCapability for LinuxHostTopology {
    fn execute_command(&self, command: &str, args: &[&str]) -> Result<String, String> {
        println!("Executing on {}: {} {:?}", self.host, command, args);
        // In a real implementation, this would SSH to the host and execute the command
        let output = Command::new(command)
            .args(args)
            .output()
            .map_err(|e| e.to_string())?;
        
        if output.status.success() {
            Ok(String::from_utf8_lossy(&output.stdout).to_string())
        } else {
            Err(String::from_utf8_lossy(&output.stderr).to_string())
        }
    }
}

/// A topology representing a K3D Kubernetes cluster
pub struct K3DTopology {
    name: String,
    linux_host: LinuxHostTopology,
    cluster_name: String,
}

impl K3DTopology {
    pub fn new(name: String, linux_host: LinuxHostTopology, cluster_name: String) -> Self {
        Self { 
            name,
            linux_host,
            cluster_name,
        }
    }
}

impl Topology for K3DTopology {
    fn name(&self) -> &str {
        &self.name
    }
}

impl CommandCapability for K3DTopology {
    fn execute_command(&self, command: &str, args: &[&str]) -> Result<String, String> {
        // Delegate to the underlying Linux host
        self.linux_host.execute_command(command, args)
    }
}

impl KubernetesCapability for K3DTopology {
    fn apply_manifest(&self, manifest: &str) -> Result<(), String> {
        println!("Applying manifest to K3D cluster '{}'", self.cluster_name);
        // Write manifest to a temporary file
        let temp_file = format!("/tmp/manifest-{}.yaml", rand::random::<u32>());
        self.execute_command("bash", &["-c", &format!("cat > {}", temp_file)])?;
        
        // Apply with kubectl
        self.execute_command(
            "kubectl", 
            &["--context", &format!("k3d-{}", self.cluster_name), "apply", "-f", &temp_file]
        )?;
        
        Ok(())
    }

    fn get_resource(&self, resource_type: &str, name: &str) -> Result<String, String> {
        println!("Getting resource {}/{} from K3D cluster '{}'", resource_type, name, self.cluster_name);
        self.execute_command(
            "kubectl",
            &[
                "--context", 
                &format!("k3d-{}", self.cluster_name),
                "get",
                resource_type,
                name,
                "-o",
                "yaml",
            ]
        )
    }
}

// ===== Concrete Scores =====

/// A score that executes commands on a topology
pub struct CommandScore {
    name: String,
    command: String,
    args: Vec<String>,
}

impl CommandScore {
    pub fn new(name: String, command: String, args: Vec<String>) -> Self {
        Self { name, command, args }
    }
}

impl<T> Score<T> for CommandScore 
where 
    T: Topology + CommandCapability
{
    fn apply(&self, topology: &T) -> Result<(), String> {
        println!("Applying CommandScore '{}' to topology '{}'", self.name, topology.name());
        let args_refs: Vec<&str> = self.args.iter().map(|s| s.as_str()).collect();
        topology.execute_command(&self.command, &args_refs)?;
        Ok(())
    }

    fn name(&self) -> &str {
        &self.name
    }
}

/// A score that applies Kubernetes resources to a topology
pub struct K8sResourceScore {
    name: String,
    manifest: String,
}

impl K8sResourceScore {
    pub fn new(name: String, manifest: String) -> Self {
        Self { name, manifest }
    }
}

impl<T> Score<T> for K8sResourceScore 
where 
    T: Topology + KubernetesCapability
{
    fn apply(&self, topology: &T) -> Result<(), String> {
        println!("Applying K8sResourceScore '{}' to topology '{}'", self.name, topology.name());
        topology.apply_manifest(&self.manifest)
    }

    fn name(&self) -> &str {
        &self.name
    }
}

// ===== Maestro Orchestrator =====

/// Type-safe orchestrator that enforces capability requirements at compile time
pub struct Maestro<T: Topology> {
    topology: T,
    scores: Vec<Box<dyn ScoreWrapper<T>>>,
}

/// A trait object wrapper that hides the specific Score type but preserves its
/// capability requirements
trait ScoreWrapper<T: Topology> {
    fn apply(&self, topology: &T) -> Result<(), String>;
    fn name(&self) -> &str;
}

/// Implementation of ScoreWrapper for any Score that works with topology T
impl<T, S> ScoreWrapper<T> for S 
where 
    T: Topology,
    S: Score<T> + 'static
{
    fn apply(&self, topology: &T) -> Result<(), String> {
        <S as Score<T>>::apply(self, topology)
    }

    fn name(&self) -> &str {
        <S as Score<T>>::name(self)
    }
}

impl<T: Topology> Maestro<T> {
    pub fn new(topology: T) -> Self {
        Self {
            topology,
            scores: Vec::new(),
        }
    }

    /// Register a score that is compatible with this topology's capabilities
    pub fn register_score<S>(&mut self, score: S) 
    where 
        S: Score<T> + 'static
    {
        println!("Registering score '{}' for topology '{}'", score.name(), self.topology.name());
        self.scores.push(Box::new(score));
    }

    /// Apply all registered scores to the topology
    pub fn orchestrate(&self) -> Result<(), String> {
        println!("Orchestrating topology '{}'", self.topology.name());
        for score in &self.scores {
            score.apply(&self.topology)?;
        }
        Ok(())
    }
}

// ===== Example Usage =====

fn main() {
    // Create a Linux host topology
    let linux_host = LinuxHostTopology::new(
        "dev-machine".to_string(),
        "localhost".to_string()
    );
    
    // Create a maestro for the Linux host
    let mut linux_maestro = Maestro::new(linux_host);
    
    // Register a command score that works with any topology having CommandCapability
    linux_maestro.register_score(CommandScore::new(
        "check-disk".to_string(),
        "df".to_string(),
        vec!["-h".to_string()]
    ));
    
    // This would fail to compile if we tried to register a K8sResourceScore
    // because LinuxHostTopology doesn't implement KubernetesCapability
    // linux_maestro.register_score(K8sResourceScore::new(...));
    
    // Create a K3D topology which has both Command and Kubernetes capabilities
    let k3d_host = LinuxHostTopology::new(
        "k3d-host".to_string(),
        "localhost".to_string()
    );
    
    let k3d_topology = K3DTopology::new(
        "dev-cluster".to_string(),
        k3d_host,
        "devcluster".to_string()
    );
    
    // Create a maestro for the K3D topology
    let mut k3d_maestro = Maestro::new(k3d_topology);
    
    // We can register both command scores and kubernetes scores
    k3d_maestro.register_score(CommandScore::new(
        "check-nodes".to_string(),
        "kubectl".to_string(),
        vec!["get".to_string(), "nodes".to_string()]
    ));
    
    k3d_maestro.register_score(K8sResourceScore::new(
        "deploy-nginx".to_string(),
        r#"
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: nginx
        spec:
          replicas: 1
          selector:
            matchLabels:
              app: nginx
          template:
            metadata:
              labels:
                app: nginx
            spec:
              containers:
              - name: nginx
                image: nginx:latest
                ports:
                - containerPort: 80
        "#.to_string()
    ));
    
    // Orchestrate both topologies
    linux_maestro.orchestrate().unwrap();
    k3d_maestro.orchestrate().unwrap();
}
