//! HTTP client for AI service (MCP, RAG, DAG, Knowledge Graph).

use std::time::Duration;

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

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

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

/// MCP tool call request
#[derive(Debug, Serialize, Deserialize)]
pub struct McpToolRequest {
    /// Tool name (e.g., "execute_provisioning_plan")
    pub tool_name: String,
    /// Tool arguments as JSON
    pub args: serde_json::Value,
}

/// MCP tool call response
#[derive(Debug, Serialize, Deserialize)]
pub struct McpToolResponse {
    /// Tool execution result
    pub result: serde_json::Value,
    /// Execution time in milliseconds
    pub duration_ms: u64,
}

/// AI question request (RAG)
#[derive(Debug, Serialize, Deserialize)]
pub struct AskRequest {
    /// User question
    pub question: String,
    /// Optional context
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context: Option<String>,
}

/// AI question response (RAG)
#[derive(Debug, Serialize, Deserialize)]
pub struct AskResponse {
    /// Answer from AI
    pub answer: String,
    /// Source documents used
    pub sources: Vec<String>,
    /// Confidence level (0-100)
    pub confidence: u8,
    /// Reasoning explanation
    pub reasoning: String,
}

/// Extension DAG (Directed Acyclic Graph) node
#[derive(Debug, Serialize, Deserialize)]
pub struct DagNode {
    /// Extension/component name
    pub name: String,
    /// Dependencies on other nodes
    pub dependencies: Vec<String>,
    /// Component version
    pub version: String,
}

/// Extension DAG response
#[derive(Debug, Serialize, Deserialize)]
pub struct DagResponse {
    /// DAG nodes (extensions)
    pub nodes: Vec<DagNode>,
    /// DAG edges (dependencies)
    pub edges: Vec<(String, String)>,
}

/// Best practice entry
#[derive(Debug, Serialize, Deserialize)]
pub struct BestPractice {
    /// Practice title
    pub title: String,
    /// Practice description
    pub description: String,
    /// Category (e.g., "deployment", "security")
    pub category: String,
    /// Relevance score (0-100)
    pub relevance: u8,
}

impl AiClient {
    /// Create a new AI 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(120),
        })
    }

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

    /// Call an MCP tool
    pub async fn call_mcp_tool(
        &self,
        tool_name: impl Into<String>,
        args: serde_json::Value,
    ) -> ServiceResult<McpToolResponse> {
        let request = McpToolRequest {
            tool_name: tool_name.into(),
            args,
        };
        self.call_mcp_tool_with_request(request).await
    }

    /// Call an MCP tool with full request object
    pub async fn call_mcp_tool_with_request(
        &self,
        request: McpToolRequest,
    ) -> ServiceResult<McpToolResponse> {
        let url = format!("{}/api/v1/ai/mcp/tool", 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?)
    }

    /// Ask AI a question (RAG-powered)
    pub async fn ask(&self, question: impl Into<String>) -> ServiceResult<AskResponse> {
        let request = AskRequest {
            question: question.into(),
            context: None,
        };
        self.ask_with_context(request).await
    }

    /// Ask AI with context
    pub async fn ask_with_context(&self, request: AskRequest) -> ServiceResult<AskResponse> {
        let url = format!("{}/api/v1/ai/ask", 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 the extension dependency DAG
    pub async fn get_extension_dag(&self) -> ServiceResult<DagResponse> {
        let url = format!("{}/api/v1/ai/dag/extensions", 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 best practices for a category
    pub async fn get_best_practices(
        &self,
        category: impl Into<String>,
    ) -> ServiceResult<Vec<BestPractice>> {
        let category = category.into();
        let url = format!(
            "{}/api/v1/ai/knowledge/best-practices?category={}",
            self.base_url, category
        );

        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 AI 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(
                "AI service unavailable".to_string(),
            )),
        }
    }
}

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

    #[test]
    fn test_ai_client_creation() {
        let client = AiClient::new("http://localhost:8083");
        assert!(client.is_ok());
    }

    #[test]
    fn test_mcp_tool_request() {
        let request = McpToolRequest {
            tool_name: "execute_provisioning_plan".to_string(),
            args: serde_json::json!({"plan_id": "12345"}),
        };

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

    #[test]
    fn test_ask_request() {
        let request = AskRequest {
            question: "How do I deploy to Kubernetes?".to_string(),
            context: Some("production environment".to_string()),
        };

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