//! HTTP client for machines service (SSH operations).

use std::time::Duration;

use reqwest::Client;
use serde::{Deserialize, Serialize};

use crate::error::{ServiceError, ServiceResult};

/// Client for calling machines service (SSH operations)
#[derive(Debug, Clone)]
pub struct MachinesClient {
    base_url: String,
    http_client: Client,
    timeout: Duration,
}

/// Command execution request
#[derive(Debug, Serialize, Deserialize)]
pub struct ExecuteCommandRequest {
    /// Target machine name or hostname
    pub host: String,
    /// Command to execute
    pub command: String,
    /// Optional retry policy
    #[serde(skip_serializing_if = "Option::is_none")]
    pub retry_policy: Option<RetryPolicy>,
}

/// Retry policy for command execution
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RetryPolicy {
    /// Maximum number of retries
    pub max_retries: u32,
    /// Backoff strategy: "exponential" or "linear"
    pub backoff: String,
    /// Initial backoff duration in milliseconds
    pub initial_backoff_ms: u64,
}

/// Command execution response
#[derive(Debug, Serialize, Deserialize)]
pub struct ExecuteCommandResponse {
    /// Command output (stdout)
    pub output: String,
    /// Error output (stderr) if any
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
    /// Process exit code
    pub exit_code: i32,
    /// Execution time in milliseconds
    pub execution_time_ms: u64,
}

/// Machine pool status
#[derive(Debug, Serialize, Deserialize)]
pub struct MachinePoolStatus {
    /// Number of active connections
    pub active_connections: usize,
    /// Number of pending operations
    pub pending_operations: usize,
    /// Health status: "healthy", "degraded", "unhealthy"
    pub health: String,
    /// Timestamp of last update
    pub last_updated: String,
}

/// Machine configuration info
#[derive(Debug, Serialize, Deserialize)]
pub struct MachineInfo {
    /// Machine name/identifier
    pub name: String,
    /// SSH address
    pub address: String,
    /// SSH port
    pub port: u16,
    /// Authentication method
    pub auth_method: String,
}

impl MachinesClient {
    /// Create a new machines service client
    pub fn new(base_url: impl Into<String>) -> ServiceResult<Self> {
        let url = base_url.into();
        // Validate URL format
        if !url.starts_with("http://") && !url.starts_with("https://") {
            return Err(ServiceError::InvalidUrl(url));
        }

        Ok(Self {
            base_url: url,
            http_client: Client::new(),
            timeout: Duration::from_secs(30),
        })
    }

    /// Create a new machines service client with custom timeout
    pub fn with_timeout(base_url: impl Into<String>, timeout: Duration) -> ServiceResult<Self> {
        let mut client = Self::new(base_url)?;
        client.timeout = timeout;
        Ok(client)
    }

    /// Execute a command on a remote machine via SSH
    pub async fn execute_command(
        &self,
        host: impl Into<String>,
        command: impl Into<String>,
    ) -> ServiceResult<ExecuteCommandResponse> {
        let request = ExecuteCommandRequest {
            host: host.into(),
            command: command.into(),
            retry_policy: None,
        };
        self.execute_command_with_retry(request).await
    }

    /// Execute a command with retry policy
    pub async fn execute_command_with_retry(
        &self,
        request: ExecuteCommandRequest,
    ) -> ServiceResult<ExecuteCommandResponse> {
        let url = format!("{}/api/v1/machines/execute", self.base_url);

        let response = self
            .http_client
            .post(&url)
            .timeout(self.timeout)
            .json(&request)
            .send()
            .await?;

        if !response.status().is_success() {
            let status = response.status().as_u16();
            let body = response.text().await.unwrap_or_default();
            return Err(ServiceError::from_status(status, body));
        }

        Ok(response.json().await?)
    }

    /// Get machine pool status
    pub async fn pool_status(&self) -> ServiceResult<MachinePoolStatus> {
        let url = format!("{}/api/v1/machines/pool/status", self.base_url);

        let response = self
            .http_client
            .get(&url)
            .timeout(self.timeout)
            .send()
            .await?;

        if !response.status().is_success() {
            let status = response.status().as_u16();
            let body = response.text().await.unwrap_or_default();
            return Err(ServiceError::from_status(status, body));
        }

        Ok(response.json().await?)
    }

    /// Get information about a specific machine
    pub async fn get_machine(&self, name: impl Into<String>) -> ServiceResult<MachineInfo> {
        let name = name.into();
        let url = format!("{}/api/v1/machines/{}", self.base_url, name);

        let response = self
            .http_client
            .get(&url)
            .timeout(self.timeout)
            .send()
            .await?;

        match response.status().as_u16() {
            404 => Err(ServiceError::MachineNotFound(name)),
            status if !response.status().is_success() => {
                let body = response.text().await.unwrap_or_default();
                Err(ServiceError::from_status(status, body))
            }
            _ => Ok(response.json().await?),
        }
    }

    /// List all machines
    pub async fn list_machines(&self) -> ServiceResult<Vec<MachineInfo>> {
        let url = format!("{}/api/v1/machines", self.base_url);

        let response = self
            .http_client
            .get(&url)
            .timeout(self.timeout)
            .send()
            .await?;

        if !response.status().is_success() {
            let status = response.status().as_u16();
            let body = response.text().await.unwrap_or_default();
            return Err(ServiceError::from_status(status, body));
        }

        Ok(response.json().await?)
    }

    /// Health check for machines service
    pub async fn health_check(&self) -> ServiceResult<bool> {
        let url = format!("{}/health", self.base_url);

        match self
            .http_client
            .get(&url)
            .timeout(Duration::from_secs(5))
            .send()
            .await
        {
            Ok(response) => Ok(response.status().is_success()),
            Err(_) => Err(ServiceError::ServiceUnavailable(
                "Machines service unavailable".to_string(),
            )),
        }
    }
}

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

    #[test]
    fn test_machines_client_creation() {
        let client = MachinesClient::new("http://localhost:8081");
        assert!(client.is_ok());
    }

    #[test]
    fn test_invalid_url() {
        let client = MachinesClient::new("invalid-url");
        assert!(client.is_err());
    }

    #[test]
    fn test_execute_command_request() {
        let request = ExecuteCommandRequest {
            host: "server1".to_string(),
            command: "ls -la".to_string(),
            retry_policy: None,
        };

        let json = serde_json::to_string(&request).unwrap();
        assert!(json.contains("server1"));
        assert!(json.contains("ls -la"));
    }
}
