//! Shared Google service account authentication for Calendar and Gmail APIs

use base64::{Engine as _, engine::general_purpose};
use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use thiserror::Error;
use tokio::sync::RwLock;

const GOOGLE_TOKEN_URL: &str = "https://oauth2.googleapis.com/token";

/// Google Calendar API scope
pub const GOOGLE_CALENDAR_SCOPE: &str = "https://www.googleapis.com/auth/calendar";

/// Gmail send scope
pub const GMAIL_SEND_SCOPE: &str = "https://www.googleapis.com/auth/gmail.send";

/// Gmail full access scope (for read/delete operations)
pub const GMAIL_FULL_SCOPE: &str = "https://mail.google.com/";

#[derive(Debug, Error)]
pub enum GoogleAuthError {
    #[error("Failed to read service account key file: {0}")]
    KeyFileRead(String),
    #[error("Invalid base64 service account key: {0}")]
    InvalidBase64(String),
    #[error("Invalid UTF-8 in service account key: {0}")]
    InvalidUtf8(String),
    #[error("Invalid service account key format: {0}")]
    InvalidKeyFormat(String),
    #[error("Invalid private key: {0}")]
    InvalidPrivateKey(String),
    #[error("Failed to create JWT: {0}")]
    JwtCreation(String),
    #[error("Token exchange failed: {0}")]
    TokenExchange(String),
    #[error("Google service account key not configured")]
    NotConfigured,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ServiceAccountKey {
    #[serde(rename = "type")]
    pub key_type: String,
    pub project_id: String,
    pub private_key_id: String,
    pub private_key: String,
    pub client_email: String,
    pub client_id: String,
    pub auth_uri: String,
    pub token_uri: String,
    pub auth_provider_x509_cert_url: String,
    pub client_x509_cert_url: String,
}

#[derive(Debug, Serialize)]
struct JwtClaims {
    iss: String,
    scope: String,
    aud: String,
    exp: usize,
    iat: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    sub: Option<String>,
}

#[derive(Debug, Deserialize)]
struct TokenResponse {
    access_token: String,
    #[allow(dead_code)]
    token_type: String,
    expires_in: u32,
}

#[derive(Debug, Clone)]
struct CachedToken {
    access_token: String,
    expires_at: u64,
    scope: String,
    sub: Option<String>,
}

/// Shared Google authentication service with token caching
pub struct GoogleAuthService {
    client: Client,
    service_account: ServiceAccountKey,
    /// Cache tokens per (scope, sub) combination
    cached_tokens: Arc<RwLock<Vec<CachedToken>>>,
}

impl GoogleAuthService {
    /// Create a new Google auth service from a service account key string
    /// The key can be: a file path, raw JSON, or base64-encoded JSON
    pub fn new(service_account_key: &str) -> Result<Self, GoogleAuthError> {
        let service_account = parse_service_account_key(service_account_key)?;

        Ok(Self {
            client: Client::new(),
            service_account,
            cached_tokens: Arc::new(RwLock::new(Vec::new())),
        })
    }

    /// Get an access token for the specified scope and optional subject (for impersonation)
    pub async fn get_access_token(
        &self,
        scope: &str,
        sub: Option<&str>,
    ) -> Result<String, GoogleAuthError> {
        // Check cache first
        {
            let tokens = self.cached_tokens.read().await;
            let now = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs();

            for cached in tokens.iter() {
                if cached.scope == scope
                    && cached.sub.as_deref() == sub
                    && cached.expires_at > now + 60
                {
                    return Ok(cached.access_token.clone());
                }
            }
        }

        // Get a new token
        let jwt = self.create_jwt(scope, sub)?;
        let token_response = self.exchange_jwt_for_token(&jwt).await?;

        // Cache the new token
        let expires_at = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs()
            + token_response.expires_in as u64;

        let new_token = CachedToken {
            access_token: token_response.access_token.clone(),
            expires_at,
            scope: scope.to_string(),
            sub: sub.map(String::from),
        };

        // Update cache - remove old token for same scope/sub if exists
        {
            let mut tokens = self.cached_tokens.write().await;
            tokens.retain(|t| !(t.scope == scope && t.sub.as_deref() == sub));
            tokens.push(new_token);
        }

        Ok(token_response.access_token)
    }

    /// Get the service account email (useful for "from" addresses)
    pub fn service_account_email(&self) -> &str {
        &self.service_account.client_email
    }

    fn create_jwt(&self, scope: &str, sub: Option<&str>) -> Result<String, GoogleAuthError> {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs() as usize;

        let claims = JwtClaims {
            iss: self.service_account.client_email.clone(),
            scope: scope.to_string(),
            aud: GOOGLE_TOKEN_URL.to_string(),
            exp: now + 3600, // 1 hour
            iat: now,
            sub: sub.map(String::from),
        };

        let encoding_key = EncodingKey::from_rsa_pem(self.service_account.private_key.as_bytes())
            .map_err(|e| GoogleAuthError::InvalidPrivateKey(e.to_string()))?;

        encode(&Header::new(Algorithm::RS256), &claims, &encoding_key)
            .map_err(|e| GoogleAuthError::JwtCreation(e.to_string()))
    }

    async fn exchange_jwt_for_token(&self, jwt: &str) -> Result<TokenResponse, GoogleAuthError> {
        let response = self
            .client
            .post(GOOGLE_TOKEN_URL)
            .form(&[
                ("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
                ("assertion", jwt),
            ])
            .send()
            .await
            .map_err(|e| GoogleAuthError::TokenExchange(e.to_string()))?;

        if response.status().is_success() {
            response
                .json()
                .await
                .map_err(|e| GoogleAuthError::TokenExchange(format!("Failed to parse response: {}", e)))
        } else {
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            Err(GoogleAuthError::TokenExchange(error_text))
        }
    }
}

/// Parse a service account key from various formats
fn parse_service_account_key(key: &str) -> Result<ServiceAccountKey, GoogleAuthError> {
    let key_data = if key.starts_with('/') || key.starts_with("./") {
        // File path
        std::fs::read_to_string(key)
            .map_err(|e| GoogleAuthError::KeyFileRead(e.to_string()))?
    } else if key.starts_with('{') {
        // Raw JSON
        key.to_string()
    } else {
        // Base64 encoded
        let decoded = general_purpose::STANDARD
            .decode(key)
            .map_err(|e| GoogleAuthError::InvalidBase64(e.to_string()))?;

        String::from_utf8(decoded)
            .map_err(|e| GoogleAuthError::InvalidUtf8(e.to_string()))?
    };

    serde_json::from_str(&key_data)
        .map_err(|e| GoogleAuthError::InvalidKeyFormat(e.to_string()))
}

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

    #[test]
    fn test_parse_json_key() {
        let json = r#"{
            "type": "service_account",
            "project_id": "test",
            "private_key_id": "key123",
            "private_key": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----\n",
            "client_email": "test@test.iam.gserviceaccount.com",
            "client_id": "123",
            "auth_uri": "https://accounts.google.com/o/oauth2/auth",
            "token_uri": "https://oauth2.googleapis.com/token",
            "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
            "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test"
        }"#;

        let result = parse_service_account_key(json);
        assert!(result.is_ok());
        let key = result.unwrap();
        assert_eq!(key.project_id, "test");
        assert_eq!(key.client_email, "test@test.iam.gserviceaccount.com");
    }
}
