use std::sync::Arc;

use crate::auth::GoogleAuth;
use crate::error::{GoogleApiErrorBody, GoogleError};

/// HTTP client wrapper that injects Google Bearer auth on every request.
#[derive(Clone)]
pub struct GoogleApiClient {
    auth: Arc<dyn GoogleAuth>,
    http: reqwest::Client,
}

impl GoogleApiClient {
    pub fn new(auth: Arc<dyn GoogleAuth>) -> Self {
        Self {
            auth,
            http: reqwest::Client::new(),
        }
    }

    pub fn with_http(mut self, http: reqwest::Client) -> Self {
        self.http = http;
        self
    }

    pub async fn get(
        &self,
        url: &str,
        query: &[(&str, &str)],
    ) -> Result<serde_json::Value, GoogleError> {
        let token = self.auth.get_access_token().await?;
        let resp = self
            .http
            .get(url)
            .bearer_auth(&token)
            .query(query)
            .send()
            .await?;
        self.handle_response(resp).await
    }

    pub async fn post(
        &self,
        url: &str,
        body: &serde_json::Value,
    ) -> Result<serde_json::Value, GoogleError> {
        let token = self.auth.get_access_token().await?;
        let resp = self
            .http
            .post(url)
            .bearer_auth(&token)
            .json(body)
            .send()
            .await?;
        self.handle_response(resp).await
    }

    pub async fn post_with_query(
        &self,
        url: &str,
        query: &[(&str, &str)],
        body: &serde_json::Value,
    ) -> Result<serde_json::Value, GoogleError> {
        let token = self.auth.get_access_token().await?;
        let resp = self
            .http
            .post(url)
            .bearer_auth(&token)
            .query(query)
            .json(body)
            .send()
            .await?;
        self.handle_response(resp).await
    }

    pub async fn put(
        &self,
        url: &str,
        query: &[(&str, &str)],
        body: &serde_json::Value,
    ) -> Result<serde_json::Value, GoogleError> {
        let token = self.auth.get_access_token().await?;
        let resp = self
            .http
            .put(url)
            .bearer_auth(&token)
            .query(query)
            .json(body)
            .send()
            .await?;
        self.handle_response(resp).await
    }

    async fn handle_response(
        &self,
        resp: reqwest::Response,
    ) -> Result<serde_json::Value, GoogleError> {
        let status = resp.status().as_u16();
        if status >= 400 {
            let body = resp.text().await.unwrap_or_default();
            // Try to parse Google's error JSON
            if let Ok(err_body) = serde_json::from_str::<GoogleApiErrorBody>(&body) {
                return Err(GoogleError::api(err_body.error.code, err_body.error.message));
            }
            return Err(GoogleError::api(status, body));
        }
        let json: serde_json::Value = resp.json().await?;
        Ok(json)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::auth::MockGoogleAuth;
    use wiremock::matchers::{header, method, path, query_param};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    async fn setup() -> (MockServer, GoogleApiClient) {
        let server = MockServer::start().await;
        let auth = Arc::new(MockGoogleAuth::new("test-token-123"));
        let client = GoogleApiClient::new(auth);
        (server, client)
    }

    #[tokio::test]
    async fn get_injects_bearer_header() {
        let (server, client) = setup().await;

        Mock::given(method("GET"))
            .and(path("/test"))
            .and(header("Authorization", "Bearer test-token-123"))
            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({"ok": true})))
            .expect(1)
            .mount(&server)
            .await;

        let result = client
            .get(&format!("{}/test", server.uri()), &[])
            .await
            .unwrap();
        assert_eq!(result["ok"], true);
    }

    #[tokio::test]
    async fn post_sends_json_body() {
        let (server, client) = setup().await;

        Mock::given(method("POST"))
            .and(path("/create"))
            .and(header("Authorization", "Bearer test-token-123"))
            .respond_with(
                ResponseTemplate::new(200).set_body_json(serde_json::json!({"id": "new-123"})),
            )
            .expect(1)
            .mount(&server)
            .await;

        let body = serde_json::json!({"name": "test"});
        let result = client
            .post(&format!("{}/create", server.uri()), &body)
            .await
            .unwrap();
        assert_eq!(result["id"], "new-123");
    }

    #[tokio::test]
    async fn maps_google_api_error() {
        let (server, client) = setup().await;

        Mock::given(method("GET"))
            .and(path("/fail"))
            .respond_with(ResponseTemplate::new(403).set_body_json(serde_json::json!({
                "error": {
                    "code": 403,
                    "message": "Insufficient permissions"
                }
            })))
            .expect(1)
            .mount(&server)
            .await;

        let err = client
            .get(&format!("{}/fail", server.uri()), &[])
            .await
            .unwrap_err();
        match err {
            GoogleError::Api { status, message } => {
                assert_eq!(status, 403);
                assert_eq!(message, "Insufficient permissions");
            }
            _ => panic!("Expected GoogleError::Api, got {:?}", err),
        }
    }

    #[tokio::test]
    async fn get_passes_query_params() {
        let (server, client) = setup().await;

        Mock::given(method("GET"))
            .and(path("/search"))
            .and(query_param("q", "test"))
            .and(query_param("limit", "10"))
            .respond_with(
                ResponseTemplate::new(200).set_body_json(serde_json::json!({"results": []})),
            )
            .expect(1)
            .mount(&server)
            .await;

        let result = client
            .get(
                &format!("{}/search", server.uri()),
                &[("q", "test"), ("limit", "10")],
            )
            .await
            .unwrap();
        assert_eq!(result["results"], serde_json::json!([]));
    }
}
