/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
 */

use crate::{
    directory::internal::TestInternalDirectory,
    imap::{ImapConnection, Type},
    jmap::{JMAPTest},
};
use common::listener::blocked::BLOCKED_IP_KEY;
use directory::Permission;
use imap_proto::ResponseType;
use jmap_client::{
    client::{Client, Credentials},
    core::set::{SetError, SetErrorType},
    mailbox::{self},
};
use std::{
    net::{IpAddr, Ipv4Addr},
    sync::Arc,
    time::Duration,
};
use store::write::now;

pub async fn test(params: &mut JMAPTest) {
    println!("Running Authorization tests...");

    // Create test account
    let server = params.server.clone();
    let account = params.account("jdoe@example.com");

    // Remove unlimited requests permission
    params
        .server
        .store()
        .remove_permissions(account.name(), [Permission::UnlimitedRequests])
        .await;
    params.server.inner.cache.access_tokens.clear();

    // Reset rate limiters
    params.webhook.clear();

    // Incorrect passwords should be rejected with a 401 error
    assert!(matches!(
        Client::new()
            .credentials(Credentials::basic("jdoe@example.com", "abcde"))
            .accept_invalid_certs(true) .follow_redirects(["127.0.0.1"])
            .connect("https://127.0.0.1:8899")
            .await,
        Err(jmap_client::Error::Problem(err)) if err.status() == Some(401)));

    // Wait until the beginning of the 5 seconds bucket
    const LIMIT: u64 = 5;
    let now = now();
    let range_start = now / LIMIT;
    let range_end = (range_start * LIMIT) + LIMIT;
    tokio::time::sleep(Duration::from_secs(range_end - now)).await;

    // Test fail2ban
    assert_eq!(
        server
            .core
            .storage
            .config
            .get(format!("{BLOCKED_IP_KEY}.127.0.0.1"))
            .await
            .unwrap(),
        None
    );
    for n in 0..98 {
        match Client::new()
            .credentials(Credentials::basic(
                "not_an_account@example.com",
                &format!("brute_force{}", n),
            ))
            .accept_invalid_certs(true)
            .follow_redirects(["127.0.0.1"])
            .connect("https://127.0.0.1:8899")
            .await
        {
            Err(jmap_client::Error::Problem(_)) => {}
            Err(err) => {
                panic!("Unexpected response: {:?}", err);
            }
            Ok(_) => {
                panic!("Unexpected success");
            }
        }
    }

    let mut imap = ImapConnection::connect(b"_x ").await;
    imap.send("AUTHENTICATE PLAIN AGpvaG4AY2hpbWljaGFuZ2Fz")
        .await;
    imap.assert_read(Type::Tagged, ResponseType::No).await;

    // There are already 100 failed login attempts for this IP address
    // so the next one should be rejected, even if done over IMAP
    imap.send("AUTHENTICATE PLAIN AGpvaG4AY2hpbWljaGFuZ2Fz")
        .await;
    imap.assert_disconnect().await;

    // Make sure the IP address is blocked
    assert_eq!(
        server
            .core
            .storage
            .config
            .get(format!("{BLOCKED_IP_KEY}.127.0.0.1"))
            .await
            .unwrap(),
        Some(String::new())
    );
    ImapConnection::connect(b"_y ")
        .await
        .assert_disconnect()
        .await;

    // Lift ban
    server
        .core
        .storage
        .config
        .clear(format!("{BLOCKED_IP_KEY}.127.0.0.1"))
        .await
        .unwrap();
    server
        .inner
        .data
        .blocked_ips
        .write()
        .remove(&IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));

    // Valid authentication requests should not be rate limited
    for _ in 0..110 {
        Client::new()
            .credentials(Credentials::basic(account.name(), account.secret()))
            .accept_invalid_certs(true)
            .follow_redirects(["127.0.0.1"])
            .connect("https://127.0.0.1:8899")
            .await
            .unwrap();
    }

    // Login with the correct credentials
    let client = Client::new()
        .credentials(Credentials::basic(account.name(), account.secret()))
        .accept_invalid_certs(true)
        .follow_redirects(["127.0.0.1"])
        .connect("https://127.0.0.1:8899")
        .await
        .unwrap();
    assert_eq!(client.session().username(), account.name());
    assert_eq!(
        client
            .session()
            .account(account.id_string())
            .unwrap()
            .name(),
        account.name()
    );
    assert!(
        client
            .session()
            .account(account.id_string())
            .unwrap()
            .is_personal()
    );

    // Uploads up to 5000000 bytes should be allowed
    assert_eq!(
        client
            .upload(None, vec![b'A'; 5000000], None)
            .await
            .unwrap()
            .size(),
        5000000
    );
    assert!(
        client
            .upload(None, vec![b'A'; 5000001], None)
            .await
            .is_err()
    );

    // Users should be allowed to create identities only
    // using email addresses associated to their principal
    let iid1 = client
        .identity_create("John Doe", "jdoe@example.com")
        .await
        .unwrap()
        .take_id();
    let iid2 = client
        .identity_create("John Doe (secondary)", "john.doe@example.com")
        .await
        .unwrap()
        .take_id();
    assert!(matches!(
        client
            .identity_create("John the Spammer", "spammy@mcspamface.com")
            .await,
        Err(jmap_client::Error::Set(SetError {
            type_: SetErrorType::InvalidProperties,
            ..
        }))
    ));
    client.identity_destroy(&iid1).await.unwrap();
    client.identity_destroy(&iid2).await.unwrap();

    // Concurrent requests check
    let client = Arc::new(client);
    for _ in 0..8 {
        let client_ = client.clone();
        tokio::spawn(async move {
            let _ = client_
                .mailbox_query(
                    mailbox::query::Filter::name("__sleep").into(),
                    [mailbox::query::Comparator::name()].into(),
                )
                .await;
        });
    }
    tokio::time::sleep(Duration::from_millis(500)).await;
    assert!(matches!(
        client
            .mailbox_query(
                mailbox::query::Filter::name("__sleep").into(),
                [mailbox::query::Comparator::name()].into(),
            )
            .await,
            Err(jmap_client::Error::Problem(err)) if err.status() == Some(400)));

    // Wait for sleep to be done
    tokio::time::sleep(Duration::from_millis(1000)).await;

    // Concurrent upload test
    for _ in 0..4 {
        let client_ = client.clone();
        tokio::spawn(async move {
            client_.upload(None, b"sleep".to_vec(), None).await.unwrap();
        });
    }
    tokio::time::sleep(Duration::from_millis(500)).await;
    assert!(matches!(
        client.upload(None, b"sleep".to_vec(), None).await,
        Err(jmap_client::Error::Problem(err)) if err.status() == Some(400)));

    // Add unlimited requests permission
    params
        .server
        .store()
        .add_permissions(account.name(), [Permission::UnlimitedRequests])
        .await;
    params.server.inner.cache.access_tokens.clear();

    // Destroy test accounts
    params.destroy_all_mailboxes(account).await;
    params.assert_is_empty().await;

    // Check webhook events
    params
        .webhook
        .assert_contains(&["auth.failed", "auth.success", "security.authentication-ban"]);
}
