//! HTTP client for init service (service initialization and startup).

use std::time::Duration;

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

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

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

/// Service initialization request
#[derive(Debug, Serialize, Deserialize)]
pub struct InitServiceRequest {
    /// Service name
    pub name: String,
    /// Service configuration as JSON
    pub config: serde_json::Value,
    /// Optional dependencies to initialize first
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dependencies: Option<Vec<String>>,
}

/// Service initialization response
#[derive(Debug, Serialize, Deserialize)]
pub struct InitServiceResponse {
    /// Service name
    pub name: String,
    /// Initialization success status
    pub success: bool,
    /// Status message
    pub message: String,
    /// Initialization time in milliseconds
    pub duration_ms: u64,
}

/// Service health check response
#[derive(Debug, Serialize, Deserialize)]
pub struct ServiceHealthStatus {
    /// Service name
    pub name: String,
    /// Health status: "running", "stopped", "error"
    pub status: String,
    /// Optional error message
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
    /// Services that depend on this one
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dependents: Option<Vec<String>>,
}

impl InitClient {
    /// Create a new init service client
    pub fn new(base_url: impl Into<String>) -> ServiceResult<Self> {
        let url = base_url.into();
        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(60),
        })
    }

    /// Create a new init 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)
    }

    /// Initialize a service
    pub async fn init_service(
        &self,
        name: impl Into<String>,
        config: serde_json::Value,
    ) -> ServiceResult<InitServiceResponse> {
        let request = InitServiceRequest {
            name: name.into(),
            config,
            dependencies: None,
        };
        self.init_service_with_deps(request).await
    }

    /// Initialize a service with dependencies
    pub async fn init_service_with_deps(
        &self,
        request: InitServiceRequest,
    ) -> ServiceResult<InitServiceResponse> {
        let url = format!("{}/api/v1/init/services", 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?)
    }

    /// Check if a service is initialized
    pub async fn check_service(
        &self,
        name: impl Into<String>,
    ) -> ServiceResult<ServiceHealthStatus> {
        let name = name.into();
        let url = format!("{}/api/v1/init/check/{}", self.base_url, name);

        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 initialization status for all services
    pub async fn status_all(&self) -> ServiceResult<Vec<ServiceHealthStatus>> {
        let url = format!("{}/api/v1/init/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?)
    }

    /// Health check for init 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(
                "Init service unavailable".to_string(),
            )),
        }
    }
}

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

    #[test]
    fn test_init_client_creation() {
        let client = InitClient::new("http://localhost:8082");
        assert!(client.is_ok());
    }

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

    #[test]
    fn test_init_service_request() {
        let request = InitServiceRequest {
            name: "database".to_string(),
            config: serde_json::json!({"host": "localhost", "port": 5432}),
            dependencies: Some(vec!["network".to_string()]),
        };

        let json = serde_json::to_string(&request).unwrap();
        assert!(json.contains("database"));
        assert!(json.contains("localhost"));
    }
}
