use larksuite_oapi_sdk_rs::Client;
use larksuite_oapi_sdk_rs::constants::AppType;
use std::time::Duration;

mod common;

#[tokio::test]
async fn all_service_accessors() {
    let client = Client::builder("app", "secret").build().unwrap();
    let _ = client.config();
    let _ = client.admin();
    let _ = client.acs();
    let _ = client.aily();
    let _ = client.application();
    let _ = client.application_v6();
    let _ = client.attendance();
    let _ = client.approval();
    let _ = client.auth();
    let _ = client.authen();
    let _ = client.bitable();
    let _ = client.calendar();
    let _ = client.contact();
    let _ = client.corehr();
    let _ = client.corehr_v2();
    let _ = client.docx();
    let _ = client.drive();
    let _ = client.drive_v2();
    let _ = client.helpdesk();
    let _ = client.hire();
    let _ = client.hire_v2();
    let _ = client.im();
    let _ = client.sheets();
    let _ = client.task();
    let _ = client.im_v2();
    let _ = client.task_v2();
    let _ = client.tenant();
    let _ = client.baike();
    let _ = client.lingo();
    let _ = client.mail();
    let _ = client.minutes();
    let _ = client.okr();
    let _ = client.translation();
    let _ = client.search();
    let _ = client.vc();
    #[allow(deprecated)]
    let _ = client.wiki();
    let _ = client.wiki_v2();
    let _ = client.wiki_v1();
    let _ = client.passport();
    let _ = client.report();
    let _ = client.workplace();
    let _ = client.face_detection();
    let _ = client.human_authentication();
    let _ = client.optical_char_recognition();
    let _ = client.speech_to_text();
    let _ = client.verification();
    let _ = client.document_ai();
    let _ = client.mdm();
    let _ = client.mdm_v3();
    let _ = client.personal_settings();
    let _ = client.security_and_compliance();
    let _ = client.security_and_compliance_v2();
    let _ = client.moments();
    let _ = client.meeting_room();
    let _ = client.ehr();
    let _ = client.compensation();
    let _ = client.payroll();
    let _ = client.performance();
    let _ = client.performance_v2();
    let _ = client.directory();
    let _ = client.docs();
    let _ = client.apaas();
    let _ = client.base_v2();
    let _ = client.block();
    let _ = client.block_v2();
    let _ = client.board();
    let _ = client.cardkit();
    let _ = client.event();
    let _ = client.ext();
}

#[cfg(feature = "ws")]
#[tokio::test]
async fn ws_client_accessor() {
    let client = Client::builder("app", "secret").build().unwrap();
    let dispatcher = larksuite_oapi_sdk_rs::EventDispatcher::new("", "");
    let _ = client.ws_client(dispatcher);
}

#[tokio::test]
async fn builder_defaults() {
    let client = Client::builder("app_id", "app_secret").build().unwrap();
    let config = client.config();
    assert_eq!(config.app_id(), "app_id");
    assert_eq!(config.app_secret(), "app_secret");
    assert_eq!(config.app_type(), AppType::SelfBuilt);
    assert!(config.enable_token_cache());
    assert_eq!(config.max_retries(), 2);
}

#[tokio::test]
async fn builder_marketplace() {
    let client = Client::builder("app", "secret")
        .marketplace()
        .build()
        .unwrap();
    assert_eq!(client.config().app_type(), AppType::Marketplace);
}

#[tokio::test]
async fn builder_base_url() {
    let client = Client::builder("app", "secret")
        .base_url("https://open.larksuite.com")
        .build()
        .unwrap();
    assert_eq!(client.config().base_url(), "https://open.larksuite.com");
}

#[tokio::test]
async fn builder_disable_token_cache() {
    let client = Client::builder("app", "secret")
        .disable_token_cache()
        .build()
        .unwrap();
    assert!(!client.config().enable_token_cache());
}

#[tokio::test]
async fn builder_timeout() {
    let client = Client::builder("app", "secret")
        .timeout(Duration::from_secs(10))
        .build()
        .unwrap();
    assert_eq!(client.config().req_timeout(), Duration::from_secs(10));
}

#[tokio::test]
async fn builder_helpdesk_credential() {
    let client = Client::builder("app", "secret")
        .helpdesk_credential("hd_id", "hd_token")
        .build()
        .unwrap();
    let config = client.config();
    assert_eq!(config.helpdesk_id(), Some("hd_id"));
    assert_eq!(config.helpdesk_token(), Some("hd_token"));
    assert!(config.helpdesk_auth_token().is_some());
    let decoded = base64::Engine::decode(
        &base64::engine::general_purpose::STANDARD,
        config.helpdesk_auth_token().unwrap(),
    )
    .unwrap();
    assert_eq!(String::from_utf8(decoded).unwrap(), "hd_id:hd_token");
}

#[tokio::test]
async fn builder_skip_sign_verify() {
    let client = Client::builder("app", "secret")
        .skip_sign_verify()
        .build()
        .unwrap();
    assert!(client.config().skip_sign_verify());
}

#[tokio::test]
async fn builder_max_retries() {
    let client = Client::builder("app", "secret")
        .max_retries(5)
        .build()
        .unwrap();
    assert_eq!(client.config().max_retries(), 5);
}

#[tokio::test]
async fn builder_log_level() {
    let client = Client::builder("app", "secret")
        .log_level(tracing::Level::TRACE)
        .build()
        .unwrap();
    assert_eq!(client.config().log_level(), Some(tracing::Level::TRACE));
}

#[tokio::test]
async fn builder_default_headers() {
    use http::HeaderMap;
    let mut headers = HeaderMap::new();
    headers.insert("X-Custom", "value".parse().unwrap());
    let client = Client::builder("app", "secret")
        .default_headers(headers)
        .build()
        .unwrap();
    assert_eq!(
        client
            .config()
            .default_headers()
            .get("X-Custom")
            .unwrap()
            .to_str()
            .unwrap(),
        "value"
    );
}

#[tokio::test]
async fn builder_custom_token_cache() {
    use larksuite_oapi_sdk_rs::cache::{Cache, LocalCache};
    use std::sync::Arc;

    let cache = Arc::new(LocalCache::new());
    cache
        .set("pre", "val", Duration::from_secs(60))
        .await
        .unwrap();

    let client = Client::builder("app", "secret")
        .token_cache(cache.clone())
        .build()
        .unwrap();

    let val = client.config().token_cache().get("pre").await.unwrap();
    assert_eq!(val.as_deref(), Some("val"));
}

#[tokio::test]
async fn client_and_builder_debug() {
    let builder = Client::builder("app", "secret");
    let debug = format!("{builder:?}");
    assert!(debug.contains("ClientBuilder"));

    let client = builder.build().unwrap();
    let debug = format!("{client:?}");
    assert!(debug.contains("Client"));
    assert!(debug.contains("app_id"));
}

#[tokio::test]
async fn builder_log_req_at_debug() {
    let client = Client::builder("app", "secret")
        .log_req_at_debug()
        .build()
        .unwrap();
    assert!(client.config().log_req_at_debug());
}

#[tokio::test]
async fn builder_log_req_at_debug_default_false() {
    let client = Client::builder("app", "secret").build().unwrap();
    assert!(!client.config().log_req_at_debug());
}

#[tokio::test]
async fn app_ticket_manager_shares_cache() {
    use std::time::Duration;

    let client = Client::builder("app", "secret").build().unwrap();
    let atm = client.app_ticket_manager();

    atm.set("app", "ticket_value", Duration::from_secs(60))
        .await
        .unwrap();

    let val = client
        .config()
        .token_cache()
        .get("app_ticket-app")
        .await
        .unwrap();
    assert_eq!(val.as_deref(), Some("ticket_value"));
}

#[tokio::test]
async fn download_file_success() {
    let body = "file bytes here";
    let (addr, _handle) = common::mock_server(vec![common::http_response_with_headers(
        200,
        "Content-Type: application/octet-stream\r\n",
        body,
    )])
    .await;

    let client = Client::builder("app", "secret")
        .base_url(format!("http://{addr}"))
        .build()
        .unwrap();

    let bytes = client
        .download_file(&format!("http://{addr}/some/file"))
        .await
        .unwrap();
    assert_eq!(bytes, body.as_bytes());
}

#[tokio::test]
async fn download_file_404_returns_error() {
    let (addr, _handle) = common::mock_server(vec![common::http_response(404, "not found")]).await;

    let client = Client::builder("app", "secret")
        .base_url(format!("http://{addr}"))
        .build()
        .unwrap();

    let result = client
        .download_file(&format!("http://{addr}/missing"))
        .await;
    assert!(result.is_err());
}
