//! SSH Connection Types and Metadata
//!
//! Defines connection metadata (not actual SSH sessions), command
//! requests/responses, and credential types for hybrid credential management.

use std::collections::HashMap;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

/// Connection metadata (NOT an actual SSH session)
///
/// Represents connection state in the pool without storing session objects.
/// This metadata-only pooling approach keeps memory lightweight and flexible.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PooledConnection {
    /// Unique connection identifier
    pub id: String,
    /// Target host
    pub host: String,
    /// SSH port
    pub port: u16,
    /// SSH username
    pub username: String,
    /// Type of credential used
    pub credential_type: SshCredentialType,
    /// When connection was created
    pub created_at: DateTime<Utc>,
    /// When connection was last used
    pub last_used_at: DateTime<Utc>,
    /// Current health status
    pub health: ConnectionHealth,
    /// Number of times connection has been reused
    pub reuse_count: usize,
    /// Total successful commands executed
    pub success_count: usize,
    /// Total failed commands
    pub failure_count: usize,
}

impl PooledConnection {
    /// Create new connection metadata
    pub fn new(
        id: String,
        host: String,
        port: u16,
        username: String,
        credential_type: SshCredentialType,
    ) -> Self {
        let now = Utc::now();
        Self {
            id,
            host,
            port,
            username,
            credential_type,
            created_at: now,
            last_used_at: now,
            health: ConnectionHealth::Healthy,
            reuse_count: 0,
            success_count: 0,
            failure_count: 0,
        }
    }

    /// Mark connection as used and update timestamp
    pub fn mark_used(&mut self) {
        self.last_used_at = Utc::now();
        self.reuse_count += 1;
    }

    /// Mark last command as successful
    pub fn mark_success(&mut self) {
        self.success_count += 1;
        self.health = ConnectionHealth::Healthy;
    }

    /// Mark last command as failed
    pub fn mark_failure(&mut self) {
        self.failure_count += 1;
    }

    /// Get success rate as percentage
    pub fn success_rate(&self) -> f64 {
        let total = self.success_count + self.failure_count;
        if total == 0 {
            100.0
        } else {
            (self.success_count as f64 / total as f64) * 100.0
        }
    }
}

/// Connection health status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConnectionHealth {
    /// Connection is healthy and working
    Healthy,
    /// Connection is degraded (some failures)
    Degraded,
    /// Connection is unhealthy (many failures)
    Unhealthy,
    /// Connection has expired
    Expired,
}

/// SSH credential type (part of hybrid strategy)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SshCredentialType {
    /// Permanent key from Control-Center vault
    PermanentKey { vault_path: String },
    /// Temporal key from orchestrator (auto-expires)
    TemporalKey {
        key_id: String,
        expires_at: DateTime<Utc>,
    },
    /// Static provisioning key (fallback)
    StaticKey { key_path: String },
}

impl SshCredentialType {
    /// Get credential type name
    pub fn type_name(&self) -> &str {
        match self {
            Self::PermanentKey { .. } => "permanent",
            Self::TemporalKey { .. } => "temporal",
            Self::StaticKey { .. } => "static",
        }
    }

    /// Check if this temporal credential has expired
    pub fn is_expired(&self) -> bool {
        match self {
            Self::TemporalKey { expires_at, .. } => Utc::now() > *expires_at,
            _ => false,
        }
    }
}

// ============================================================================
// SSH Command Request/Response Types
// ============================================================================

/// SSH command execution request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SshCommandRequest {
    /// Target host
    pub host: String,
    /// SSH port (default: 22)
    #[serde(default = "default_ssh_port")]
    pub port: u16,
    /// SSH username
    pub username: String,
    /// Command to execute
    pub command: String,
    /// Working directory for command
    pub working_dir: Option<String>,
    /// Environment variables to set
    #[serde(default)]
    pub env: HashMap<String, String>,
    /// Command timeout in seconds
    pub timeout_seconds: Option<u64>,
    /// Preferred credential type
    pub credential_preference: Option<SshCredentialType>,
}

fn default_ssh_port() -> u16 {
    22
}

impl SshCommandRequest {
    /// Validate the request
    pub fn validate(&self) -> Result<(), String> {
        if self.host.is_empty() {
            return Err("host cannot be empty".to_string());
        }

        if self.username.is_empty() {
            return Err("username cannot be empty".to_string());
        }

        if self.command.is_empty() {
            return Err("command cannot be empty".to_string());
        }

        if self.port == 0 {
            return Err("port must be > 0".to_string());
        }

        Ok(())
    }
}

/// SSH command execution result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SshCommandResult {
    /// Command exit code
    pub exit_code: i32,
    /// Standard output
    pub stdout: String,
    /// Standard error
    pub stderr: String,
    /// Execution duration in milliseconds
    pub duration_ms: u64,
    /// Connection ID used for this command
    pub connection_id: String,
    /// Whether execution was successful (exit code 0)
    pub success: bool,
    /// When command was executed
    pub executed_at: DateTime<Utc>,
}

impl SshCommandResult {
    /// Create a successful result
    pub fn success(
        stdout: String,
        stderr: String,
        duration_ms: u64,
        connection_id: String,
    ) -> Self {
        Self {
            exit_code: 0,
            stdout,
            stderr,
            duration_ms,
            connection_id,
            success: true,
            executed_at: Utc::now(),
        }
    }

    /// Create a failed result
    pub fn failure(
        exit_code: i32,
        stdout: String,
        stderr: String,
        duration_ms: u64,
        connection_id: String,
    ) -> Self {
        Self {
            exit_code,
            stdout,
            stderr,
            duration_ms,
            connection_id,
            success: false,
            executed_at: Utc::now(),
        }
    }

    /// Get output (stdout or stderr)
    pub fn output(&self) -> String {
        if self.stdout.is_empty() {
            self.stderr.clone()
        } else {
            self.stdout.clone()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_connection_creation() {
        let conn = PooledConnection::new(
            "conn-1".to_string(),
            "host.example.com".to_string(),
            22,
            "root".to_string(),
            SshCredentialType::StaticKey {
                key_path: "/home/user/.ssh/id_rsa".to_string(),
            },
        );

        assert_eq!(conn.host, "host.example.com");
        assert_eq!(conn.reuse_count, 0);
        assert_eq!(conn.health, ConnectionHealth::Healthy);
    }

    #[test]
    fn test_success_rate() {
        let mut conn = PooledConnection::new(
            "conn-1".to_string(),
            "host.example.com".to_string(),
            22,
            "root".to_string(),
            SshCredentialType::StaticKey {
                key_path: "/home/user/.ssh/id_rsa".to_string(),
            },
        );

        conn.success_count = 90;
        conn.failure_count = 10;

        assert_eq!(conn.success_rate(), 90.0);
    }

    #[test]
    fn test_request_validation() {
        let mut req = SshCommandRequest {
            host: "host.example.com".to_string(),
            port: 22,
            username: "root".to_string(),
            command: "ls -la".to_string(),
            working_dir: None,
            env: HashMap::new(),
            timeout_seconds: None,
            credential_preference: None,
        };

        assert!(req.validate().is_ok());

        req.host = String::new();
        assert!(req.validate().is_err());
    }

    #[test]
    fn test_credential_expiry() {
        let expired = SshCredentialType::TemporalKey {
            key_id: "key-1".to_string(),
            expires_at: Utc::now() - chrono::Duration::seconds(100),
        };

        assert!(expired.is_expired());

        let valid = SshCredentialType::TemporalKey {
            key_id: "key-2".to_string(),
            expires_at: Utc::now() + chrono::Duration::seconds(3600),
        };

        assert!(!valid.is_expired());
    }
}
