extern crate chrono;
extern crate orizentic;

use orizentic::filedb::*;
use orizentic::*;
use std::fs;
use std::ops;
use std::thread;
use std::time;

struct FileCleanup(String);

impl FileCleanup {
    fn new(path: &str) -> FileCleanup {
        FileCleanup(String::from(path))
    }
}

impl ops::Drop for FileCleanup {
    fn drop(&mut self) {
        fs::remove_file(&self.0).expect("failed to remove time series file");
    }
}

#[test]
fn can_create_a_new_claimset() {
    let mut ctx = OrizenticCtx::new(Secret("abcdefg".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-1")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims.clone());
    assert_eq!(claims.audience, Username::from("Savanni"));
    match claims.expiration {
        Some(ttl) => assert_eq!(ttl - claims.issued_at, chrono::Duration::seconds(3600)),
        None => panic!("ttl should not be None"),
    }
    assert_eq!(claims.issuer, Issuer(String::from("test")));
    assert_eq!(claims.resource, ResourceName(String::from("resource-1")));
    assert_eq!(
        claims.permissions,
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ])
    );
    {
        let tok_list = ctx.list_claimsets();
        assert_eq!(tok_list.len(), 1);
        assert!(tok_list.contains(&&claims));
    }

    let claims2 = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-2")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims2.clone());

    assert_ne!(claims2.id, claims.id);
    assert_eq!(claims2.resource, ResourceName(String::from("resource-2")));

    let tok_list = ctx.list_claimsets();
    assert_eq!(tok_list.len(), 2);
    assert!(tok_list.contains(&&claims));
    assert!(tok_list.contains(&&claims2));
}

#[test]
fn can_retrieve_claim_by_id() {
    let mut ctx = OrizenticCtx::new(Secret("abcdefg".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-1")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    let claims2 = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-2")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims.clone());
    ctx.add_claimset(claims2.clone());

    assert_eq!(ctx.find_claimset(&claims.id), Some(&claims));
    assert_eq!(ctx.find_claimset(&claims2.id), Some(&claims2));

    ctx.revoke_claimset(&claims);
    assert_eq!(ctx.find_claimset(&claims.id), None);
    assert_eq!(ctx.find_claimset(&claims2.id), Some(&claims2));
}

#[test]
fn can_revoke_claim_by_id() {
    let mut ctx = OrizenticCtx::new(Secret("abcdefg".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-1")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    let claims2 = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-2")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );

    ctx.add_claimset(claims.clone());
    ctx.add_claimset(claims2.clone());

    assert_eq!(ctx.find_claimset(&claims.id), Some(&claims));
    assert_eq!(ctx.find_claimset(&claims2.id), Some(&claims2));

    ctx.revoke_by_uuid(&claims.id);
    assert_eq!(ctx.find_claimset(&claims.id), None);
    assert_eq!(ctx.find_claimset(&claims2.id), Some(&claims2));
}

#[test]
fn can_revoke_a_token() {
    let mut ctx = OrizenticCtx::new(Secret("abcdefg".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-1")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    let claims2 = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-2")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims.clone());
    ctx.add_claimset(claims2.clone());

    ctx.revoke_claimset(&claims);
    let tok_list = ctx.list_claimsets();
    assert_eq!(tok_list.len(), 1);
    assert!(!tok_list.contains(&&claims));
    assert!(tok_list.contains(&&claims2));
}

#[test]
fn rejects_tokens_with_an_invalid_secret() {
    let mut ctx1 = OrizenticCtx::new(Secret("ctx1".to_string().into_bytes()), Vec::new());
    let ctx2 = OrizenticCtx::new(Secret("ctx2".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-1")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx1.add_claimset(claims.clone());
    let encoded_token = ctx1.encode_claimset(&claims).ok().unwrap();
    assert!(ctx2.decode_and_validate_text(encoded_token.text).is_err());
}

#[test]
fn rejects_tokens_that_are_absent_from_the_database() {
    let mut ctx = OrizenticCtx::new(Secret("ctx".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-1")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims.clone());
    let encoded_token = ctx.encode_claimset(&claims).ok().unwrap();

    ctx.revoke_claimset(&claims);
    assert!(ctx.decode_and_validate_text(encoded_token.text).is_err());
}

#[test]
fn validates_present_tokens_with_a_valid_secret() {
    let mut ctx = OrizenticCtx::new(Secret("ctx".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-1")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims.clone());
    let encoded_token = ctx.encode_claimset(&claims).ok().unwrap();
    assert!(ctx.decode_and_validate_text(encoded_token.text).is_ok());
}

#[test]
fn rejects_expired_tokens() {
    let mut ctx = OrizenticCtx::new(Secret("ctx".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(1))),
        ResourceName(String::from("resource-1")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims.clone());
    thread::sleep(time::Duration::from_secs(2));
    let encoded_token = ctx.encode_claimset(&claims).ok().unwrap();
    assert!(ctx.decode_and_validate_text(encoded_token.text).is_err());
}

#[test]
fn accepts_tokens_that_have_no_expiration() {
    let mut ctx = OrizenticCtx::new(Secret("ctx".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        None,
        ResourceName(String::from("resource-1")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims.clone());
    let encoded_token = ctx.encode_claimset(&claims).ok().unwrap();
    assert!(ctx.decode_and_validate_text(encoded_token.text).is_ok());
}

#[test]
fn authorizes_a_token_with_the_correct_resource_and_permissions() {
    let mut ctx = OrizenticCtx::new(Secret("ctx".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        None,
        ResourceName(String::from("resource-1")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims.clone());
    let encoded_token = ctx.encode_claimset(&claims).ok().unwrap();
    let token = ctx
        .decode_and_validate_text(encoded_token.text)
        .ok()
        .unwrap();
    let res = token.check_authorizations(|rn: &ResourceName, perms: &Permissions| {
        *rn == ResourceName(String::from("resource-1")) && perms.0.contains(&String::from("grant"))
    });
    assert!(res);
}

#[test]
fn rejects_a_token_with_the_incorrect_permissions() {
    let mut ctx = OrizenticCtx::new(Secret("ctx".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        None,
        ResourceName(String::from("resource-1")),
        Username::from("Savanni"),
        Permissions(vec![String::from("read"), String::from("write")]),
    );
    ctx.add_claimset(claims.clone());
    let encoded_token = ctx.encode_claimset(&claims).ok().unwrap();
    let token = ctx
        .decode_and_validate_text(encoded_token.text)
        .ok()
        .unwrap();
    let res = token.check_authorizations(|rn: &ResourceName, perms: &Permissions| {
        *rn == ResourceName(String::from("resource-1")) && perms.0.contains(&String::from("grant"))
    });
    assert!(!res);
}

#[test]
fn rejects_a_token_with_the_incorrect_resource_name() {
    let mut ctx = OrizenticCtx::new(Secret("ctx".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        None,
        ResourceName(String::from("resource-2")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims.clone());
    let encoded_token = ctx.encode_claimset(&claims).ok().unwrap();
    let token = ctx
        .decode_and_validate_text(encoded_token.text)
        .ok()
        .unwrap();
    let res = token.check_authorizations(|rn: &ResourceName, perms: &Permissions| {
        *rn == ResourceName(String::from("resource-1")) && perms.0.contains(&String::from("grant"))
    });
    assert!(!res);
}

#[test]
fn claims_serialize_to_json() {
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        None,
        ResourceName(String::from("resource-2")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );

    let expected_jti = format!("\"jti\":\"{}\"", claims.id);

    let claim_str = claims.to_json().expect("to_json threw an error");
    //.expect(assert!(false, format!("[claims_serilazie_to_json] {}", err)));
    assert!(claim_str.contains(&expected_jti));

    let claims_ = ClaimSet::from_json(&claim_str).expect("from_json threw an error");
    assert_eq!(claims, claims_);
}

#[test]
fn save_and_load() {
    let _file_cleanup = FileCleanup::new("var/claims.db");
    let mut ctx = OrizenticCtx::new(Secret("ctx".to_string().into_bytes()), Vec::new());
    let claims = ClaimSet::new(
        Issuer(String::from("test")),
        None,
        ResourceName(String::from("resource-2")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims.clone());

    let claims2 = ClaimSet::new(
        Issuer(String::from("test")),
        Some(TTL(chrono::Duration::seconds(3600))),
        ResourceName(String::from("resource-2")),
        Username::from("Savanni"),
        Permissions(vec![
            String::from("read"),
            String::from("write"),
            String::from("grant"),
        ]),
    );
    ctx.add_claimset(claims2.clone());

    let res = save_claims_to_file(&ctx.list_claimsets(), &String::from("var/claims.db"));
    assert!(res.is_ok());

    let claimset = load_claims_from_file(&String::from("var/claims.db"));
    match claimset {
        Ok(claimset_) => {
            assert!(claimset_.contains(&claims));
            assert!(claimset_.contains(&claims2));
        }
        Err(err) => assert!(false, "{}", err),
    }
}
