/// Configuration model for JWT auth enforcement rules.
use std::fmt;

use schemars::JsonSchema;
use serde::{Deserialize, Deserializer};

use crate::plugins::auth_enforcement::auth_result::AuthResultConfiguration;

/// Rule-level JWT configuration used by the auth enforcement plugin.
#[derive(Clone, Debug, Deserialize, JsonSchema)]
pub struct JWTRuleConfiguration {
    /// Result behavior when a JWT is invalid.
    pub invalid_jwt: AuthResultConfiguration,
    /// Result behavior when a JWT is expired.
    pub expired_jwt: AuthResultConfiguration,
    /// Allowed issuer values (`iss`) for incoming JWTs.
    pub issuers: Vec<String>,
    /// Allowed audience values (`aud`) for incoming JWTs.
    pub audiences: Vec<String>,
    /// Optional explicit JWKS URI.
    pub jwks_uri: Option<String>,
    /// JWKS cache lifespan in seconds before the next refresh attempt.
    #[serde(
        default = "default_jwks_lifespan_secs",
        deserialize_with = "deserialize_positive_jwks_lifespan_secs"
    )]
    pub jwks_lifespan_secs: u64,
}

/// Default JWKS cache lifespan (1 hour).
fn default_jwks_lifespan_secs() -> u64 {
    3600
}

/// Deserializes a strictly positive JWKS cache lifespan value.
fn deserialize_positive_jwks_lifespan_secs<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
    D: Deserializer<'de>,
{
    let value = u64::deserialize(deserializer)?;
    if value == 0 {
        return Err(serde::de::Error::custom(
            "jwks_lifespan_secs must be greater than 0",
        ));
    }
    Ok(value)
}

/// Errors that can occur while resolving the effective JWKS URI.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ResolveJwksUriError {
    /// `jwks_uri` is not set and no issuer exists to derive it from.
    MissingIssuerWhenJwksUriUnset,
    /// `jwks_uri` is not set and the selected issuer is empty after trimming.
    EmptyIssuerWhenJwksUriUnset,
}

impl std::error::Error for ResolveJwksUriError {}

impl fmt::Display for ResolveJwksUriError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ResolveJwksUriError::MissingIssuerWhenJwksUriUnset => {
                write!(
                    f,
                    "JWT config requires at least one issuer when jwks_uri is not set"
                )
            }
            ResolveJwksUriError::EmptyIssuerWhenJwksUriUnset => {
                write!(
                    f,
                    "JWT config issuer cannot be empty when jwks_uri is not set"
                )
            }
        }
    }
}

/// Resolves the effective JWKS URI.
///
/// Returns `jwks_uri` when explicitly configured; otherwise derives
/// `<first issuer>/.well-known/jwks.json` from the first issuer entry.
///
/// URI scheme validation (https-only, with localhost http exemptions) is
/// enforced at fetch time by `jwks::fetch_jwks`, not here.
pub fn resolve_jwks_uri(config: &JWTRuleConfiguration) -> Result<String, ResolveJwksUriError> {
    if let Some(jwks_uri) = &config.jwks_uri {
        return Ok(jwks_uri.clone());
    }

    let issuer = config
        .issuers
        .first()
        .ok_or(ResolveJwksUriError::MissingIssuerWhenJwksUriUnset)?;
    let issuer = issuer.trim();

    if issuer.is_empty() {
        return Err(ResolveJwksUriError::EmptyIssuerWhenJwksUriUnset);
    }

    Ok(format!(
        "{}/.well-known/jwks.json",
        issuer.trim_end_matches('/')
    ))
}

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

    use super::*;

    impl JWTRuleConfiguration {
        /// Creates a configuration where all outcomes are `Ok`.
        pub fn all_ok() -> JWTRuleConfiguration {
            JWTRuleConfiguration {
                invalid_jwt: AuthResultConfiguration::Ok,
                expired_jwt: AuthResultConfiguration::Ok,
                issuers: vec!["https://test.example.com/".to_string()],
                audiences: vec!["https://api.example.com".to_string()],
                jwks_uri: None,
                jwks_lifespan_secs: default_jwks_lifespan_secs(),
            }
        }

        /// Creates a configuration where all outcomes are `Warning`.
        pub fn all_warning() -> JWTRuleConfiguration {
            JWTRuleConfiguration {
                invalid_jwt: AuthResultConfiguration::Warning,
                expired_jwt: AuthResultConfiguration::Warning,
                issuers: vec!["https://test.example.com/".to_string()],
                audiences: vec!["https://api.example.com".to_string()],
                jwks_uri: None,
                jwks_lifespan_secs: default_jwks_lifespan_secs(),
            }
        }

        /// Creates a configuration where all outcomes are `Block`.
        pub fn all_block() -> JWTRuleConfiguration {
            JWTRuleConfiguration {
                invalid_jwt: AuthResultConfiguration::Block,
                expired_jwt: AuthResultConfiguration::Block,
                issuers: vec!["https://test.example.com/".to_string()],
                audiences: vec!["https://api.example.com".to_string()],
                jwks_uri: None,
                jwks_lifespan_secs: default_jwks_lifespan_secs(),
            }
        }
    }

    #[test]
    fn test_resolve_jwks_uri_derived_from_issuer() {
        let config = JWTRuleConfiguration::all_ok();
        assert_eq!(
            resolve_jwks_uri(&config).unwrap(),
            "https://test.example.com/.well-known/jwks.json"
        );
    }

    #[test]
    fn test_resolve_jwks_uri_uses_first_issuer_when_multiple_present() {
        let mut config = JWTRuleConfiguration::all_ok();
        config.issuers = vec![
            "https://first.example.com/".to_string(),
            "https://second.example.com/".to_string(),
        ];

        assert_eq!(
            resolve_jwks_uri(&config).unwrap(),
            "https://first.example.com/.well-known/jwks.json"
        );
    }

    #[test]
    fn test_resolve_jwks_uri_explicit_overrides_derived() {
        let mut config = JWTRuleConfiguration::all_ok();
        config.jwks_uri = Some("https://custom.example.com/.well-known/jwks.json".to_string());
        assert_eq!(
            resolve_jwks_uri(&config).unwrap(),
            "https://custom.example.com/.well-known/jwks.json"
        );
    }

    #[test]
    fn test_resolve_jwks_uri_explicit_with_empty_issuers() {
        let mut config = JWTRuleConfiguration::all_ok();
        config.issuers = vec![];
        config.jwks_uri = Some("https://custom.example.com/.well-known/jwks.json".to_string());

        assert_eq!(
            resolve_jwks_uri(&config).unwrap(),
            "https://custom.example.com/.well-known/jwks.json"
        );
    }

    #[test]
    fn test_resolve_jwks_uri_returns_error_when_issuers_empty() {
        let mut config = JWTRuleConfiguration::all_ok();
        config.issuers = vec![];
        config.jwks_uri = None;

        let result = resolve_jwks_uri(&config);
        assert_eq!(
            result,
            Err(ResolveJwksUriError::MissingIssuerWhenJwksUriUnset)
        );
    }

    #[test]
    fn test_resolve_jwks_uri_returns_error_when_first_issuer_is_empty() {
        let mut config = JWTRuleConfiguration::all_ok();
        config.issuers = vec!["".to_string()];
        config.jwks_uri = None;

        let result = resolve_jwks_uri(&config);
        assert_eq!(
            result,
            Err(ResolveJwksUriError::EmptyIssuerWhenJwksUriUnset)
        );
    }

    #[test]
    fn test_resolve_jwks_uri_returns_error_when_first_issuer_is_whitespace() {
        let mut config = JWTRuleConfiguration::all_ok();
        config.issuers = vec!["   \t  ".to_string()];
        config.jwks_uri = None;

        let result = resolve_jwks_uri(&config);
        assert_eq!(
            result,
            Err(ResolveJwksUriError::EmptyIssuerWhenJwksUriUnset)
        );
    }

    #[test]
    fn test_default_jwks_lifespan_secs() {
        assert_eq!(default_jwks_lifespan_secs(), 3600);
    }

    #[test]
    fn test_deserialize_uses_default_jwks_lifespan_secs_when_missing() {
        let raw = json!({
            "invalid_jwt": "Ok",
            "expired_jwt": "Warning",
            "issuers": ["https://test.example.com/"],
            "audiences": ["https://api.example.com"],
            "jwks_uri": null
        });

        let config: JWTRuleConfiguration = serde_json::from_value(raw).unwrap();

        assert_eq!(config.jwks_lifespan_secs, default_jwks_lifespan_secs());
    }

    #[test]
    fn test_deserialize_fails_when_invalid_jwt_missing() {
        let raw = json!({
            "expired_jwt": "Warning",
            "issuers": ["https://test.example.com/"],
            "audiences": ["https://api.example.com"],
            "jwks_uri": null
        });

        let result = serde_json::from_value::<JWTRuleConfiguration>(raw);
        assert!(result.is_err());
    }

    #[test]
    fn test_deserialize_fails_when_expired_jwt_missing() {
        let raw = json!({
            "invalid_jwt": "Ok",
            "issuers": ["https://test.example.com/"],
            "audiences": ["https://api.example.com"],
            "jwks_uri": null
        });

        let result = serde_json::from_value::<JWTRuleConfiguration>(raw);
        assert!(result.is_err());
    }

    #[test]
    fn test_deserialize_fails_when_issuers_missing() {
        let raw = json!({
            "invalid_jwt": "Ok",
            "expired_jwt": "Warning",
            "audiences": ["https://api.example.com"],
            "jwks_uri": null
        });

        let result = serde_json::from_value::<JWTRuleConfiguration>(raw);
        assert!(result.is_err());
    }

    #[test]
    fn test_deserialize_fails_when_audiences_missing() {
        let raw = json!({
            "invalid_jwt": "Ok",
            "expired_jwt": "Warning",
            "issuers": ["https://test.example.com/"],
            "jwks_uri": null
        });

        let result = serde_json::from_value::<JWTRuleConfiguration>(raw);
        assert!(result.is_err());
    }

    #[test]
    fn test_deserialize_fails_when_jwks_lifespan_secs_is_negative() {
        let raw = json!({
            "invalid_jwt": "Ok",
            "expired_jwt": "Warning",
            "issuers": ["https://test.example.com/"],
            "audiences": ["https://api.example.com"],
            "jwks_uri": null,
            "jwks_lifespan_secs": -1
        });

        let result = serde_json::from_value::<JWTRuleConfiguration>(raw);
        assert!(result.is_err());
    }

    #[test]
    fn test_deserialize_fails_when_jwks_lifespan_secs_is_zero() {
        let raw = json!({
            "invalid_jwt": "Ok",
            "expired_jwt": "Warning",
            "issuers": ["https://test.example.com/"],
            "audiences": ["https://api.example.com"],
            "jwks_uri": null,
            "jwks_lifespan_secs": 0
        });

        let result = serde_json::from_value::<JWTRuleConfiguration>(raw);
        assert!(result.is_err());
    }

    #[test]
    fn test_config_uses_provided_jwks_lifespan_secs() {
        let config = JWTRuleConfiguration {
            invalid_jwt: AuthResultConfiguration::Block,
            expired_jwt: AuthResultConfiguration::Block,
            issuers: vec!["https://test.example.com/".to_string()],
            audiences: vec!["https://api.example.com".to_string()],
            jwks_uri: None,
            jwks_lifespan_secs: 6000,
        };
        assert_eq!(config.jwks_lifespan_secs, 6000);
    }
}
