//! Python bindings for LLM client — create_llm_client(), chat(), chat_with_tools()

use pyo3::prelude::*;
use std::sync::Arc;

use flowgentra_ai::core::llm::{
    create_llm_client, LLMClient, Message, ToolDefinition,
    CachedLLMClient, FallbackLLMClient, RetryLLMClient,
};

use crate::error::to_py_err;
use crate::get_runtime;
use crate::llm::{PyLLMConfig, PyMessage, PyToolDefinition, PyTokenUsage};

// ─── PyLLMClient ────────────────────────────────────────────────────────────

/// An LLM client for sending messages and receiving responses.
///
/// Create from an LLMConfig:
///     config = LLMConfig("openai", "gpt-4", api_key="sk-...")
///     client = LLMClient.from_config(config)
///     response = client.chat([Message.user("Hello!")])
///     print(response.content)
#[pyclass(name = "LLMClient")]
pub struct PyLLMClient {
    pub(crate) inner: Arc<dyn LLMClient>,
}

#[pymethods]
impl PyLLMClient {
    /// Create an LLM client from config.
    #[staticmethod]
    fn from_config(config: &PyLLMConfig) -> PyResult<Self> {
        let client = create_llm_client(&config.inner).map_err(to_py_err)?;
        Ok(PyLLMClient { inner: client })
    }

    /// Send messages and get a response.
    ///
    /// Args:
    ///     messages: List of Message objects
    ///
    /// Returns:
    ///     A Message with the LLM's response
    fn chat(&self, messages: Vec<PyMessage>) -> PyResult<PyMessage> {
        let msgs: Vec<Message> = messages.into_iter().map(|m| m.inner).collect();
        let rt = get_runtime();
        let result = rt.block_on(self.inner.chat(msgs)).map_err(to_py_err)?;
        Ok(PyMessage { inner: result })
    }

    /// Send messages and get a response with token usage stats.
    ///
    /// Returns:
    ///     Tuple of (Message, optional TokenUsage)
    fn chat_with_usage(
        &self,
        py: Python<'_>,
        messages: Vec<PyMessage>,
    ) -> PyResult<(PyMessage, PyObject)> {
        let msgs: Vec<Message> = messages.into_iter().map(|m| m.inner).collect();
        let rt = get_runtime();
        let (msg, usage) = rt
            .block_on(self.inner.chat_with_usage(msgs))
            .map_err(to_py_err)?;
        let py_usage = match usage {
            Some(u) => PyTokenUsage {
                inner: u,
            }
            .into_py(py),
            None => py.None(),
        };
        Ok((PyMessage { inner: msg }, py_usage))
    }

    /// Send messages with tool definitions and get a response (function calling).
    ///
    /// Args:
    ///     messages: List of Message objects
    ///     tools: List of ToolDefinition objects
    ///
    /// Returns:
    ///     A Message (may contain tool_calls)
    fn chat_with_tools(
        &self,
        messages: Vec<PyMessage>,
        tools: Vec<PyToolDefinition>,
    ) -> PyResult<PyMessage> {
        let msgs: Vec<Message> = messages.into_iter().map(|m| m.inner).collect();
        let tool_defs: Vec<ToolDefinition> = tools.into_iter().map(|t| t.inner).collect();
        let rt = get_runtime();
        let result = rt
            .block_on(self.inner.chat_with_tools(msgs, &tool_defs))
            .map_err(to_py_err)?;
        Ok(PyMessage { inner: result })
    }

    /// Wrap this client with a response cache.
    ///
    /// Example:
    ///     cached = client.cached(max_entries=1000)
    #[pyo3(signature = (max_entries=100))]
    fn cached(&self, max_entries: usize) -> Self {
        let cached = CachedLLMClient::new(self.inner.clone()).with_max_entries(max_entries);
        PyLLMClient {
            inner: Arc::new(cached),
        }
    }

    /// Create a fallback client that tries this client first, then falls back.
    ///
    /// Example:
    ///     robust = primary.with_fallback(secondary)
    fn with_fallback(&self, fallback: &PyLLMClient) -> Self {
        let fb = FallbackLLMClient::new(self.inner.clone())
            .with_fallback(fallback.inner.clone());
        PyLLMClient {
            inner: Arc::new(fb),
        }
    }

    /// Wrap this client with automatic retry and exponential backoff.
    ///
    /// Example:
    ///     retrying = client.with_retry(max_retries=3)
    #[pyo3(signature = (max_retries=3))]
    fn with_retry(&self, max_retries: u32) -> Self {
        let retry = RetryLLMClient::new(self.inner.clone(), max_retries);
        PyLLMClient {
            inner: Arc::new(retry),
        }
    }

    fn __repr__(&self) -> String {
        "LLMClient(...)".to_string()
    }
}
