#![cfg(any(feature = "client", feature = "server"))]

use assert_matches2::assert_matches;
use assign::assign;
use ruma_client_api::uiaa::{
    self, AuthData, AuthFlow, AuthType, UiaaInfo, UiaaResponse, UserIdentifier,
};
use ruma_common::{
    api::{EndpointError, OutgoingResponse, error::ErrorKind},
    canonical_json::assert_to_canonical_json_eq,
};
use serde_json::{
    Value as JsonValue, from_slice as from_json_slice, from_str as from_json_str,
    from_value as from_json_value, json, value::to_raw_value as to_raw_json_value,
};

#[test]
fn deserialize_matrix_user_identifier() {
    assert_matches!(
        from_json_value(json!({
            "type": "m.id.user",
            "user": "cheeky_monkey"
        }))
        .unwrap(),
        UserIdentifier::Matrix(id)
    );
    assert_eq!(id.user, "cheeky_monkey");
}

#[test]
fn serialize_auth_data_registration_token() {
    let auth_data =
        AuthData::RegistrationToken(assign!(uiaa::RegistrationToken::new("mytoken".to_owned()), {
            session: Some("session".to_owned()),
        }));

    assert_to_canonical_json_eq!(
        auth_data,
        json!({
            "type": "m.login.registration_token",
            "token": "mytoken",
            "session": "session",
        })
    );
}

#[test]
fn deserialize_auth_data_registration_token() {
    let json = json!({
        "type": "m.login.registration_token",
        "token": "mytoken",
        "session": "session",
    });

    assert_matches!(from_json_value(json), Ok(AuthData::RegistrationToken(data)));
    assert_eq!(data.token, "mytoken");
    assert_eq!(data.session.as_deref(), Some("session"));
}

#[test]
fn serialize_auth_data_fallback() {
    let auth_data =
        AuthData::FallbackAcknowledgement(uiaa::FallbackAcknowledgement::new("ZXY000".to_owned()));

    assert_to_canonical_json_eq!(auth_data, json!({ "session": "ZXY000" }));
}

#[test]
fn deserialize_auth_data_fallback() {
    let json = json!({ "session": "opaque_session_id" });

    assert_matches!(from_json_value(json).unwrap(), AuthData::FallbackAcknowledgement(data));
    assert_eq!(data.session, "opaque_session_id");
}

#[test]
fn serialize_uiaa_info() {
    let flows = vec![AuthFlow::new(vec!["m.login.password".into(), "m.login.dummy".into()])];
    let params = to_raw_json_value(&json!({
        "example.type.baz": {
            "example_key": "foobar"
        }
    }))
    .unwrap();
    let uiaa_info = assign!(UiaaInfo::new(flows), {
        params: Some(params),
        completed: vec!["m.login.password".into()],
    });

    assert_to_canonical_json_eq!(
        uiaa_info,
        json!({
            "flows": [{ "stages": ["m.login.password", "m.login.dummy"] }],
            "completed": ["m.login.password"],
            "params": {
                "example.type.baz": {
                    "example_key": "foobar"
                }
            }
        })
    );
}

#[test]
fn deserialize_uiaa_info() {
    let json = json!({
        "errcode": "M_FORBIDDEN",
        "error": "Invalid password",
        "completed": ["m.login.recaptcha"],
        "flows": [
            {
                "stages": ["m.login.password"]
            },
            {
                "stages": ["m.login.email.identity", "m.login.msisdn"]
            }
        ],
        "params": {
            "example.type.baz": {
                "example_key": "foobar"
            }
        },
        "session": "xxxxxx"
    });

    let info = from_json_value::<UiaaInfo>(json).unwrap();
    assert_eq!(info.completed, vec![AuthType::ReCaptcha]);
    assert_eq!(info.flows.len(), 2);
    assert_eq!(info.flows[0].stages, vec![AuthType::Password]);
    assert_eq!(info.flows[1].stages, vec![AuthType::EmailIdentity, AuthType::Msisdn]);
    assert_eq!(info.session.as_deref(), Some("xxxxxx"));
    let auth_error = info.auth_error.unwrap();
    assert_matches!(auth_error.kind, ErrorKind::Forbidden);
    assert_eq!(auth_error.message, "Invalid password");
    assert_eq!(
        from_json_str::<JsonValue>(info.params.unwrap().get()).unwrap(),
        json!({
            "example.type.baz": {
                "example_key": "foobar"
            }
        })
    );
}

#[test]
fn try_uiaa_response_into_http_response() {
    let flows = vec![AuthFlow::new(vec![AuthType::Password, AuthType::Dummy])];
    let params = to_raw_json_value(&json!({
        "example.type.baz": {
            "example_key": "foobar"
        }
    }))
    .unwrap();
    let uiaa_info = assign!(UiaaInfo::new(flows), {
        params: Some(params),
        completed: vec![AuthType::ReCaptcha],
    });
    let uiaa_response =
        UiaaResponse::AuthResponse(uiaa_info).try_into_http_response::<Vec<u8>>().unwrap();

    let info = from_json_slice::<UiaaInfo>(uiaa_response.body()).unwrap();
    assert_eq!(info.flows.len(), 1);
    assert_eq!(info.flows[0].stages, vec![AuthType::Password, AuthType::Dummy]);
    assert_eq!(info.completed, vec![AuthType::ReCaptcha]);
    assert_eq!(info.session, None);
    assert_matches!(info.auth_error, None);
    assert_eq!(
        from_json_str::<JsonValue>(info.params.unwrap().get()).unwrap(),
        json!({
            "example.type.baz": {
                "example_key": "foobar"
            }
        })
    );
    assert_eq!(uiaa_response.status(), http::status::StatusCode::UNAUTHORIZED);
}

#[test]
fn try_uiaa_response_from_http_response() {
    let json = serde_json::to_string(&json!({
        "errcode": "M_FORBIDDEN",
        "error": "Invalid password",
        "completed": ["m.login.recaptcha"],
        "flows": [
            {
                "stages": ["m.login.password"]
            },
            {
                "stages": ["m.login.email.identity", "m.login.msisdn"]
            }
        ],
        "params": {
            "example.type.baz": {
                "example_key": "foobar"
            }
        },
        "session": "xxxxxx"
    }))
    .unwrap();

    let http_response = http::Response::builder()
        .status(http::StatusCode::UNAUTHORIZED)
        .body(json.as_bytes())
        .unwrap();

    assert_matches!(
        UiaaResponse::from_http_response(http_response),
        UiaaResponse::AuthResponse(info)
    );
    assert_eq!(info.completed, vec![AuthType::ReCaptcha]);
    assert_eq!(info.flows.len(), 2);
    assert_eq!(info.flows[0].stages, vec![AuthType::Password]);
    assert_eq!(info.flows[1].stages, vec![AuthType::EmailIdentity, AuthType::Msisdn]);
    assert_eq!(info.session.as_deref(), Some("xxxxxx"));
    let auth_error = info.auth_error.unwrap();
    assert_matches!(auth_error.kind, ErrorKind::Forbidden);
    assert_eq!(auth_error.message, "Invalid password");
    assert_eq!(
        from_json_str::<JsonValue>(info.params.unwrap().get()).unwrap(),
        json!({
            "example.type.baz": {
                "example_key": "foobar"
            }
        })
    );
}
