//! Anthropic service — public API for Anthropic API-key management and model listing.
//!
//! Exposes functions to check, store, and use the Anthropic API key, and to
//! list available models. HTTP handlers call these functions instead of
//! talking to `llm::chat` or making HTTP requests directly.
//!
//! Conventions: `docs/architecture/service-modules.md`

pub(super) mod io;

use crate::store::StoreOps;
use serde::{Deserialize, Serialize};

const ANTHROPIC_MODELS_URL: &str = "https://api.anthropic.com/v1/models";

// ── Error type ────────────────────────────────────────────────────────────────

/// Typed errors returned by `service::anthropic` functions.
///
/// HTTP handlers map these to status codes:
/// - [`Error::Validation`] → 400 Bad Request
/// - [`Error::UpstreamApi`] → 502 Bad Gateway (or 400 for invalid keys)
/// - [`Error::Internal`] → 500 Internal Server Error
#[derive(Debug)]
pub enum Error {
    /// The request was invalid (e.g. missing, empty, or malformed API key).
    Validation(String),
    /// The upstream Anthropic API returned an error or was unreachable.
    UpstreamApi(String),
    /// An internal error occurred (JSON parse failure, store I/O error, etc.).
    Internal(String),
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Validation(msg) => write!(f, "Validation error: {msg}"),
            Self::UpstreamApi(msg) => write!(f, "Upstream API error: {msg}"),
            Self::Internal(msg) => write!(f, "Internal error: {msg}"),
        }
    }
}

// ── Types ─────────────────────────────────────────────────────────────────────

/// A summary of an Anthropic model as returned by the `/v1/models` endpoint.
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct ModelSummary {
    pub id: String,
    pub context_window: u64,
}

/// Raw response shape from the Anthropic `/v1/models` endpoint.
#[derive(Deserialize)]
pub(super) struct ModelsResponse {
    pub data: Vec<ModelInfo>,
}

/// A single model entry in the Anthropic API response.
#[derive(Deserialize)]
pub(super) struct ModelInfo {
    pub id: String,
    pub context_window: u64,
}

// ── Public API ────────────────────────────────────────────────────────────────

/// Return whether a non-empty Anthropic API key is currently stored.
pub fn get_api_key_exists(store: &dyn StoreOps) -> Result<bool, Error> {
    Ok(io::api_key_exists(store))
}

/// Read the stored Anthropic API key.
///
/// Returns [`Error::Validation`] when the key is absent, empty, or not a string.
pub fn get_api_key(store: &dyn StoreOps) -> Result<String, Error> {
    io::get_api_key(store)
}

/// Store or replace the Anthropic API key.
pub fn set_api_key(store: &dyn StoreOps, api_key: String) -> Result<(), Error> {
    io::save_api_key(store, &api_key).map_err(Error::Internal)
}

/// List available Anthropic models from the production endpoint.
pub async fn list_models(store: &dyn StoreOps) -> Result<Vec<ModelSummary>, Error> {
    list_models_from(store, ANTHROPIC_MODELS_URL).await
}

/// List available Anthropic models from `url` (injectable for tests).
pub async fn list_models_from(store: &dyn StoreOps, url: &str) -> Result<Vec<ModelSummary>, Error> {
    let api_key = get_api_key(store)?;
    io::fetch_models(&api_key, url).await
}

/// Parse a raw JSON string from the Anthropic `/v1/models` endpoint into model summaries.
///
/// Pure function for unit testing; production code uses [`list_models`].
#[cfg(test)]
pub fn parse_models_response(json: &str) -> Result<Vec<ModelSummary>, Error> {
    let response: ModelsResponse = serde_json::from_str(json)
        .map_err(|e| Error::Internal(format!("Failed to parse models response: {e}")))?;
    Ok(response
        .data
        .into_iter()
        .map(|m| ModelSummary {
            id: m.id,
            context_window: m.context_window,
        })
        .collect())
}

// ── Tests ─────────────────────────────────────────────────────────────────────

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

    // Pure unit tests for response parsing — no tempdir, no network.

    #[test]
    fn parse_models_response_parses_single_model() {
        let json = r#"{"data":[{"id":"claude-opus-4-5","context_window":200000}]}"#;
        let models = parse_models_response(json).unwrap();
        assert_eq!(models.len(), 1);
        assert_eq!(models[0].id, "claude-opus-4-5");
        assert_eq!(models[0].context_window, 200000);
    }

    #[test]
    fn parse_models_response_parses_multiple_models() {
        let json = r#"{"data":[
            {"id":"claude-opus-4-5","context_window":200000},
            {"id":"claude-haiku-4-5-20251001","context_window":100000}
        ]}"#;
        let models = parse_models_response(json).unwrap();
        assert_eq!(models.len(), 2);
        assert_eq!(models[0].id, "claude-opus-4-5");
        assert_eq!(models[1].context_window, 100000);
    }

    #[test]
    fn parse_models_response_returns_empty_for_empty_data() {
        let json = r#"{"data":[]}"#;
        let models = parse_models_response(json).unwrap();
        assert!(models.is_empty());
    }

    #[test]
    fn parse_models_response_returns_internal_error_for_invalid_json() {
        let result = parse_models_response("not json at all");
        assert!(matches!(result, Err(Error::Internal(_))));
    }

    #[test]
    fn parse_models_response_returns_error_for_missing_data_field() {
        let result = parse_models_response(r#"{"wrong_field":[]}"#);
        assert!(matches!(result, Err(Error::Internal(_))));
    }

    #[test]
    fn error_display_validation() {
        let e = Error::Validation("no key".to_string());
        assert!(e.to_string().contains("no key"));
    }

    #[test]
    fn error_display_upstream_api() {
        let e = Error::UpstreamApi("500 Server Error".to_string());
        assert!(e.to_string().contains("500 Server Error"));
    }

    #[test]
    fn error_display_internal() {
        let e = Error::Internal("parse failed".to_string());
        assert!(e.to_string().contains("parse failed"));
    }
}
