mod tests {
    use readur::models::{AuthProvider, CreateUser, UserRole};
    use axum::http::StatusCode;
    use serde_json::json;
    use tower::util::ServiceExt;
    use wiremock::{matchers::{method, path, query_param, header}, Mock, MockServer, ResponseTemplate};
    use std::sync::Arc;
    use readur::{AppState, oidc::OidcClient};
    use uuid;

    async fn create_test_app_simple() -> (axum::Router, ()) {
        // Use TEST_DATABASE_URL directly, no containers
        let database_url = std::env::var("TEST_DATABASE_URL")
            .or_else(|_| std::env::var("DATABASE_URL"))
            .unwrap_or_else(|_| "postgresql://readur:readur@localhost:5432/readur".to_string());
        
        let config = readur::config::Config {
            database_url: database_url.clone(),
            server_address: "127.0.0.1:0".to_string(),
            jwt_secret: "test-secret".to_string(),
            upload_path: "./test-uploads".to_string(),
            watch_folder: "./test-watch".to_string(),
            allowed_file_types: vec!["pdf".to_string()],
            watch_interval_seconds: Some(30),
            file_stability_check_ms: Some(500),
            max_file_age_hours: None,
            ocr_language: "eng".to_string(),
            concurrent_ocr_jobs: 2,
            ocr_timeout_seconds: 60,
            max_file_size_mb: 10,
            memory_limit_mb: 256,
            cpu_priority: "normal".to_string(),
            oidc_enabled: false,
            oidc_client_id: None,
            oidc_client_secret: None,
            oidc_issuer_url: None,
            oidc_redirect_uri: None,
        };

        let db = readur::db::Database::new(&config.database_url).await.unwrap();
        
        // Retry migration up to 3 times to handle concurrent test execution
        for attempt in 1..=3 {
            match db.migrate().await {
                Ok(_) => break,
                Err(e) if attempt < 3 && e.to_string().contains("tuple concurrently updated") => {
                    // Wait a bit and retry
                    tokio::time::sleep(tokio::time::Duration::from_millis(100 * attempt)).await;
                    continue;
                }
                Err(e) => panic!("Migration failed after {} attempts: {}", attempt, e),
            }
        }
        
        let app = axum::Router::new()
            .nest("/api/auth", readur::routes::auth::router())
            .with_state(Arc::new(AppState {
                db: db.clone(),
                config,
                webdav_scheduler: None,
                source_scheduler: None,
                queue_service: Arc::new(readur::ocr::queue::OcrQueueService::new(
                    db.clone(),
                    db.pool.clone(),
                    2
                )),
                oidc_client: None,
            }));

        (app, ())
    }

    async fn create_test_app_with_oidc() -> (axum::Router, MockServer) {
        let mock_server = MockServer::start().await;
        
        // Mock OIDC discovery endpoint
        let discovery_response = json!({
            "issuer": mock_server.uri(),
            "authorization_endpoint": format!("{}/auth", mock_server.uri()),
            "token_endpoint": format!("{}/token", mock_server.uri()),
            "userinfo_endpoint": format!("{}/userinfo", mock_server.uri())
        });

        Mock::given(method("GET"))
            .and(path("/.well-known/openid-configuration"))
            .respond_with(ResponseTemplate::new(200).set_body_json(discovery_response))
            .mount(&mock_server)
            .await;

        // Use TEST_DATABASE_URL directly, no containers
        let database_url = std::env::var("TEST_DATABASE_URL")
            .or_else(|_| std::env::var("DATABASE_URL"))
            .unwrap_or_else(|_| "postgresql://readur:readur@localhost:5432/readur".to_string());
        
        // Update the app state to include OIDC client
        let config = readur::config::Config {
            database_url: database_url.clone(),
            server_address: "127.0.0.1:0".to_string(),
            jwt_secret: "test-secret".to_string(),
            upload_path: "./test-uploads".to_string(),
            watch_folder: "./test-watch".to_string(),
            allowed_file_types: vec!["pdf".to_string()],
            watch_interval_seconds: Some(30),
            file_stability_check_ms: Some(500),
            max_file_age_hours: None,
            ocr_language: "eng".to_string(),
            concurrent_ocr_jobs: 2,
            ocr_timeout_seconds: 60,
            max_file_size_mb: 10,
            memory_limit_mb: 256,
            cpu_priority: "normal".to_string(),
            oidc_enabled: true,
            oidc_client_id: Some("test-client-id".to_string()),
            oidc_client_secret: Some("test-client-secret".to_string()),
            oidc_issuer_url: Some(mock_server.uri()),
            oidc_redirect_uri: Some("http://localhost:8000/auth/oidc/callback".to_string()),
        };

        let oidc_client = match OidcClient::new(&config).await {
            Ok(client) => Some(Arc::new(client)),
            Err(e) => {
                panic!("OIDC client creation failed: {}", e);
            }
        };
        
        // Connect to the database and run migrations with retry logic for concurrency
        let db = readur::db::Database::new(&config.database_url).await.unwrap();
        
        // Retry migration up to 3 times to handle concurrent test execution
        for attempt in 1..=3 {
            match db.migrate().await {
                Ok(_) => break,
                Err(e) if attempt < 3 && e.to_string().contains("tuple concurrently updated") => {
                    // Wait a bit and retry
                    tokio::time::sleep(tokio::time::Duration::from_millis(100 * attempt)).await;
                    continue;
                }
                Err(e) => panic!("Migration failed after {} attempts: {}", attempt, e),
            }
        }
        
        // Create app with OIDC configuration
        let app = axum::Router::new()
            .nest("/api/auth", readur::routes::auth::router())
            .with_state(Arc::new(AppState {
                db: db.clone(),
                config,
                webdav_scheduler: None,
                source_scheduler: None,
                queue_service: Arc::new(readur::ocr::queue::OcrQueueService::new(
                    db.clone(),
                    db.pool.clone(),
                    2
                )),
                oidc_client,
            }));

        (app, mock_server)
    }

    #[tokio::test]
    async fn test_oidc_login_redirect() {
        let (app, _mock_server) = create_test_app_with_oidc().await;

        let response = app
            .oneshot(
                axum::http::Request::builder()
                    .method("GET")
                    .uri("/api/auth/oidc/login")
                    .body(axum::body::Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::SEE_OTHER);
        
        let location = response.headers().get("location").unwrap().to_str().unwrap();
        assert!(location.contains("/auth"));
        assert!(location.contains("client_id=test-client-id"));
        assert!(location.contains("scope=openid"));
    }

    #[tokio::test]
    async fn test_oidc_login_disabled() {
        let (app, _container) = create_test_app_simple().await; // Regular app without OIDC

        let response = app
            .oneshot(
                axum::http::Request::builder()
                    .method("GET")
                    .uri("/api/auth/oidc/login")
                    .body(axum::body::Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
    }

    #[tokio::test]
    async fn test_oidc_callback_missing_code() {
        let (app, _mock_server) = create_test_app_with_oidc().await;

        let response = app
            .oneshot(
                axum::http::Request::builder()
                    .method("GET")
                    .uri("/api/auth/oidc/callback")
                    .body(axum::body::Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
    }

    #[tokio::test]
    async fn test_oidc_callback_with_error() {
        let (app, _mock_server) = create_test_app_with_oidc().await;

        let response = app
            .oneshot(
                axum::http::Request::builder()
                    .method("GET")
                    .uri("/api/auth/oidc/callback?error=access_denied")
                    .body(axum::body::Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
    }

    #[tokio::test]
    async fn test_oidc_callback_success_new_user() {
        let (app, mock_server) = create_test_app_with_oidc().await;
        
        // Generate random identifiers to avoid test interference
        let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string();
        let test_username = format!("oidcuser_{}", test_id);
        let test_email = format!("oidc_{}@example.com", test_id);
        let test_subject = format!("oidc-user-{}", test_id);
        
        // Clean up any existing test user to ensure test isolation
        let database_url = std::env::var("TEST_DATABASE_URL")
            .or_else(|_| std::env::var("DATABASE_URL"))
            .unwrap_or_else(|_| "postgresql://readur:readur@localhost:5432/readur".to_string());
        let db = readur::db::Database::new(&database_url).await.unwrap();
        
        // Delete any existing user with the test username or OIDC subject
        let _ = sqlx::query("DELETE FROM users WHERE username = $1 OR oidc_subject = $2")
            .bind(&test_username)
            .bind(&test_subject)
            .execute(&db.pool)
            .await;
        

        // Mock token exchange
        let token_response = json!({
            "access_token": "test-access-token",
            "token_type": "Bearer",
            "expires_in": 3600
        });

        Mock::given(method("POST"))
            .and(path("/token"))
            .and(header("content-type", "application/x-www-form-urlencoded"))
            .respond_with(ResponseTemplate::new(200)
                .set_body_json(token_response)
                .insert_header("content-type", "application/json"))
            .mount(&mock_server)
            .await;

        // Mock user info
        let user_info_response = json!({
            "sub": test_subject,
            "email": test_email,
            "name": "OIDC User",
            "preferred_username": test_username
        });

        Mock::given(method("GET"))
            .and(path("/userinfo"))
            .respond_with(ResponseTemplate::new(200)
                .set_body_json(user_info_response)
                .insert_header("content-type", "application/json"))
            .mount(&mock_server)
            .await;

        // Add a small delay to make sure everything is set up
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
        
        let response = app
            .oneshot(
                axum::http::Request::builder()
                    .method("GET")
                    .uri("/api/auth/oidc/callback?code=test-auth-code&state=test-state")
                    .body(axum::body::Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        let status = response.status();
        let body = axum::body::to_bytes(response.into_body(), usize::MAX)
            .await
            .unwrap();
        
        if status != StatusCode::OK {
            let error_text = String::from_utf8_lossy(&body);
            eprintln!("Response status: {}", status);
            eprintln!("Response body: {}", error_text);
            
            // Also check if we made the expected API calls to the mock server
            eprintln!("Mock server received calls:");
            let received_requests = mock_server.received_requests().await.unwrap();
            for req in received_requests {
                eprintln!("  {} {} - {}", req.method, req.url.path(), String::from_utf8_lossy(&req.body));
            }
            
            // Try to parse as JSON to see if there's a more detailed error message
            if let Ok(error_json) = serde_json::from_slice::<serde_json::Value>(&body) {
                eprintln!("Error JSON: {:#}", error_json);
            }
        }
        
        assert_eq!(status, StatusCode::OK);
        
        let login_response: serde_json::Value = serde_json::from_slice(&body).unwrap();
        
        assert!(login_response["token"].is_string());
        assert_eq!(login_response["user"]["username"], test_username);
        assert_eq!(login_response["user"]["email"], test_email);
    }

    #[tokio::test]
    async fn test_oidc_callback_invalid_token() {
        let (app, mock_server) = create_test_app_with_oidc().await;

        // Mock failed token exchange
        Mock::given(method("POST"))
            .and(path("/token"))
            .respond_with(ResponseTemplate::new(400).set_body_json(json!({
                "error": "invalid_grant"
            })))
            .mount(&mock_server)
            .await;

        let response = app
            .oneshot(
                axum::http::Request::builder()
                    .method("GET")
                    .uri("/api/auth/oidc/callback?code=invalid-auth-code")
                    .body(axum::body::Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
    }

    #[tokio::test]
    async fn test_oidc_callback_invalid_user_info() {
        let (app, mock_server) = create_test_app_with_oidc().await;
        
        // Generate random identifiers to avoid test interference
        let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string();
        let test_username = format!("oidcuser_{}", test_id);
        let test_subject = format!("oidc-user-{}", test_id);
        
        // Clean up any existing test user to ensure test isolation
        let database_url = std::env::var("TEST_DATABASE_URL")
            .or_else(|_| std::env::var("DATABASE_URL"))
            .unwrap_or_else(|_| "postgresql://readur:readur@localhost:5432/readur".to_string());
        let db = readur::db::Database::new(&database_url).await.unwrap();
        
        // Delete any existing user that might conflict
        let _ = sqlx::query("DELETE FROM users WHERE username = $1 OR oidc_subject = $2")
            .bind(&test_username)
            .bind(&test_subject)
            .execute(&db.pool)
            .await;

        // Mock successful token exchange
        let token_response = json!({
            "access_token": "test-access-token",
            "token_type": "Bearer",
            "expires_in": 3600
        });

        Mock::given(method("POST"))
            .and(path("/token"))
            .respond_with(ResponseTemplate::new(200).set_body_json(token_response))
            .mount(&mock_server)
            .await;

        // Mock failed user info
        Mock::given(method("GET"))
            .and(path("/userinfo"))
            .respond_with(ResponseTemplate::new(401))
            .mount(&mock_server)
            .await;

        let response = app
            .oneshot(
                axum::http::Request::builder()
                    .method("GET")
                    .uri("/api/auth/oidc/callback?code=test-auth-code")
                    .body(axum::body::Body::empty())
                    .unwrap(),
            )
            .await
            .unwrap();

        assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
    }
}