#![allow(unused_imports)]
use std::collections::BTreeMap;
use std::str::FromStr;

use compact_jwt::{JwkKeySet, JwsEs256Verifier, JwsVerifier, OidcToken, OidcUnverified};
use kanidm_client::KanidmClient;

use kanidm_proto::constants::uri::{OAUTH2_AUTHORISE, OAUTH2_AUTHORISE_PERMIT};

use kanidm_proto::internal::Oauth2ClaimMapJoin;
use kanidm_proto::oauth2::{
    AccessTokenRequest, AccessTokenResponse, AuthorisationResponse, GrantTypeReq,
};

use kanidmd_lib::constants::NAME_IDM_ALL_ACCOUNTS;
use kanidmd_lib::prelude::uri::{OAUTH2_AUTHORISE_DEVICE, OAUTH2_TOKEN_ENDPOINT};
use kanidmd_lib::prelude::{Attribute, OAUTH2_SCOPE_EMAIL, OAUTH2_SCOPE_OPENID, OAUTH2_SCOPE_READ};
use kanidmd_testkit::{
    assert_no_cache, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER, IDM_ADMIN_TEST_PASSWORD,
    IDM_ADMIN_TEST_USER, NOT_ADMIN_TEST_EMAIL, NOT_ADMIN_TEST_PASSWORD, NOT_ADMIN_TEST_USERNAME,
    TEST_INTEGRATION_RS_DISPLAY, TEST_INTEGRATION_RS_GROUP_ALL, TEST_INTEGRATION_RS_ID,
    TEST_INTEGRATION_RS_REDIRECT_URL, TEST_INTEGRATION_RS_URL,
};

use oauth2_ext::basic::BasicClient;
use oauth2_ext::http::StatusCode;
use oauth2_ext::{
    AuthUrl, ClientId, DeviceAuthorizationUrl, HttpRequest, HttpResponse, PkceCodeChallenge,
    RequestTokenError, Scope, StandardDeviceAuthorizationResponse, StandardErrorResponse, TokenUrl,
};
use reqwest::Client;
use tracing::{debug, error, info};
use url::Url;

#[cfg(feature = "dev-oauth2-device-flow")]
async fn http_client(
    request: HttpRequest,
) -> Result<HttpResponse, oauth2_ext::reqwest::Error<reqwest::Error>> {
    // let ca_contents = std::fs::read("/tmp/kanidm/ca.pem")
    //     .map_err(|err| oauth2::reqwest::Error::Other(err.to_string()))?;

    let client = Client::builder()
        .danger_accept_invalid_certs(true)
        // Following redirects opens the client up to SSRF vulnerabilities.
        .redirect(reqwest::redirect::Policy::none())
        // reqwest::Certificate::from_der(&ca_contents)
        // .map_err(oauth2::reqwest::Error::Reqwest)?,
        // )
        .build()
        .map_err(oauth2_ext::reqwest::Error::Reqwest)?;

    let method = reqwest::Method::from_str(request.method.as_str())
        .map_err(|err| oauth2_ext::reqwest::Error::Other(err.to_string()))?;

    let mut request_builder = client
        .request(method, request.url.as_str())
        .body(request.body);

    for (name, value) in &request.headers {
        request_builder = request_builder.header(name.as_str(), value.as_bytes());
    }

    let response = client
        .execute(request_builder.build().map_err(|err| {
            error!("Failed to build request... {:?}", err);
            oauth2_ext::reqwest::Error::Reqwest(err)
        })?)
        .await
        .map_err(|err| {
            error!("Failed to query url {} error={:?}", request.url, err);
            oauth2_ext::reqwest::Error::Reqwest(err)
        })?;

    let status_code = StatusCode::from_u16(response.status().as_u16())
        .map_err(|err| oauth2_ext::reqwest::Error::Other(err.to_string()))?;
    let headers = response
        .headers()
        .into_iter()
        .map(|(k, v)| {
            debug!("header key={:?} value={:?}", k, v);
            (
                oauth2_ext::http::HeaderName::from_str(k.as_str()).expect("Failed to parse header"),
                oauth2_ext::http::HeaderValue::from_str(
                    v.to_str().expect("Failed to parse header value"),
                )
                .expect("Failed to parse header value"),
            )
        })
        .collect();

    let body = response.bytes().await.map_err(|err| {
        error!("Failed to parse body...? {:?}", err);
        oauth2_ext::reqwest::Error::Reqwest(err)
    })?;
    info!("Response body: {:?}", String::from_utf8(body.to_vec()));

    Ok(HttpResponse {
        status_code,
        headers,
        body: body.to_vec(),
    })
}

#[cfg(feature = "dev-oauth2-device-flow")]
#[kanidmd_testkit::test]
async fn oauth2_device_flow(rsclient: KanidmClient) {
    let res = rsclient
        .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
        .await;
    assert!(res.is_ok());

    // Create an oauth2 application integration.
    rsclient
        .idm_oauth2_rs_public_create(
            TEST_INTEGRATION_RS_ID,
            TEST_INTEGRATION_RS_DISPLAY,
            TEST_INTEGRATION_RS_URL,
        )
        .await
        .expect("Failed to create oauth2 config");

    rsclient
        .idm_oauth2_client_add_origin(
            TEST_INTEGRATION_RS_ID,
            &Url::parse(TEST_INTEGRATION_RS_REDIRECT_URL).expect("Invalid URL"),
        )
        .await
        .expect("Failed to update oauth2 config");

    // Extend the admin account with extended details for openid claims.
    rsclient
        .idm_person_account_create(NOT_ADMIN_TEST_USERNAME, NOT_ADMIN_TEST_USERNAME)
        .await
        .expect("Failed to create account details");

    rsclient
        .idm_person_account_set_attr(
            NOT_ADMIN_TEST_USERNAME,
            Attribute::Mail.as_ref(),
            &[NOT_ADMIN_TEST_EMAIL],
        )
        .await
        .expect("Failed to create account mail");

    rsclient
        .idm_person_account_primary_credential_set_password(
            NOT_ADMIN_TEST_USERNAME,
            NOT_ADMIN_TEST_PASSWORD,
        )
        .await
        .expect("Failed to configure account password");

    rsclient
        .idm_oauth2_rs_update(TEST_INTEGRATION_RS_ID, None, None, None, true, true, true)
        .await
        .expect("Failed to update oauth2 config");

    rsclient
        .idm_oauth2_rs_update_scope_map(
            TEST_INTEGRATION_RS_ID,
            NAME_IDM_ALL_ACCOUNTS,
            vec![OAUTH2_SCOPE_READ, OAUTH2_SCOPE_EMAIL, OAUTH2_SCOPE_OPENID],
        )
        .await
        .expect("Failed to update oauth2 scopes");

    rsclient
        .idm_oauth2_rs_update_sup_scope_map(
            TEST_INTEGRATION_RS_ID,
            NAME_IDM_ALL_ACCOUNTS,
            vec![ADMIN_TEST_USER],
        )
        .await
        .expect("Failed to update oauth2 scopes");

    // Add a custom claim map.
    rsclient
        .idm_oauth2_rs_update_claim_map(
            TEST_INTEGRATION_RS_ID,
            "test_claim",
            NAME_IDM_ALL_ACCOUNTS,
            &["claim_a".to_string(), "claim_b".to_string()],
        )
        .await
        .expect("Failed to update oauth2 claims");

    // Set an alternate join
    rsclient
        .idm_oauth2_rs_update_claim_map_join(
            TEST_INTEGRATION_RS_ID,
            "test_claim",
            Oauth2ClaimMapJoin::Ssv,
        )
        .await
        .expect("Failed to update oauth2 claims");

    // Get our admin's auth token for our new client.
    // We have to re-auth to update the mail field.
    let res = rsclient
        .auth_simple_password(IDM_ADMIN_TEST_USER, IDM_ADMIN_TEST_PASSWORD)
        .await;
    assert!(res.is_ok());

    // set up the device flow values

    let rsdata = rsclient
        .idm_oauth2_rs_get(TEST_INTEGRATION_RS_ID)
        .await
        .expect("failed to query rs")
        .expect("failed to get rsdata");

    dbg!(&rsdata);

    assert!(
        !rsdata
            .attrs
            .contains_key(Attribute::OAuth2DeviceFlowEnable.as_str()),
        "Found device flow enable attribute, shouldn't be there yet!"
    );

    rsclient
        .idm_oauth2_client_device_flow_update(TEST_INTEGRATION_RS_ID, false)
        .await
        .expect("Failed to update oauth2 config to disable device flow");

    rsclient
        .idm_oauth2_client_device_flow_update(TEST_INTEGRATION_RS_ID, true)
        .await
        .expect("Failed to update oauth2 config to enable device flow");

    let rsdata = rsclient
        .idm_oauth2_rs_get(TEST_INTEGRATION_RS_ID)
        .await
        .expect("failed to query rs")
        .expect("failed to get rsdata");

    dbg!(&rsdata);

    assert!(
        rsdata
            .attrs
            .contains_key(Attribute::OAuth2DeviceFlowEnable.as_str()),
        "Couldn't find device flow enable attribute"
    );
    assert_eq!(
        rsdata
            .attrs
            .get(Attribute::OAuth2DeviceFlowEnable.as_str())
            .expect("Couldn't find device flow enable attribute"),
        &vec!["true".to_string()],
        "Device flow enable attribute not set to true"
    );

    // ok we've checked that adding the thing works.
    // now we need to test the device flow itself.

    // first we need to get the device code.

    // kanidm system oauth2 create-public device_flow device_flow 'https://deviceauth'
    let client = BasicClient::new(
        ClientId::new(TEST_INTEGRATION_RS_ID.to_string()),
        None,
        AuthUrl::new(rsclient.make_url(OAUTH2_AUTHORISE).to_string())
            .expect("Failed to build authurl"),
        Some(
            TokenUrl::new(rsclient.make_url(OAUTH2_TOKEN_ENDPOINT).to_string())
                .expect("Failed to build token url"),
        ),
    )
    .set_device_authorization_url(
        DeviceAuthorizationUrl::new(rsclient.make_url(OAUTH2_AUTHORISE_DEVICE).to_string())
            .expect("Failed to build DeviceAuthorizationUrl"),
    );

    let details: StandardDeviceAuthorizationResponse = client
        .exchange_device_code()
        .expect("Failed to exchange device code")
        .add_scope(Scope::new("read".to_string()))
        .request_async(http_client)
        .await
        .expect("Failed to get device code!");

    debug!("{:?}", details);
    dbg!(&details.device_code().secret());
    assert!(details.device_code().secret().len() == 24);

    // now take that device code and get the token... glhf!

    let result = client
        .exchange_device_access_token(&details)
        .request_async(
            http_client,
            tokio::time::sleep,
            Some(std::time::Duration::from_secs(1)),
        )
        .await;

    assert!(result.is_err());
    let err = result.err().expect("Failed to get error");
    dbg!(&err.to_string());
    assert!(err.to_string().contains("Server returned error response"));
}
