//! Anthropic Direct API adapter.

use std::sync::Arc;

use async_trait::async_trait;
use secrecy::{ExposeSecret, SecretString};
use tokio::sync::RwLock;

use super::config::{BetaFeature, ProviderConfig};
use super::traits::ProviderAdapter;
use crate::auth::{Credential, CredentialProvider, OAuthConfig};
use crate::client::messages::{
    CountTokensRequest, CountTokensResponse, CreateMessageRequest, ErrorResponse,
};
use crate::types::ApiResponse;
use crate::{Error, Result};

const BASE_URL: &str = "https://api.anthropic.com";

#[derive(Clone)]
enum AuthMethod {
    ApiKey(SecretString),
    OAuth {
        token: SecretString,
        config: OAuthConfig,
    },
}

impl std::fmt::Debug for AuthMethod {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::ApiKey(_) => f.debug_tuple("ApiKey").field(&"[redacted]").finish(),
            Self::OAuth { config, .. } => f
                .debug_struct("OAuth")
                .field("token", &"[redacted]")
                .field("config", config)
                .finish(),
        }
    }
}

impl AuthMethod {
    fn from_credential(credential: &Credential, oauth_config: Option<OAuthConfig>) -> Self {
        match credential {
            Credential::ApiKey(key) => Self::ApiKey(key.clone()),
            Credential::OAuth(oauth) => Self::OAuth {
                token: oauth.access_token.clone(),
                config: oauth_config.unwrap_or_default(),
            },
        }
    }

    fn update_token(&mut self, credential: &Credential) {
        match credential {
            Credential::ApiKey(key) => *self = Self::ApiKey(key.clone()),
            Credential::OAuth(oauth) => {
                if let Self::OAuth { token, .. } = self {
                    *token = oauth.access_token.clone();
                } else {
                    *self = Self::OAuth {
                        token: oauth.access_token.clone(),
                        config: OAuthConfig::default(),
                    };
                }
            }
        }
    }
}

pub struct AnthropicAdapter {
    config: ProviderConfig,
    base_url: String,
    auth: RwLock<AuthMethod>,
    credential_provider: Option<Arc<dyn CredentialProvider>>,
}

impl std::fmt::Debug for AnthropicAdapter {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AnthropicAdapter")
            .field("config", &self.config)
            .field("base_url", &self.base_url)
            .finish()
    }
}

impl AnthropicAdapter {
    pub fn new(config: ProviderConfig) -> Self {
        Self {
            config,
            base_url: Self::base_url_from_env(),
            auth: RwLock::new(AuthMethod::ApiKey(Self::api_key_from_env())),
            credential_provider: None,
        }
    }

    fn api_key_from_env() -> SecretString {
        SecretString::from(std::env::var("ANTHROPIC_API_KEY").unwrap_or_default())
    }

    fn base_url_from_env() -> String {
        std::env::var("ANTHROPIC_BASE_URL").unwrap_or_else(|_| BASE_URL.into())
    }

    pub fn from_credential(
        config: ProviderConfig,
        credential: &Credential,
        oauth_config: Option<OAuthConfig>,
    ) -> Self {
        Self {
            config,
            base_url: Self::base_url_from_env(),
            auth: RwLock::new(AuthMethod::from_credential(credential, oauth_config)),
            credential_provider: None,
        }
    }

    pub fn from_credential_provider(
        config: ProviderConfig,
        credential: &Credential,
        oauth_config: Option<OAuthConfig>,
        provider: Arc<dyn CredentialProvider>,
    ) -> Self {
        Self {
            config,
            base_url: Self::base_url_from_env(),
            auth: RwLock::new(AuthMethod::from_credential(credential, oauth_config)),
            credential_provider: Some(provider),
        }
    }

    pub fn api_key(self, key: impl Into<String>) -> Self {
        Self {
            auth: RwLock::new(AuthMethod::ApiKey(SecretString::from(key.into()))),
            ..self
        }
    }

    pub fn base_url(mut self, url: impl Into<String>) -> Self {
        self.base_url = url.into();
        self
    }

    fn build_endpoint_url(&self, auth: &AuthMethod, endpoint: &str) -> String {
        match auth {
            AuthMethod::OAuth { config, .. } => config.build_url(&self.base_url, endpoint),
            AuthMethod::ApiKey(_) => format!("{}{}", self.base_url, endpoint),
        }
    }

    fn build_headers(
        &self,
        req: reqwest::RequestBuilder,
        auth: &AuthMethod,
    ) -> reqwest::RequestBuilder {
        let mut r = match auth {
            AuthMethod::ApiKey(key) => req
                .header("x-api-key", key.expose_secret())
                .header("anthropic-version", &self.config.api_version)
                .header("content-type", "application/json"),
            AuthMethod::OAuth { token, config } => config.apply_headers(
                req,
                token.expose_secret(),
                &self.config.api_version,
                &self.config.beta,
            ),
        };

        if let AuthMethod::ApiKey(_) = auth
            && let Some(beta) = self.config.beta.header_value()
        {
            r = r.header("anthropic-beta", beta);
        }

        for (k, v) in &self.config.extra_headers {
            r = r.header(k.as_str(), v.as_str());
        }

        r
    }

    fn prepare_request_with_auth(
        &self,
        mut request: CreateMessageRequest,
        auth: &AuthMethod,
    ) -> CreateMessageRequest {
        if let AuthMethod::OAuth { .. } = auth {
            use crate::prompts::CLI_IDENTITY;

            let mut blocks = vec![crate::types::SystemBlock::uncached(CLI_IDENTITY)];

            match &request.system {
                Some(crate::types::SystemPrompt::Text(existing)) if !existing.is_empty() => {
                    if !existing.starts_with(CLI_IDENTITY) {
                        blocks.push(crate::types::SystemBlock::uncached(existing));
                    }
                }
                Some(crate::types::SystemPrompt::Blocks(existing_blocks))
                    if !existing_blocks.is_empty() =>
                {
                    for block in existing_blocks {
                        if block.text != CLI_IDENTITY {
                            blocks.push(block.clone());
                        }
                    }
                }
                _ => {}
            }

            request.system = Some(crate::types::SystemPrompt::Blocks(blocks));
        }
        request
    }

    async fn do_refresh(&self) -> Result<()> {
        if let Some(ref provider) = self.credential_provider {
            let new_credential = provider.refresh().await?;
            self.auth.write().await.update_token(&new_credential);
        }
        Ok(())
    }

    pub fn credential_provider(&self) -> Option<&Arc<dyn CredentialProvider>> {
        self.credential_provider.as_ref()
    }

    fn needs_structured_outputs(request: &CreateMessageRequest) -> bool {
        let has_strict_tools = request
            .tools
            .as_ref()
            .is_some_and(|tools| tools.iter().any(|t| t.is_strict()));
        request.output_format.is_some() || has_strict_tools
    }

    fn apply_structured_outputs_header(
        &self,
        req: reqwest::RequestBuilder,
        needs: bool,
    ) -> reqwest::RequestBuilder {
        if needs && !self.config.beta.has(BetaFeature::StructuredOutputs) {
            req.header(
                "anthropic-beta",
                BetaFeature::StructuredOutputs.header_value(),
            )
        } else {
            req
        }
    }

    async fn check_error_response(response: reqwest::Response) -> Result<reqwest::Response> {
        if !response.status().is_success() {
            let status = response.status().as_u16();
            let error: ErrorResponse = response.json().await?;
            return Err(error.into_error(status));
        }
        Ok(response)
    }
}

#[async_trait]
impl ProviderAdapter for AnthropicAdapter {
    fn config(&self) -> &ProviderConfig {
        &self.config
    }

    fn name(&self) -> &'static str {
        "anthropic"
    }

    fn base_url(&self) -> &str {
        &self.base_url
    }

    async fn build_url(&self, _model: &str, _stream: bool) -> String {
        let auth = self.auth.read().await;
        self.build_endpoint_url(&auth, "/v1/messages")
    }

    async fn transform_request(&self, request: CreateMessageRequest) -> Result<serde_json::Value> {
        let auth = self.auth.read().await;
        let prepared = self.prepare_request_with_auth(request, &auth);
        serde_json::to_value(&prepared).map_err(|e| Error::InvalidRequest(e.to_string()))
    }

    async fn apply_auth_headers(&self, req: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
        let auth = self.auth.read().await;
        self.build_headers(req, &auth)
    }

    async fn send(
        &self,
        http: &reqwest::Client,
        request: CreateMessageRequest,
    ) -> Result<ApiResponse> {
        let needs = Self::needs_structured_outputs(&request);

        let (url, body) = {
            let auth = self.auth.read().await;
            let url = self.build_endpoint_url(&auth, "/v1/messages");
            let prepared = self.prepare_request_with_auth(request, &auth);
            (url, serde_json::to_value(&prepared)?)
        };

        let req = self.apply_auth_headers(http.post(&url)).await;
        let req = self.apply_structured_outputs_header(req, needs);
        let response = req.json(&body).send().await?;
        let response = Self::check_error_response(response).await?;

        let json: serde_json::Value = response.json().await?;
        self.transform_response(json)
    }

    async fn send_stream(
        &self,
        http: &reqwest::Client,
        mut request: CreateMessageRequest,
    ) -> Result<reqwest::Response> {
        let needs = Self::needs_structured_outputs(&request);
        request.stream = Some(true);

        let (url, body) = {
            let auth = self.auth.read().await;
            let url = self.build_endpoint_url(&auth, "/v1/messages");
            let prepared = self.prepare_request_with_auth(request, &auth);
            (url, serde_json::to_value(&prepared)?)
        };

        let req = self.apply_auth_headers(http.post(&url)).await;
        let req = self.apply_structured_outputs_header(req, needs);
        let response = req.json(&body).send().await?;
        Self::check_error_response(response).await
    }

    fn supports_credential_refresh(&self) -> bool {
        self.credential_provider
            .as_ref()
            .is_some_and(|p| p.supports_refresh())
    }

    async fn ensure_fresh_credentials(&self) -> Result<()> {
        if let Some(ref provider) = self.credential_provider {
            let current = provider.resolve().await?;
            if current.needs_refresh() && provider.supports_refresh() {
                let new_cred = provider.refresh().await?;
                self.auth.write().await.update_token(&new_cred);
            }
        }
        Ok(())
    }

    async fn refresh_credentials(&self) -> Result<()> {
        self.do_refresh().await
    }

    async fn count_tokens(
        &self,
        http: &reqwest::Client,
        request: CountTokensRequest,
    ) -> Result<CountTokensResponse> {
        let (url, body) = {
            let auth = self.auth.read().await;
            let url = self.build_endpoint_url(&auth, "/v1/messages/count_tokens");
            (url, serde_json::to_value(&request)?)
        };

        let response = self
            .apply_auth_headers(http.post(&url))
            .await
            .json(&body)
            .send()
            .await?;
        let response = Self::check_error_response(response).await?;

        Ok(response.json().await?)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::client::adapter::{BetaConfig, BetaFeature, ModelConfig};
    use crate::types::Message;

    #[tokio::test]
    async fn test_build_url() {
        let adapter = AnthropicAdapter::new(ProviderConfig::new(ModelConfig::anthropic()));
        let url = adapter.build_url("claude-sonnet-4-5", false).await;
        assert!(url.contains("/v1/messages"));
    }

    #[tokio::test]
    async fn test_transform_request() {
        let adapter = AnthropicAdapter::new(ProviderConfig::new(ModelConfig::anthropic()));
        let request = CreateMessageRequest::new("claude-sonnet-4-5", vec![Message::user("Hello")]);
        let body = adapter.transform_request(request).await.unwrap();
        assert!(body.get("model").is_some());
        assert!(body.get("messages").is_some());
    }

    #[tokio::test]
    async fn test_oauth_url_params() {
        let credential = Credential::oauth("test-token");
        let adapter = AnthropicAdapter::from_credential(
            ProviderConfig::new(ModelConfig::anthropic()),
            &credential,
            None,
        );
        let url = adapter.build_url("model", false).await;
        assert!(url.contains("beta=true"));
    }

    #[tokio::test]
    async fn test_oauth_system_prompt() {
        let credential = Credential::oauth("test-token");
        let adapter = AnthropicAdapter::from_credential(
            ProviderConfig::new(ModelConfig::anthropic()),
            &credential,
            None,
        );
        let request = CreateMessageRequest::new("model", vec![Message::user("Hi")]);
        let body = adapter.transform_request(request).await.unwrap();
        let system_blocks = body
            .get("system")
            .and_then(|v| v.as_array())
            .expect("OAuth should produce system blocks");
        let first_text = system_blocks[0]
            .get("text")
            .and_then(|v| v.as_str())
            .unwrap_or("");
        assert!(first_text.contains("Claude Code"));
    }

    #[test]
    fn test_api_key_with_beta() {
        let config = ProviderConfig::new(ModelConfig::anthropic())
            .beta(BetaFeature::InterleavedThinking)
            .beta(BetaFeature::ContextManagement);

        let adapter = AnthropicAdapter::new(config);
        assert!(adapter.config.beta.has(BetaFeature::InterleavedThinking));
        assert!(adapter.config.beta.has(BetaFeature::ContextManagement));
    }

    #[test]
    fn test_api_key_with_custom_beta() {
        let beta = BetaConfig::new().custom("new-feature-2026-01-01");
        let config = ProviderConfig::new(ModelConfig::anthropic()).beta_config(beta);

        let adapter = AnthropicAdapter::new(config);
        let header = adapter.config.beta.header_value().unwrap();
        assert!(header.contains("new-feature-2026-01-01"));
    }

    #[tokio::test]
    async fn test_oauth_prepends_cli_identity_to_system_prompt() {
        let credential = Credential::oauth("test-token");
        let adapter = AnthropicAdapter::from_credential(
            ProviderConfig::new(ModelConfig::anthropic()),
            &credential,
            None,
        );

        let request = CreateMessageRequest::new("model", vec![Message::user("Hi")])
            .system("Custom user system prompt");

        let body = adapter.transform_request(request).await.unwrap();
        let system_blocks = body
            .get("system")
            .and_then(|v| v.as_array())
            .expect("OAuth should produce system blocks");

        assert!(system_blocks.len() >= 2, "Should have at least 2 blocks");

        let first_text = system_blocks[0]
            .get("text")
            .and_then(|v| v.as_str())
            .unwrap_or("");
        assert!(
            first_text.starts_with("You are Claude Code"),
            "First block should be Claude Code identity: {}",
            first_text
        );

        let second_text = system_blocks[1]
            .get("text")
            .and_then(|v| v.as_str())
            .unwrap_or("");
        assert_eq!(
            second_text, "Custom user system prompt",
            "Second block should preserve original"
        );
    }

    #[tokio::test]
    async fn test_api_key_does_not_modify_system_prompt() {
        let adapter =
            AnthropicAdapter::new(ProviderConfig::new(ModelConfig::anthropic())).api_key("sk-test");

        // Create request with existing system prompt
        let request = CreateMessageRequest::new("model", vec![Message::user("Hi")])
            .system("Custom user system prompt");

        let body = adapter.transform_request(request).await.unwrap();
        let system = body.get("system").and_then(|v| v.as_str()).unwrap_or("");

        // For API key auth, system prompt should be unchanged
        assert_eq!(
            system, "Custom user system prompt",
            "API key auth should not modify system prompt"
        );
        // Should NOT contain CLI identity
        assert!(
            !system.contains("Claude Code"),
            "API key auth should not add CLI identity: {}",
            system
        );
    }
}
