use std::time::Duration;

use reqwest::Client;
use serde_json::json;
use tracing::{debug, info};
use vapora_a2a::{A2aMessage, A2aMessagePart, A2aTask, A2aTaskStatus, AgentCard};

use crate::error::{A2aClientError, Result};
use crate::retry::RetryPolicy;

/// A2A Protocol Client for calling remote A2A servers
pub struct A2aClient {
    base_url: String,
    http_client: Client,
    timeout: Duration,
    retry_policy: RetryPolicy,
}

impl A2aClient {
    /// Create a new A2A client pointing to a remote server
    pub fn new(base_url: impl Into<String>) -> Self {
        Self::with_timeout(base_url, Duration::from_secs(30))
    }

    /// Create a new A2A client with custom timeout
    pub fn with_timeout(base_url: impl Into<String>, timeout: Duration) -> Self {
        Self::with_retry_policy(base_url, timeout, RetryPolicy::default())
    }

    /// Create a new A2A client with custom timeout and retry policy
    pub fn with_retry_policy(
        base_url: impl Into<String>,
        timeout: Duration,
        retry_policy: RetryPolicy,
    ) -> Self {
        let http_client = Client::builder()
            .timeout(timeout)
            .build()
            .unwrap_or_default();

        Self {
            base_url: base_url.into(),
            http_client,
            timeout,
            retry_policy,
        }
    }

    /// Discover agent capabilities by fetching the agent card
    pub async fn discover_agent(&self) -> Result<AgentCard> {
        debug!("Discovering agent at {}", self.base_url);

        let url = format!("{}/.well-known/agent.json", self.base_url);
        let response = self
            .http_client
            .get(&url)
            .timeout(self.timeout)
            .send()
            .await
            .map_err(|e| {
                if e.is_timeout() {
                    A2aClientError::Timeout(self.base_url.clone())
                } else {
                    A2aClientError::HttpError(e)
                }
            })?;

        if !response.status().is_success() {
            return Err(A2aClientError::InvalidResponse);
        }

        let agent_card = response.json::<AgentCard>().await?;
        info!(
            "Discovered agent: {} ({})",
            agent_card.name, agent_card.version
        );

        Ok(agent_card)
    }

    /// Dispatch a task to the remote A2A server
    pub async fn dispatch_task(
        &self,
        task_id: String,
        title: String,
        description: Option<String>,
        skill: Option<String>,
    ) -> Result<String> {
        debug!("Dispatching task {} to {}", task_id, self.base_url);

        let task = self.build_task(task_id, title, description, skill);

        // Use retry policy for transient failures
        self.retry_policy
            .execute(|| {
                let task = task.clone();
                async move { self.send_task(task).await }
            })
            .await
    }

    fn build_task(
        &self,
        task_id: String,
        title: String,
        description: Option<String>,
        skill: Option<String>,
    ) -> A2aTask {
        let desc = description.unwrap_or_default();
        let task_text = if desc.is_empty() {
            title
        } else {
            format!("{}\n{}", title, desc)
        };

        let mut metadata = std::collections::HashMap::new();
        if let Some(s) = skill {
            metadata.insert("skill".to_string(), json!(s));
        }

        A2aTask {
            id: task_id,
            message: A2aMessage {
                role: "user".to_string(),
                parts: vec![A2aMessagePart::Text(task_text)],
            },
            metadata,
        }
    }

    async fn send_task(&self, task: A2aTask) -> Result<String> {
        let url = format!("{}/a2a", self.base_url);
        let response = self
            .http_client
            .post(&url)
            .json(&task)
            .timeout(self.timeout)
            .send()
            .await
            .map_err(|e| self.map_http_error(e))?;

        let status = response.status();
        let body = response.text().await?;

        if !status.is_success() {
            return self.parse_error_response(&body);
        }

        self.extract_task_id(&body)
    }

    fn map_http_error(&self, e: reqwest::Error) -> A2aClientError {
        if e.is_timeout() {
            A2aClientError::Timeout(self.base_url.clone())
        } else if e.is_connect() {
            A2aClientError::ConnectionRefused(self.base_url.clone())
        } else {
            A2aClientError::HttpError(e)
        }
    }

    fn parse_error_response(&self, body: &str) -> Result<String> {
        if let Ok(error_response) = serde_json::from_str::<serde_json::Value>(body) {
            if let Some(error) = error_response.get("error") {
                let code = error.get("code").and_then(|c| c.as_i64()).unwrap_or(-1);
                let message = error
                    .get("message")
                    .and_then(|m| m.as_str())
                    .unwrap_or("Unknown error");
                return Err(A2aClientError::ServerError {
                    code: code as i32,
                    message: message.to_string(),
                });
            }
        }
        Err(A2aClientError::InvalidResponse)
    }

    fn extract_task_id(&self, body: &str) -> Result<String> {
        let response_json = serde_json::from_str::<serde_json::Value>(body)?;

        if let Some(id) = response_json.get("id").and_then(|v| v.as_str()) {
            info!("Task dispatched with ID: {}", id);
            return Ok(id.to_string());
        }

        if let Some(result) = response_json.get("result") {
            if let Some(id) = result.get("id").and_then(|v| v.as_str()) {
                return Ok(id.to_string());
            }
        }

        Err(A2aClientError::InvalidResponse)
    }

    /// Query the status of a task
    pub async fn get_task_status(&self, task_id: &str) -> Result<A2aTaskStatus> {
        debug!("Fetching task status for {}", task_id);

        let url = format!("{}/a2a/tasks/{}", self.base_url, task_id);
        let response = self
            .http_client
            .get(&url)
            .timeout(self.timeout)
            .send()
            .await
            .map_err(|e| {
                if e.is_timeout() {
                    A2aClientError::Timeout(self.base_url.clone())
                } else {
                    A2aClientError::HttpError(e)
                }
            })?;

        let status = response.status();
        let body = response.text().await?;

        if status == reqwest::StatusCode::NOT_FOUND {
            return Err(A2aClientError::TaskNotFound(task_id.to_string()));
        }

        if !status.is_success() {
            if let Ok(error_response) = serde_json::from_str::<serde_json::Value>(&body) {
                if let Some(error) = error_response.get("error") {
                    let code = error.get("code").and_then(|c| c.as_i64()).unwrap_or(-1);
                    let message = error
                        .get("message")
                        .and_then(|m| m.as_str())
                        .unwrap_or("Unknown error");
                    return Err(A2aClientError::ServerError {
                        code: code as i32,
                        message: message.to_string(),
                    });
                }
            }
            return Err(A2aClientError::InvalidResponse);
        }

        let response_json = serde_json::from_str::<serde_json::Value>(&body)?;

        // Handle JSON-RPC wrapped response
        if let Some(result) = response_json.get("result") {
            let status = serde_json::from_value::<A2aTaskStatus>(result.clone())?;
            Ok(status)
        } else {
            // Try direct deserialization
            let status = serde_json::from_str::<A2aTaskStatus>(&body)?;
            Ok(status)
        }
    }

    /// Check health of the remote A2A server
    pub async fn health_check(&self) -> Result<bool> {
        debug!("Checking health of {}", self.base_url);

        let url = format!("{}/health", self.base_url);
        let response = self
            .http_client
            .get(&url)
            .timeout(self.timeout)
            .send()
            .await
            .map_err(|e| {
                if e.is_timeout() {
                    A2aClientError::Timeout(self.base_url.clone())
                } else if e.is_connect() {
                    A2aClientError::ConnectionRefused(self.base_url.clone())
                } else {
                    A2aClientError::HttpError(e)
                }
            })?;

        Ok(response.status().is_success())
    }
}

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

    #[test]
    fn test_client_creation() {
        let client = A2aClient::new("http://localhost:8003");
        assert_eq!(client.base_url, "http://localhost:8003");
        assert_eq!(client.timeout, Duration::from_secs(30));
    }

    #[test]
    fn test_client_with_custom_timeout() {
        let timeout = Duration::from_secs(60);
        let client = A2aClient::with_timeout("http://localhost:8003", timeout);
        assert_eq!(client.timeout, timeout);
    }
}
