//! Anthropic API client with OAuth support
//!
//! Handles authenticated requests to the Anthropic API using OAuth tokens.

use bytes::Bytes;
use futures::Stream;
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
use std::pin::Pin;
use std::sync::Arc;
use tokio::sync::RwLock;

use super::oauth::{AnthropicOAuth, OAuthTokens};
use super::transform::RequestTransformer;
use super::{REQUIRED_BETAS, USER_AGENT as CLAUDE_USER_AGENT};
use crate::error::{Error, Result};

/// Anthropic API client with OAuth token management
pub struct AnthropicClient {
    http: reqwest::Client,
    oauth: AnthropicOAuth,
    tokens: Arc<RwLock<OAuthTokens>>,
}

impl AnthropicClient {
    /// Create a new client with OAuth tokens
    pub fn new(tokens: OAuthTokens) -> Self {
        Self {
            http: reqwest::Client::new(),
            oauth: AnthropicOAuth::new(),
            tokens: Arc::new(RwLock::new(tokens)),
        }
    }

    /// Get a valid access token, refreshing if necessary
    async fn get_access_token(&self) -> Result<String> {
        // Check if current token is valid
        {
            let tokens = self.tokens.read().await;
            if !tokens.is_expired() {
                return Ok(tokens.access_token.clone());
            }
        }

        // Token expired, refresh it
        let new_tokens = {
            let tokens = self.tokens.read().await;
            self.oauth.refresh(&tokens.refresh_token).await?
        };

        // Update stored tokens
        {
            let mut tokens = self.tokens.write().await;
            *tokens = new_tokens.clone();
        }

        Ok(new_tokens.access_token)
    }

    /// Build headers for OAuth request
    fn build_headers(&self, access_token: &str) -> HeaderMap {
        let mut headers = HeaderMap::new();

        // Authorization
        headers.insert(
            AUTHORIZATION,
            HeaderValue::from_str(&format!("Bearer {}", access_token)).unwrap(),
        );

        // User-Agent (mimic Claude CLI)
        headers.insert(
            USER_AGENT,
            HeaderValue::from_static(CLAUDE_USER_AGENT),
        );

        // Content-Type
        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));

        // Anthropic version
        headers.insert(
            "anthropic-version",
            HeaderValue::from_static("2023-06-01"),
        );

        // Beta headers
        let betas = REQUIRED_BETAS.join(",");
        headers.insert(
            "anthropic-beta",
            HeaderValue::from_str(&betas).unwrap(),
        );

        // Additional Claude CLI headers
        headers.insert("x-app", HeaderValue::from_static("cli"));

        headers
    }

    /// Send a chat completion request
    ///
    /// Automatically handles:
    /// - Token refresh if expired
    /// - Header injection (User-Agent, beta flags)
    /// - Tool name prefixing
    /// - URL modification (?beta=true)
    pub async fn messages(&self, body: &str) -> Result<String> {
        let access_token = self.get_access_token().await?;
        let headers = self.build_headers(&access_token);

        // Transform request body
        let transformed_body = RequestTransformer::transform_request(body)
            .unwrap_or_else(|| body.to_string());

        let response = self
            .http
            .post("https://api.anthropic.com/v1/messages?beta=true")
            .headers(headers)
            .body(transformed_body)
            .send()
            .await?;

        if !response.status().is_success() {
            let status = response.status();
            let text = response.text().await.unwrap_or_default();
            return Err(Error::OAuth(format!(
                "API request failed: {} - {}",
                status, text
            )));
        }

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

        // Transform response (unprefix tool names)
        Ok(RequestTransformer::transform_response(&text))
    }

    /// Send a streaming chat completion request
    ///
    /// Returns a stream of response chunks with tool names unprefixed.
    pub async fn messages_stream(
        &self,
        body: &str,
    ) -> Result<Pin<Box<dyn Stream<Item = Result<Bytes>> + Send>>> {
        let access_token = self.get_access_token().await?;
        let headers = self.build_headers(&access_token);

        // Transform request body
        let transformed_body = RequestTransformer::transform_request(body)
            .unwrap_or_else(|| body.to_string());

        let response = self
            .http
            .post("https://api.anthropic.com/v1/messages?beta=true")
            .headers(headers)
            .body(transformed_body)
            .send()
            .await?;

        if !response.status().is_success() {
            let status = response.status();
            let text = response.text().await.unwrap_or_default();
            return Err(Error::OAuth(format!(
                "API request failed: {} - {}",
                status, text
            )));
        }

        // Create a stream that transforms each chunk
        let byte_stream = response.bytes_stream();

        use futures::StreamExt;
        let transformed_stream = byte_stream.map(|chunk_result| {
            chunk_result
                .map(|chunk| {
                    let text = String::from_utf8_lossy(&chunk);
                    let transformed = RequestTransformer::transform_response(&text);
                    Bytes::from(transformed)
                })
                .map_err(Error::from)
        });

        Ok(Box::pin(transformed_stream))
    }

    /// Get current tokens (for persistence)
    pub async fn tokens(&self) -> OAuthTokens {
        self.tokens.read().await.clone()
    }

    /// Update tokens (e.g., after loading from storage)
    pub async fn set_tokens(&self, tokens: OAuthTokens) {
        let mut current = self.tokens.write().await;
        *current = tokens;
    }
}

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

    fn dummy_tokens() -> OAuthTokens {
        OAuthTokens {
            access_token: "test_access".to_string(),
            refresh_token: "test_refresh".to_string(),
            expires_at: u64::MAX,
        }
    }

    #[test]
    fn test_build_headers() {
        let client = AnthropicClient::new(dummy_tokens());
        let headers = client.build_headers("test_token");

        assert!(headers.contains_key(AUTHORIZATION));
        assert!(headers.contains_key(USER_AGENT));
        assert!(headers.contains_key("anthropic-beta"));

        let auth = headers.get(AUTHORIZATION).unwrap().to_str().unwrap();
        assert!(auth.starts_with("Bearer "));

        let ua = headers.get(USER_AGENT).unwrap().to_str().unwrap();
        assert!(ua.contains("claude-cli"));
    }
}
