use athena_auth_core::AuthError;
use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode, decode_header};
use serde::Deserialize;

use super::providers::OAuthUserInfo;

const APPLE_JWKS_URL: &str = "https://appleid.apple.com/auth/keys";
const APPLE_ISSUER: &str = "https://appleid.apple.com";

#[derive(Debug, Deserialize)]
struct AppleJwks {
    keys: Vec<AppleJwk>,
}

#[derive(Debug, Deserialize)]
struct AppleJwk {
    kid: String,
    kty: String,
    #[serde(default)]
    n: Option<String>,
    #[serde(default)]
    e: Option<String>,
    #[serde(default)]
    x: Option<String>,
    #[serde(default)]
    y: Option<String>,
}

#[derive(Debug, Deserialize)]
struct AppleIdTokenClaims {
    sub: String,
    #[serde(default)]
    email: Option<String>,
    #[serde(default, rename = "email_verified")]
    email_verified: Option<serde_json::Value>,
}

/// Verify an Apple `identityToken` JWT and map it to [`OAuthUserInfo`].
pub async fn verify_apple_id_token(
    id_token: &str,
    client_id: &str,
) -> Result<OAuthUserInfo, AuthError> {
    let header = decode_header(id_token)
        .map_err(|_| AuthError::bad_request("Invalid Apple ID token header"))?;
    let kid = header
        .kid
        .ok_or_else(|| AuthError::bad_request("Apple ID token is missing key id"))?;

    let jwks: AppleJwks = reqwest::Client::new()
        .get(APPLE_JWKS_URL)
        .send()
        .await
        .map_err(|e| AuthError::internal(format!("Failed to fetch Apple JWKS: {e}")))?
        .json()
        .await
        .map_err(|e| AuthError::internal(format!("Failed to parse Apple JWKS: {e}")))?;

    let jwk = jwks
        .keys
        .into_iter()
        .find(|key| key.kid == kid)
        .ok_or_else(|| AuthError::bad_request("Apple ID token key id not found in JWKS"))?;

    let decoding_key: DecodingKey = decoding_key_from_jwk(&jwk)?;
    let algorithm: Algorithm = match jwk.kty.as_str() {
        "RSA" => Algorithm::RS256,
        "EC" => Algorithm::ES256,
        other => {
            return Err(AuthError::bad_request(format!(
                "Unsupported Apple JWK key type: {other}"
            )));
        }
    };

    let mut validation = Validation::new(algorithm);
    validation.set_issuer(&[APPLE_ISSUER]);
    validation.set_audience(&[client_id]);

    let token: jsonwebtoken::TokenData<AppleIdTokenClaims> =
        decode::<AppleIdTokenClaims>(id_token, &decoding_key, &validation)
            .map_err(|_| AuthError::bad_request("Invalid Apple ID token"))?;

    let claims = token.claims;
    let email = claims.email.unwrap_or_default();

    let email_verified = claims
        .email_verified
        .as_ref()
        .map(parse_email_verified)
        .unwrap_or(false);

    Ok(OAuthUserInfo {
        id: claims.sub,
        email,
        name: None,
        image: None,
        email_verified,
    })
}

fn decoding_key_from_jwk(jwk: &AppleJwk) -> Result<DecodingKey, AuthError> {
    match jwk.kty.as_str() {
        "RSA" => {
            let n = jwk
                .n
                .as_deref()
                .ok_or_else(|| AuthError::internal("Apple RSA JWK missing modulus"))?;
            let e = jwk
                .e
                .as_deref()
                .ok_or_else(|| AuthError::internal("Apple RSA JWK missing exponent"))?;
            DecodingKey::from_rsa_components(n, e)
                .map_err(|_| AuthError::internal("Invalid Apple RSA JWK components"))
        }
        "EC" => {
            let x = jwk
                .x
                .as_deref()
                .ok_or_else(|| AuthError::internal("Apple EC JWK missing x coordinate"))?;
            let y = jwk
                .y
                .as_deref()
                .ok_or_else(|| AuthError::internal("Apple EC JWK missing y coordinate"))?;
            DecodingKey::from_ec_components(x, y)
                .map_err(|_| AuthError::internal("Invalid Apple EC JWK components"))
        }
        other => Err(AuthError::internal(format!(
            "Unsupported Apple JWK key type: {other}"
        ))),
    }
}

fn parse_email_verified(value: &serde_json::Value) -> bool {
    value
        .as_bool()
        .or_else(|| value.as_str().map(|flag| flag.eq_ignore_ascii_case("true")))
        .unwrap_or(false)
}

#[cfg(test)]
mod tests {
    use super::parse_email_verified;
    use serde_json::json;

    #[test]
    fn parses_string_email_verified_flag() {
        assert!(parse_email_verified(&json!("true")));
        assert!(!parse_email_verified(&json!("false")));
    }
}
