//! Agent registry methods for `AgentIdPClient`.
//!
//! Covers `POST /agents`, `GET /agents`, `GET /agents/{id}`,
//! `PATCH /agents/{id}`, and `DELETE /agents/{id}`.

use crate::client::AgentIdPClient;
use crate::error::AgentIdPError;
use crate::models::{Agent, AgentList, RegisterAgentRequest, UpdateAgentRequest};

impl AgentIdPClient {
    /// Registers a new AI agent identity.
    ///
    /// `POST /agents` → `201 Agent`
    ///
    /// # Errors
    ///
    /// Returns [`AgentIdPError::AuthError`] on 401/403, or
    /// [`AgentIdPError::ApiError`] for other non-2xx responses.
    pub async fn register_agent(
        &self,
        req: RegisterAgentRequest,
    ) -> Result<Agent, AgentIdPError> {
        let auth = self.get_auth_header().await?;
        let url = format!("{}/agents", self.base_url);

        let resp = self
            .http
            .post(&url)
            .header("Authorization", auth)
            .json(&req)
            .send()
            .await?;

        parse_response(resp).await
    }

    /// Retrieves a single agent by its unique identifier.
    ///
    /// `GET /agents/{id}` → `200 Agent`
    ///
    /// # Errors
    ///
    /// Returns [`AgentIdPError::NotFound`] when the agent does not exist.
    pub async fn get_agent(&self, agent_id: &str) -> Result<Agent, AgentIdPError> {
        let auth = self.get_auth_header().await?;
        let url = format!("{}/agents/{}", self.base_url, agent_id);

        let resp = self
            .http
            .get(&url)
            .header("Authorization", auth)
            .send()
            .await?;

        parse_response(resp).await
    }

    /// Returns a paginated list of agents owned by the authenticated client.
    ///
    /// `GET /agents` → `200 AgentList`
    ///
    /// Pass `None` for `page` or `per_page` to use the server defaults.
    pub async fn list_agents(
        &self,
        page: Option<u32>,
        per_page: Option<u32>,
    ) -> Result<AgentList, AgentIdPError> {
        let auth = self.get_auth_header().await?;
        let url = format!("{}/agents", self.base_url);

        let mut query: Vec<(&str, String)> = Vec::new();
        if let Some(p) = page {
            query.push(("page", p.to_string()));
        }
        if let Some(pp) = per_page {
            query.push(("per_page", pp.to_string()));
        }

        let resp = self
            .http
            .get(&url)
            .header("Authorization", auth)
            .query(&query)
            .send()
            .await?;

        parse_response(resp).await
    }

    /// Partially updates an existing agent.
    ///
    /// `PATCH /agents/{id}` → `200 Agent`
    ///
    /// Only fields set to `Some` in `req` are sent to the API.
    pub async fn update_agent(
        &self,
        agent_id: &str,
        req: UpdateAgentRequest,
    ) -> Result<Agent, AgentIdPError> {
        let auth = self.get_auth_header().await?;
        let url = format!("{}/agents/{}", self.base_url, agent_id);

        let resp = self
            .http
            .patch(&url)
            .header("Authorization", auth)
            .json(&req)
            .send()
            .await?;

        parse_response(resp).await
    }

    /// Permanently deletes an agent.
    ///
    /// `DELETE /agents/{id}` → `204 No Content`
    ///
    /// # Errors
    ///
    /// Returns [`AgentIdPError::NotFound`] when the agent does not exist.
    pub async fn delete_agent(&self, agent_id: &str) -> Result<(), AgentIdPError> {
        let auth = self.get_auth_header().await?;
        let url = format!("{}/agents/{}", self.base_url, agent_id);

        let resp = self
            .http
            .delete(&url)
            .header("Authorization", auth)
            .send()
            .await?;

        if resp.status().as_u16() == 204 {
            return Ok(());
        }

        // Reuse parse_response to handle errors; the Ok(Agent) path will never
        // be reached since 204 is handled above, but we need to satisfy the type.
        let _: Agent = parse_response(resp).await?;
        Ok(())
    }
}

/// Converts an HTTP response into `T` or an appropriate `AgentIdPError`.
///
/// Status mapping:
/// - `2xx` → deserialise body as `T`
/// - `401` / `403` → [`AgentIdPError::AuthError`]
/// - `404` → [`AgentIdPError::NotFound`]
/// - `429` → [`AgentIdPError::RateLimited`] (parses `Retry-After` header)
/// - Other non-2xx → [`AgentIdPError::ApiError`]
pub(crate) async fn parse_response<T: serde::de::DeserializeOwned>(
    resp: reqwest::Response,
) -> Result<T, AgentIdPError> {
    let status = resp.status();

    if status.is_success() {
        let value: T = resp.json().await?;
        return Ok(value);
    }

    let status_code = status.as_u16();

    match status_code {
        401 | 403 => {
            let body: serde_json::Value = resp.json().await.unwrap_or(serde_json::Value::Null);
            let msg = extract_message(&body);
            Err(AgentIdPError::AuthError(msg))
        }
        404 => {
            let body: serde_json::Value = resp.json().await.unwrap_or(serde_json::Value::Null);
            let msg = extract_message(&body);
            Err(AgentIdPError::NotFound(msg))
        }
        429 => {
            let retry_after_secs = resp
                .headers()
                .get("Retry-After")
                .and_then(|v| v.to_str().ok())
                .and_then(|s| s.parse::<u64>().ok())
                .unwrap_or(60);
            Err(AgentIdPError::RateLimited { retry_after_secs })
        }
        _ => {
            let body: serde_json::Value = resp.json().await.unwrap_or(serde_json::Value::Null);
            let message = extract_message(&body);
            let code = body
                .get("code")
                .and_then(|v| v.as_str())
                .map(str::to_owned);
            Err(AgentIdPError::ApiError {
                status: status_code,
                message,
                code,
            })
        }
    }
}

/// Extracts a human-readable message from an API error body.
fn extract_message(body: &serde_json::Value) -> String {
    body.get("message")
        .and_then(|v| v.as_str())
        .unwrap_or("unknown error")
        .to_owned()
}
