use crate::types::AccessTokenAuthorization;

/// Hearth SDK error type — spec §5.
///
/// The `HearthError` enum covers both HTTP-level API errors and all
/// client-side errors required by spec §5.
#[derive(Debug, thiserror::Error)]
pub enum HearthError {
    // ── HTTP / network layer ─────────────────────────────────────────────

    #[error("HTTP {status}: {message}")]
    Api {
        status: u16,
        message: String,
        details: Option<serde_json::Value>,
    },

    #[error("network error: {0}")]
    Network(#[from] reqwest::Error),

    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),

    #[error("{0}")]
    Other(String),

    // ── Spec §5 error types ──────────────────────────────────────────────

    /// The client is misconfigured (e.g. missing base URL or realm ID).
    #[error("configuration error: {message}")]
    ConfigurationError { message: String },

    /// The OIDC discovery document could not be fetched or parsed.
    #[error("discovery error from {url}: {message}")]
    DiscoveryError { url: String, message: String },

    /// The JWKS document could not be retrieved or parsed.
    #[error("JWKS fetch error from {url}: {message}")]
    JWKSFetchError { url: String, message: String },

    /// A token's `exp` claim is in the past.
    #[error("token expired at unix={expired_at}")]
    TokenExpiredError { expired_at: i64 },

    /// A token's `nbf` claim is in the future.
    #[error("token not yet valid until unix={not_before}")]
    TokenNotYetValidError { not_before: i64 },

    /// A token fails structural or signature validation.
    #[error("token invalid: {reason}")]
    TokenInvalidError { reason: String },

    /// The token's `iss` claim does not match the expected issuer.
    #[error("token issuer mismatch: expected {expected:?}, got {actual:?}")]
    TokenIssuerError { expected: String, actual: String },

    /// The token's `aud` claim does not include the expected audience.
    #[error("token audience mismatch: expected {expected:?}, got {actual:?}")]
    TokenAudienceError {
        expected: String,
        actual: Vec<String>,
    },

    /// A token introspection request failed or returned inactive.
    #[error("introspection error: {message}")]
    IntrospectionError { message: String },

    /// The token has `token_type == "required_action"` — the subject must complete
    /// the pending actions before accessing general API resources.
    ///
    /// `required_actions` contains the action names from the `required_actions` JWT claim
    /// (e.g. `["VERIFY_EMAIL", "UPDATE_PASSWORD"]`).  `redirect_uri` is the optional
    /// Hearth interstitial URL when one is provided by the server.
    #[error("required action pending: {required_actions:?}")]
    RequiredActionError {
        required_actions: Vec<String>,
        redirect_uri: Option<String>,
    },

    /// The server echoed a different authorization mode than the SDK was configured to expect.
    ///
    /// This is a hard rejection — the SDK never silently falls back to a different mode.
    #[error("mode mismatch: expected {expected:?}, got {actual:?}")]
    ModeMismatch {
        expected: AccessTokenAuthorization,
        actual: AccessTokenAuthorization,
    },

    /// A `Decision`-mode `/oauth/authorize` call failed; fail-closed.
    ///
    /// Any network or parse error on the decision endpoint is surfaced as this variant
    /// so callers can distinguish authorization-network failures from logic denials.
    #[error("authorization check failed: {reason}")]
    AuthorizationFailed { reason: String },
}

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

    #[test]
    fn required_action_error_fields() {
        let err = HearthError::RequiredActionError {
            required_actions: vec!["VERIFY_EMAIL".into(), "UPDATE_PASSWORD".into()],
            redirect_uri: Some("https://auth.example.com/ui/required-actions/verify-email".into()),
        };
        let msg = err.to_string();
        assert!(msg.contains("required action pending"));
        assert!(msg.contains("VERIFY_EMAIL"));

        match err {
            HearthError::RequiredActionError { required_actions, redirect_uri } => {
                assert_eq!(required_actions, vec!["VERIFY_EMAIL", "UPDATE_PASSWORD"]);
                assert!(redirect_uri.is_some());
            }
            _ => panic!("wrong variant"),
        }
    }

    #[test]
    fn required_action_error_no_redirect() {
        let err = HearthError::RequiredActionError {
            required_actions: vec!["VERIFY_EMAIL".into()],
            redirect_uri: None,
        };
        match err {
            HearthError::RequiredActionError { redirect_uri, .. } => {
                assert!(redirect_uri.is_none());
            }
            _ => panic!("wrong variant"),
        }
    }
}
