use std::net::TcpListener;

async fn start_server() -> (String, u16) {
    // Bind to random port
    let listener = TcpListener::bind("127.0.0.1:0").unwrap();
    let port = listener.local_addr().unwrap().port();
    drop(listener); // Free port for server

    let base_url = format!("http://127.0.0.1:{port}");

    tokio::spawn(async move {
        let config = aws_local_cognito::config::Config {
            port,
            region: "us-east-1".to_string(),
            data_dir: format!("/tmp/cognito-test-{port}"),
            signing_secret: "test-secret".to_string(),
            log_level: "warn".to_string(),
            issuer_base: String::new(),
        };

        let db = aws_local_cognito::db::connect(&config.data_dir).unwrap();
        aws_local_cognito::db::run_migrations(&db).unwrap();

        let crypto = aws_local_cognito::crypto::CryptoProvider::new(&config.signing_secret);

        let state = aws_local_cognito::server::AppState {
            db,
            crypto,
            config,
        };

        let app = aws_local_cognito::server::build_router(state);
        let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{port}"))
            .await
            .unwrap();
        axum::serve(listener, app).await.unwrap();
    });

    // Wait for server readiness
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;

    (base_url, port)
}

async fn make_client(base_url: &str) -> aws_sdk_cognitoidentityprovider::Client {
    let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
        .endpoint_url(base_url)
        .region(aws_config::Region::new("us-east-1"))
        .credentials_provider(aws_credential_types::Credentials::new(
            "fake", "fake", None, None, "test",
        ))
        .load()
        .await;
    aws_sdk_cognitoidentityprovider::Client::new(&config)
}

#[tokio::test]
async fn test_full_user_pool_lifecycle() {
    let (base_url, _port) = start_server().await;
    let client = make_client(&base_url).await;

    // 1. Create a user pool
    let create_resp = client
        .create_user_pool()
        .pool_name("IntegrationTestPool")
        .send()
        .await
        .expect("CreateUserPool should succeed");

    let pool = create_resp.user_pool().expect("response should contain UserPool");
    let pool_id = pool.id().expect("pool should have an id");
    let pool_name = pool.name().expect("pool should have a name");
    assert_eq!(pool_name, "IntegrationTestPool");
    assert!(pool_id.starts_with("us-east-1_"));

    // 2. Describe the pool
    let describe_resp = client
        .describe_user_pool()
        .user_pool_id(pool_id)
        .send()
        .await
        .expect("DescribeUserPool should succeed");

    let described = describe_resp.user_pool().expect("response should contain UserPool");
    assert_eq!(described.id().unwrap(), pool_id);
    assert_eq!(described.name().unwrap(), "IntegrationTestPool");

    // 3. List pools
    let list_resp = client
        .list_user_pools()
        .max_results(10)
        .send()
        .await
        .expect("ListUserPools should succeed");

    let pools = list_resp.user_pools();
    assert!(!pools.is_empty(), "should list at least one pool");
    assert!(
        pools.iter().any(|p| p.id().unwrap_or_default() == pool_id),
        "listed pools should include the one we created"
    );

    // 4. Delete the pool
    client
        .delete_user_pool()
        .user_pool_id(pool_id)
        .send()
        .await
        .expect("DeleteUserPool should succeed");

    // 5. Verify it's gone — describe should fail
    let describe_after_delete = client
        .describe_user_pool()
        .user_pool_id(pool_id)
        .send()
        .await;
    assert!(
        describe_after_delete.is_err(),
        "DescribeUserPool should fail after deletion"
    );
}

#[tokio::test]
async fn test_admin_user_crud() {
    let (base_url, _port) = start_server().await;
    let client = make_client(&base_url).await;

    // 1. Create a pool to hold users
    let create_pool_resp = client
        .create_user_pool()
        .pool_name("UserCrudTestPool")
        .send()
        .await
        .expect("CreateUserPool should succeed");

    let pool = create_pool_resp.user_pool().unwrap();
    let pool_id = pool.id().unwrap();

    // 2. Admin-create a user with a temporary password
    let create_user_resp = client
        .admin_create_user()
        .user_pool_id(pool_id)
        .username("testuser1")
        .temporary_password("TempPass123!")
        .send()
        .await
        .expect("AdminCreateUser should succeed");

    let user = create_user_resp.user().expect("response should contain User");
    assert_eq!(user.username().unwrap(), "testuser1");
    assert_eq!(
        user.user_status().unwrap(),
        &aws_sdk_cognitoidentityprovider::types::UserStatusType::ForceChangePassword,
    );

    // 3. Admin-get the user
    let get_user_resp = client
        .admin_get_user()
        .user_pool_id(pool_id)
        .username("testuser1")
        .send()
        .await
        .expect("AdminGetUser should succeed");

    assert_eq!(get_user_resp.username(), "testuser1");

    // 4. List users in the pool
    let list_resp = client
        .list_users()
        .user_pool_id(pool_id)
        .send()
        .await
        .expect("ListUsers should succeed");

    let users = list_resp.users();
    assert_eq!(users.len(), 1, "should have exactly one user");
    assert_eq!(users[0].username().unwrap(), "testuser1");

    // 5. Admin-delete the user
    client
        .admin_delete_user()
        .user_pool_id(pool_id)
        .username("testuser1")
        .send()
        .await
        .expect("AdminDeleteUser should succeed");

    // 6. Verify user is gone
    let get_after_delete = client
        .admin_get_user()
        .user_pool_id(pool_id)
        .username("testuser1")
        .send()
        .await;
    assert!(
        get_after_delete.is_err(),
        "AdminGetUser should fail after user deletion"
    );

    // Cleanup
    client
        .delete_user_pool()
        .user_pool_id(pool_id)
        .send()
        .await
        .expect("DeleteUserPool cleanup should succeed");
}
