//! Tests for el-auth.

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use crate::{
        context::{AuthContext, AuthUser},
        jwt::{JwtClaims, JwtProvider},
        middleware::AuthMiddleware,
        roles::{Permission, Role, RoleRegistry},
        session::SessionProvider,
        AuthError, AuthProvider,
    };

    fn test_user() -> AuthUser {
        AuthUser::new("user-1", "alice@example.com", "Alice")
    }

    fn test_provider() -> JwtProvider {
        JwtProvider::new(b"super-secret-key-for-testing-only".to_vec())
            .with_ttl(3600)
    }

    fn registry() -> RoleRegistry {
        let mut r = RoleRegistry::new();
        r.register(
            Role::new("admin")
                .with_permissions(vec!["read", "write", "delete"]),
        );
        r.register(
            Role::new("user")
                .with_permissions(vec!["read"]),
        );
        r
    }

    // ── Test 1: JWT round-trip — sign and verify ──────────────────────────────
    #[test]
    fn test_jwt_sign_and_verify() {
        let provider = test_provider();
        let user = test_user();
        let claims = JwtClaims::new(&user, vec!["user".into()], 3600);
        let token = provider.encode(&claims);
        let decoded = provider.decode(&token).unwrap();
        assert_eq!(decoded.sub, "user-1");
        assert_eq!(decoded.email, "alice@example.com");
        assert_eq!(decoded.roles, vec!["user"]);
    }

    // ── Test 2: JWT invalid signature is rejected ─────────────────────────────
    #[test]
    fn test_jwt_invalid_signature() {
        let provider = test_provider();
        let other_provider = JwtProvider::new(b"different-secret".to_vec());
        let user = test_user();
        let claims = JwtClaims::new(&user, vec![], 3600);
        let token = other_provider.encode(&claims);
        let result = provider.decode(&token);
        assert!(matches!(result, Err(AuthError::TokenInvalid(_))));
    }

    // ── Test 3: JWT expired token is rejected ────────────────────────────────
    #[test]
    fn test_jwt_expired_token() {
        let provider = test_provider();
        let user = test_user();
        // TTL of 0 — expires immediately
        let claims = JwtClaims::new(&user, vec![], 0);
        let token = provider.encode(&claims);
        // Wait a moment (in tests, just check the claims are_expired)
        assert!(claims.is_expired() || {
            std::thread::sleep(std::time::Duration::from_millis(1100));
            true
        });
        let result = provider.decode(&token);
        assert!(matches!(result, Err(AuthError::TokenExpired) | Err(AuthError::TokenInvalid(_))));
    }

    // ── Test 4: JWT verify returns correct AuthContext ───────────────────────
    #[test]
    fn test_jwt_verify_returns_auth_context() {
        let provider = test_provider();
        let user = test_user();
        let reg = registry();
        let token = provider.issue(user, &reg).unwrap();
        let ctx = provider.verify(&token).unwrap();
        assert!(ctx.is_authenticated());
        assert_eq!(ctx.user_id().unwrap(), "user-1");
    }

    // ── Test 5: AuthContext::has_role works ──────────────────────────────────
    #[test]
    fn test_auth_context_has_role() {
        let ctx = AuthContext::authenticated(
            test_user(),
            vec!["admin".into(), "user".into()],
            "tok",
        );
        assert!(ctx.has_role("admin"));
        assert!(ctx.has_role("user"));
        assert!(!ctx.has_role("superadmin"));
    }

    // ── Test 6: AuthContext::has_permission works ─────────────────────────────
    #[test]
    fn test_auth_context_has_permission() {
        let ctx = AuthContext::authenticated(test_user(), vec!["admin".into()], "tok")
            .with_permissions(vec!["read".into(), "write".into(), "delete".into()]);
        assert!(ctx.has_permission("read"));
        assert!(ctx.has_permission("delete"));
        assert!(!ctx.has_permission("sudo"));
    }

    // ── Test 7: AuthContext::anonymous is not authenticated ───────────────────
    #[test]
    fn test_anonymous_context() {
        let ctx = AuthContext::anonymous();
        assert!(!ctx.is_authenticated());
        assert!(ctx.user_id().is_none());
    }

    // ── Test 8: Role has_permission ──────────────────────────────────────────
    #[test]
    fn test_role_has_permission() {
        let role = Role::new("editor")
            .with_permissions(vec!["read", "write"]);
        assert!(role.has_permission("read"));
        assert!(role.has_permission("write"));
        assert!(!role.has_permission("delete"));
    }

    // ── Test 9: RoleRegistry::has_permission checks across roles ─────────────
    #[test]
    fn test_role_registry_permission_check() {
        let reg = registry();
        let roles = vec!["user".to_string()];
        assert!(reg.has_permission(&roles, "read"));
        assert!(!reg.has_permission(&roles, "delete"));

        let admin_roles = vec!["admin".to_string()];
        assert!(reg.has_permission(&admin_roles, "delete"));
    }

    // ── Test 10: SessionProvider issue and verify ─────────────────────────────
    #[test]
    fn test_session_issue_and_verify() {
        let provider = SessionProvider::new();
        let reg = registry();
        let session_id = provider.issue(test_user(), &reg).unwrap();
        let ctx = provider.verify(&session_id).unwrap();
        assert!(ctx.is_authenticated());
        assert_eq!(ctx.user_id().unwrap(), "user-1");
    }

    // ── Test 11: SessionProvider revoke removes session ───────────────────────
    #[test]
    fn test_session_revoke() {
        let provider = SessionProvider::new();
        let reg = registry();
        let session_id = provider.issue(test_user(), &reg).unwrap();
        provider.revoke(&session_id).unwrap();
        let result = provider.verify(&session_id);
        assert!(matches!(result, Err(AuthError::SessionNotFound)));
    }

    // ── Test 12: SessionProvider unknown session returns error ────────────────
    #[test]
    fn test_session_unknown() {
        let provider = SessionProvider::new();
        let result = provider.verify("nonexistent-session-id");
        assert!(matches!(result, Err(AuthError::SessionNotFound)));
    }

    // ── Test 13: AuthMiddleware extracts Bearer token ─────────────────────────
    #[test]
    fn test_middleware_extracts_bearer_token() {
        let provider = Arc::new(test_provider());
        let user = test_user();
        let claims = JwtClaims::new(&user, vec!["user".into()], 3600);
        let token = provider.encode(&claims);
        let middleware = AuthMiddleware::new(provider);
        let header = format!("Bearer {}", token);
        let ctx = middleware.authenticate_from_header(Some(&header)).unwrap();
        assert!(ctx.is_authenticated());
    }

    // ── Test 14: AuthMiddleware with no header returns anonymous ──────────────
    #[test]
    fn test_middleware_no_header_anonymous() {
        let provider = Arc::new(test_provider());
        let middleware = AuthMiddleware::new(provider);
        let ctx = middleware.authenticate_from_header(None).unwrap();
        assert!(!ctx.is_authenticated());
    }

    // ── Test 15: Permission Display ───────────────────────────────────────────
    #[test]
    fn test_permission_display() {
        let perm = Permission::new("write");
        assert_eq!(perm.to_string(), "write");
        assert_eq!(perm.as_str(), "write");
    }

    // ── Test 16: JwtClaims::to_json and from_json round-trip ─────────────────
    #[test]
    fn test_jwt_claims_json_round_trip() {
        let user = test_user();
        let claims = JwtClaims::new(&user, vec!["admin".into(), "user".into()], 3600);
        let json = claims.to_json();
        let decoded = JwtClaims::from_json(&json).unwrap();
        assert_eq!(decoded.sub, "user-1");
        assert_eq!(decoded.email, "alice@example.com");
        assert_eq!(decoded.roles, vec!["admin", "user"]);
    }

    // ── Test 17: JWT with multiple roles ─────────────────────────────────────
    #[test]
    fn test_jwt_multiple_roles() {
        let provider = test_provider();
        let user = test_user();
        let claims = JwtClaims::new(&user, vec!["admin".into(), "user".into()], 3600);
        let token = provider.encode(&claims);
        let decoded = provider.decode(&token).unwrap();
        assert_eq!(decoded.roles.len(), 2);
        assert!(decoded.roles.contains(&"admin".to_string()));
    }

    // ── Test 18: RoleRegistry::role_names lists all roles ────────────────────
    #[test]
    fn test_role_registry_names() {
        let reg = registry();
        let mut names = reg.role_names();
        names.sort();
        assert_eq!(names, vec!["admin", "user"]);
    }

    // ── Test 19: SessionProvider TTL configuration ────────────────────────────
    #[test]
    fn test_session_ttl_config() {
        let provider = SessionProvider::new().with_ttl(7200);
        assert_eq!(provider.ttl.as_secs(), 7200);
    }

    // ── Test 20: AuthMiddleware provider_name returns correct name ────────────
    #[test]
    fn test_middleware_provider_name() {
        let jwt_provider = Arc::new(test_provider());
        let middleware = AuthMiddleware::new(jwt_provider);
        assert_eq!(middleware.provider_name(), "jwt");
    }
}
