use std::collections::HashMap;
use std::sync::{Arc, Mutex};

use larksuite_oapi_sdk_rs::event::{
    CallbackAction, CardAction, CardActionHandler, CardHandlerResult, CustomResp, EventDispatcher,
    EventReq, EventResp, ToastI18n,
};

// ── EventResp ──

#[test]
fn event_resp_success_has_json_content_type() {
    let resp = EventResp::success(serde_json::json!({"ok": true}));
    assert_eq!(resp.status_code, 200);
    assert_eq!(
        resp.headers.get("Content-Type").unwrap(),
        "application/json"
    );
    let parsed: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert_eq!(parsed["ok"], true);
}

#[test]
fn event_resp_error_has_msg() {
    let resp = EventResp::error(500, "internal error");
    assert_eq!(resp.status_code, 500);
    let parsed: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert_eq!(parsed["msg"], "internal error");
}

// ── EventDispatcher: url_verification with wrong token ──

#[tokio::test]
async fn url_verification_wrong_token_rejected() {
    let dispatcher = EventDispatcher::new("correct_token", "");
    let body = serde_json::json!({
        "type": "url_verification",
        "token": "wrong_token",
        "challenge": "test"
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

#[tokio::test]
async fn url_verification_empty_token_accepts_any() {
    let dispatcher = EventDispatcher::new("", "");
    let body = serde_json::json!({
        "type": "url_verification",
        "token": "anything",
        "challenge": "abc"
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
    let parsed: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert_eq!(parsed["challenge"], "abc");
}

// ── EventDispatcher: encrypted body ──

#[tokio::test]
async fn encrypted_url_verification() {
    use base64::Engine as _;
    use sha2::{Digest, Sha256};

    let encrypt_key = "test_encrypt_key";
    let plaintext = serde_json::json!({
        "type": "url_verification",
        "token": "",
        "challenge": "encrypted_challenge"
    });
    let plaintext_str = serde_json::to_string(&plaintext).unwrap();

    let key_bytes: [u8; 32] = {
        let mut h = Sha256::new();
        h.update(encrypt_key.as_bytes());
        h.finalize().into()
    };

    let iv: [u8; 16] = [1; 16];
    let ciphertext = {
        use aes::cipher::{BlockModeEncrypt, KeyIvInit};
        type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;

        let pad_len = 16 - (plaintext_str.len() % 16);
        let mut buf = plaintext_str.as_bytes().to_vec();
        buf.extend(std::iter::repeat_n(pad_len as u8, pad_len));
        let buf_len = buf.len();
        let enc = Aes256CbcEnc::new_from_slices(&key_bytes, &iv).unwrap();
        enc.encrypt_padded::<aes::cipher::block_padding::NoPadding>(&mut buf, buf_len)
            .unwrap()
            .to_vec()
    };

    let mut combined = iv.to_vec();
    combined.extend_from_slice(&ciphertext);
    let encoded = base64::engine::general_purpose::STANDARD.encode(&combined);

    let encrypted_body = serde_json::json!({ "encrypt": encoded });

    let dispatcher = EventDispatcher::new("", encrypt_key);
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&encrypted_body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
    let parsed: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert_eq!(parsed["challenge"], "encrypted_challenge");
}

// ── EventDispatcher: callback handler ──

#[tokio::test]
async fn callback_handler_returns_custom_response() {
    let dispatcher = EventDispatcher::new("", "").on_callback("card.action", |_val| async {
        Ok(serde_json::json!({"toast": {"type": "success"}}))
    });

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev1",
            "event_type": "card.action",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {"action": "click"}
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
    let parsed: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert_eq!(parsed["toast"]["type"], "success");
}

// ── EventDispatcher: handler error returns 500 ──

#[tokio::test]
async fn event_handler_error_returns_500() {
    let dispatcher = EventDispatcher::new("", "").on_event("test.event_v1", |_val| async {
        Err(larksuite_oapi_sdk_rs::error::LarkError::Event(
            "handler failed".to_string(),
        ))
    });

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev2",
            "event_type": "test.event_v1",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {}
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

// ── EventDispatcher: invalid utf8 body ──

#[tokio::test]
async fn invalid_utf8_body_returns_500() {
    let dispatcher = EventDispatcher::new("", "");
    let req = EventReq {
        headers: Default::default(),
        body: vec![0xFF, 0xFE],
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

// ── EventDispatcher: invalid json body ──

#[tokio::test]
async fn invalid_json_body_returns_500() {
    let dispatcher = EventDispatcher::new("", "");
    let req = EventReq {
        headers: Default::default(),
        body: b"not json".to_vec(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

// ── EventDispatcher: skip_sign_verify ──

#[tokio::test]
async fn skip_sign_verify_bypasses_signature_check() {
    let received = Arc::new(Mutex::new(false));
    let received_clone = Arc::clone(&received);

    let dispatcher = EventDispatcher::new("", "some_encrypt_key")
        .skip_sign_verify()
        .on_event("test.event_v1", move |_val| {
            let r = Arc::clone(&received_clone);
            async move {
                *r.lock().unwrap() = true;
                Ok(())
            }
        });

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev3",
            "event_type": "test.event_v1",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {}
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
    assert!(*received.lock().unwrap());
}

// ── CardActionHandler: url verification ──

#[tokio::test]
async fn card_handler_url_verification() {
    let handler = CardActionHandler::new("token123", "", |_action: CardAction| async {
        Ok(serde_json::json!({}))
    });

    let body = serde_json::json!({
        "type": "url_verification",
        "token": "token123",
        "challenge": "card_challenge"
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/card".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 200);
    let parsed: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert_eq!(parsed["challenge"], "card_challenge");
}

#[tokio::test]
async fn card_handler_wrong_verification_token() {
    let handler = CardActionHandler::new("correct", "", |_action: CardAction| async {
        Ok(serde_json::json!({}))
    });

    let body = serde_json::json!({
        "type": "url_verification",
        "token": "wrong",
        "challenge": "test"
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/card".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

// ── CardActionHandler: action dispatch ──

#[tokio::test]
async fn card_handler_dispatches_action() {
    let received_action = Arc::new(Mutex::new(None::<CallbackAction>));
    let received_clone = Arc::clone(&received_action);

    let handler = CardActionHandler::new("", "", move |action: CardAction| {
        let r = Arc::clone(&received_clone);
        async move {
            *r.lock().unwrap() = Some(action.action.clone());
            Ok(serde_json::json!({"status": "handled"}))
        }
    })
    .skip_sign_verify();

    let body = serde_json::json!({
        "open_id": "ou_123",
        "user_id": "u_456",
        "open_message_id": "om_789",
        "open_chat_id": "oc_abc",
        "tenant_key": "tk_1",
        "token": "",
        "action": {"tag": "button", "value": {"key": "val"}},
        "host": "",
        "delivery_type": ""
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/card".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 200);

    let action_val = received_action.lock().unwrap().clone().unwrap();
    assert_eq!(action_val.tag, "button");
}

// ── EventDispatcher: SHA-256 signature verification ──

fn compute_sha256_sig(timestamp: &str, nonce: &str, key: &str, body: &[u8]) -> String {
    use sha2::{Digest, Sha256};
    let mut content = String::new();
    content.push_str(timestamp);
    content.push_str(nonce);
    content.push_str(key);
    content.push_str(&String::from_utf8_lossy(body));
    let mut hasher = Sha256::new();
    hasher.update(content.as_bytes());
    hex::encode(hasher.finalize())
}

fn compute_sha1_sig(timestamp: &str, nonce: &str, key: &str, body: &str) -> String {
    use sha1::{Digest, Sha1};
    let mut content = String::new();
    content.push_str(timestamp);
    content.push_str(nonce);
    content.push_str(key);
    content.push_str(body);
    let mut hasher = Sha1::new();
    hasher.update(content.as_bytes());
    hex::encode(hasher.finalize())
}

#[tokio::test]
async fn event_sha256_signature_valid() {
    let encrypt_key = "test_sign_key";
    let timestamp = "1620000000";
    let nonce = "abc123";

    let received = Arc::new(Mutex::new(false));
    let received_clone = Arc::clone(&received);

    let dispatcher =
        EventDispatcher::new("", encrypt_key).on_event("im.message.receive_v1", move |_val| {
            let r = Arc::clone(&received_clone);
            async move {
                *r.lock().unwrap() = true;
                Ok(())
            }
        });

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_sig",
            "event_type": "im.message.receive_v1",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {}
    });
    let body_bytes = serde_json::to_vec(&body).unwrap();
    let sig = compute_sha256_sig(timestamp, nonce, encrypt_key, &body_bytes);

    let headers = HashMap::from([
        (
            "X-Lark-Request-Timestamp".to_string(),
            vec![timestamp.to_string()],
        ),
        ("X-Lark-Request-Nonce".to_string(), vec![nonce.to_string()]),
        ("X-Lark-Signature".to_string(), vec![sig]),
    ]);

    let req = EventReq {
        headers,
        body: body_bytes,
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
    assert!(*received.lock().unwrap());
}

#[tokio::test]
async fn event_sha256_signature_invalid_returns_500() {
    let encrypt_key = "test_sign_key";

    let dispatcher =
        EventDispatcher::new("", encrypt_key).on_event("test.event_v1", |_val| async { Ok(()) });

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_bad_sig",
            "event_type": "test.event_v1",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {}
    });
    let body_bytes = serde_json::to_vec(&body).unwrap();

    let headers = HashMap::from([
        (
            "X-Lark-Request-Timestamp".to_string(),
            vec!["1620000000".to_string()],
        ),
        (
            "X-Lark-Request-Nonce".to_string(),
            vec!["nonce".to_string()],
        ),
        (
            "X-Lark-Signature".to_string(),
            vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()],
        ),
    ]);

    let req = EventReq {
        headers,
        body: body_bytes,
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

#[tokio::test]
async fn event_missing_signature_header_returns_500() {
    let encrypt_key = "test_sign_key";

    let dispatcher =
        EventDispatcher::new("", encrypt_key).on_event("test.event_v1", |_val| async { Ok(()) });

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_no_sig",
            "event_type": "test.event_v1",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {}
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

// ── EventDispatcher: P1 protocol (no header, event type from event.type) ──

#[tokio::test]
async fn event_p1_protocol_dispatches_by_event_type() {
    let received = Arc::new(Mutex::new(false));
    let received_clone = Arc::clone(&received);

    let dispatcher = EventDispatcher::new("", "").on_event("app_open", move |_val| {
        let r = Arc::clone(&received_clone);
        async move {
            *r.lock().unwrap() = true;
            Ok(())
        }
    });

    let body = serde_json::json!({
        "event": {
            "type": "app_open",
            "app_id": "cli_test"
        }
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
    assert!(*received.lock().unwrap());
}

// ── CardActionHandler: SHA-1 signature verification ──

#[tokio::test]
async fn card_sha1_signature_valid() {
    let verification_token = "card_sign_key";
    let timestamp = "1620000000";
    let nonce = "xyz789";

    let received = Arc::new(Mutex::new(false));
    let received_clone = Arc::clone(&received);

    let handler = CardActionHandler::new(verification_token, "", move |_action: CardAction| {
        let r = Arc::clone(&received_clone);
        async move {
            *r.lock().unwrap() = true;
            Ok(serde_json::json!({}))
        }
    });

    let body = serde_json::json!({
        "open_id": "ou_1",
        "user_id": "u_1",
        "open_message_id": "om_1",
        "open_chat_id": "oc_1",
        "tenant_key": "tk",
        "token": "",
        "action": {},
        "host": "",
        "delivery_type": ""
    });
    let body_str = serde_json::to_string(&body).unwrap();
    let sig = compute_sha1_sig(timestamp, nonce, verification_token, &body_str);

    let headers = HashMap::from([
        (
            "X-Lark-Request-Timestamp".to_string(),
            vec![timestamp.to_string()],
        ),
        ("X-Lark-Request-Nonce".to_string(), vec![nonce.to_string()]),
        ("X-Lark-Signature".to_string(), vec![sig]),
    ]);

    let req = EventReq {
        headers,
        body: body_str.into_bytes(),
        request_uri: "/card".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 200);
    assert!(*received.lock().unwrap());
}

#[tokio::test]
async fn card_sha1_signature_invalid_returns_500() {
    let verification_token = "card_sign_key";

    let handler = CardActionHandler::new(verification_token, "", |_action: CardAction| async {
        Ok(serde_json::json!({}))
    });

    let body = serde_json::json!({
        "open_id": "ou_1",
        "user_id": "u_1",
        "open_message_id": "om_1",
        "open_chat_id": "oc_1",
        "tenant_key": "tk",
        "token": "",
        "action": {},
        "host": "",
        "delivery_type": ""
    });
    let body_bytes = serde_json::to_vec(&body).unwrap();

    let headers = HashMap::from([
        (
            "X-Lark-Request-Timestamp".to_string(),
            vec!["1620000000".to_string()],
        ),
        (
            "X-Lark-Request-Nonce".to_string(),
            vec!["nonce".to_string()],
        ),
        (
            "X-Lark-Signature".to_string(),
            vec!["0000000000000000000000000000000000000000".to_string()],
        ),
    ]);

    let req = EventReq {
        headers,
        body: body_bytes,
        request_uri: "/card".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

#[tokio::test]
async fn card_missing_signature_header_returns_500() {
    let verification_token = "card_sign_key";

    let handler = CardActionHandler::new(verification_token, "", |_action: CardAction| async {
        Ok(serde_json::json!({}))
    });

    let body = serde_json::json!({
        "open_id": "ou_1",
        "user_id": "u_1",
        "open_message_id": "om_1",
        "open_chat_id": "oc_1",
        "tenant_key": "tk",
        "token": "",
        "action": {},
        "host": "",
        "delivery_type": ""
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/card".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

// ── EventReq: serde ──

#[test]
fn event_req_serde_roundtrip() {
    let req = EventReq {
        headers: HashMap::from([("X-Test".to_string(), vec!["val".to_string()])]),
        body: b"hello".to_vec(),
        request_uri: "/test".to_string(),
    };
    let json = serde_json::to_string(&req).unwrap();
    let deserialized: EventReq = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.request_uri, "/test");
    assert_eq!(deserialized.headers.get("X-Test").unwrap(), &vec!["val"]);
}

// ── EventDispatcher: with_auto_app_ticket ──

#[tokio::test]
async fn auto_app_ticket_stores_ticket() {
    let dispatcher = EventDispatcher::new("", "")
        .skip_sign_verify()
        .with_auto_app_ticket();

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_ticket",
            "event_type": "app_ticket",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {
            "type": "app_ticket",
            "app_id": "test_app_123",
            "app_ticket": "ticket_value_abc"
        }
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
}

#[tokio::test]
async fn auto_app_ticket_empty_fields_noop() {
    let dispatcher = EventDispatcher::new("", "")
        .skip_sign_verify()
        .with_auto_app_ticket();

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_ticket2",
            "event_type": "app_ticket",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {
            "type": "app_ticket",
            "app_id": "",
            "app_ticket": ""
        }
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
}

// ── EventDispatcher: no handler returns 200 with message ──

#[tokio::test]
async fn event_no_handler_returns_200() {
    let dispatcher = EventDispatcher::new("", "").skip_sign_verify();

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_noop",
            "event_type": "unknown.event_v1",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {}
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
    let parsed: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert!(parsed["msg"].as_str().unwrap().contains("no handler"));
}

// ── CardActionHandler: invalid utf8 body returns 500 ──

#[tokio::test]
async fn card_handler_invalid_utf8_returns_500() {
    let handler = CardActionHandler::new("", "", |_action: CardAction| async {
        Ok(serde_json::json!({}))
    });
    let req = EventReq {
        headers: Default::default(),
        body: vec![0xFF, 0xFE],
        request_uri: "/card".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

// ── CardActionHandler: invalid json body returns 500 ──

#[tokio::test]
async fn card_handler_invalid_json_returns_500() {
    let handler = CardActionHandler::new("", "", |_action: CardAction| async {
        Ok(serde_json::json!({}))
    });
    let req = EventReq {
        headers: Default::default(),
        body: b"not json".to_vec(),
        request_uri: "/card".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

// ── CardActionHandler: handler error returns 500 ──

#[tokio::test]
async fn card_handler_error_returns_500() {
    let handler = CardActionHandler::new("", "", |_action: CardAction| async {
        Err(larksuite_oapi_sdk_rs::error::LarkError::Event(
            "handler failed".to_string(),
        ))
    })
    .skip_sign_verify();

    let body = serde_json::json!({
        "open_id": "ou_1",
        "user_id": "u_1",
        "open_message_id": "om_1",
        "open_chat_id": "oc_1",
        "tenant_key": "tk",
        "token": "",
        "action": {},
        "host": "",
        "delivery_type": ""
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/card".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

// ── EventDispatcher: encrypted body with decrypt error ──

#[tokio::test]
async fn event_encrypted_body_decrypt_error_returns_500() {
    let dispatcher = EventDispatcher::new("", "some_key");
    let body = serde_json::json!({ "encrypt": "!!!invalid-base64!!!" });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 500);
}

// ── EventDispatcher: on_customized_event ──

#[tokio::test]
async fn customized_event_handler_receives_full_req() {
    let captured_uri = Arc::new(Mutex::new(String::new()));
    let captured_header = Arc::new(Mutex::new(None));

    let uri_clone = captured_uri.clone();
    let header_clone = captured_header.clone();

    let dispatcher = EventDispatcher::new("", "")
        .skip_sign_verify()
        .on_customized_event(
            "im.message.receive_v1",
            move |req: EventReq, body: larksuite_oapi_sdk_rs::event::EventV2Body| {
                let uri = uri_clone.clone();
                let hdr = header_clone.clone();
                async move {
                    *uri.lock().unwrap() = req.request_uri.clone();
                    *hdr.lock().unwrap() = body.header;
                    Ok(())
                }
            },
        );

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_123",
            "event_type": "im.message.receive_v1",
            "app_id": "cli_test",
            "tenant_key": "tk",
            "create_time": "1234567890",
            "token": ""
        },
        "event": {
            "message": { "message_id": "om_abc" }
        }
    });

    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook/event".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
    assert_eq!(&*captured_uri.lock().unwrap(), "/webhook/event");
    let hdr = captured_header.lock().unwrap();
    assert_eq!(hdr.as_ref().unwrap().event_id, "ev_123");
    assert_eq!(hdr.as_ref().unwrap().app_id, "cli_test");
}

// ── EventReq::request_id ──

#[test]
fn event_req_request_id_from_logid() {
    let req = EventReq {
        headers: HashMap::from([("X-Tt-Logid".to_string(), vec!["log-abc-123".to_string()])]),
        body: vec![],
        request_uri: String::new(),
    };
    assert_eq!(req.request_id(), "log-abc-123");
}

#[test]
fn event_req_request_id_falls_back_to_request_id() {
    let req = EventReq {
        headers: HashMap::from([("X-Request-Id".to_string(), vec!["req-456".to_string()])]),
        body: vec![],
        request_uri: String::new(),
    };
    assert_eq!(req.request_id(), "req-456");
}

#[test]
fn event_req_request_id_empty_when_no_headers() {
    let req = EventReq {
        headers: HashMap::new(),
        body: vec![],
        request_uri: String::new(),
    };
    assert_eq!(req.request_id(), "");
}

// ── CardAction.req populated by handler ──

#[tokio::test]
async fn card_action_receives_event_req() {
    let captured_uri = Arc::new(Mutex::new(String::new()));
    let captured_id = Arc::new(Mutex::new(String::new()));
    let uri_clone = captured_uri.clone();
    let id_clone = captured_id.clone();

    let handler = CardActionHandler::new("", "", move |action: CardAction| {
        let uri = uri_clone.clone();
        let id = id_clone.clone();
        async move {
            if let Some(ref req) = action.req {
                *uri.lock().unwrap() = req.request_uri.clone();
                *id.lock().unwrap() = req.request_id().to_string();
            }
            Ok(serde_json::json!({ "ok": true }))
        }
    })
    .skip_sign_verify();

    let body = serde_json::json!({
        "open_id": "ou_1",
        "action": { "tag": "button" }
    });
    let req = EventReq {
        headers: HashMap::from([("X-Tt-Logid".to_string(), vec!["log-xyz".to_string()])]),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/card/action".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 200);
    assert_eq!(&*captured_uri.lock().unwrap(), "/card/action");
    assert_eq!(&*captured_id.lock().unwrap(), "log-xyz");
}

// ── CardAction.timezone field ──

#[tokio::test]
async fn card_action_timezone_field() {
    let captured_tz = Arc::new(Mutex::new(String::new()));
    let tz_clone = captured_tz.clone();

    let handler = CardActionHandler::new("", "", move |action: CardAction| {
        let tz = tz_clone.clone();
        async move {
            *tz.lock().unwrap() = action.timezone.clone();
            Ok(serde_json::json!({}))
        }
    })
    .skip_sign_verify();

    let body = serde_json::json!({
        "open_id": "ou_1",
        "timezone": "Asia/Shanghai",
        "action": {}
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: String::new(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 200);
    assert_eq!(&*captured_tz.lock().unwrap(), "Asia/Shanghai");
}

// ── CardActionHandler::new_custom with CustomResp ──

#[tokio::test]
async fn card_handler_custom_resp_status_and_body() {
    let handler = CardActionHandler::new_custom("", "", |_action: CardAction| async {
        Ok(CardHandlerResult::Custom(CustomResp {
            status_code: 202,
            body: serde_json::Map::from_iter([(
                "toast".to_string(),
                serde_json::json!({ "content": "Updated!" }),
            )]),
        }))
    })
    .skip_sign_verify();

    let body = serde_json::json!({
        "open_id": "ou_1",
        "action": { "tag": "button" }
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: String::new(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 202);
    let resp_body: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert_eq!(resp_body["toast"]["content"], "Updated!");
}

#[tokio::test]
async fn card_handler_custom_resp_zero_status_defaults_to_200() {
    let handler = CardActionHandler::new_custom("", "", |_action: CardAction| async {
        Ok(CardHandlerResult::Custom(CustomResp {
            status_code: 0,
            body: serde_json::Map::from_iter([("msg".to_string(), serde_json::json!("ok"))]),
        }))
    })
    .skip_sign_verify();

    let body = serde_json::json!({ "open_id": "ou_1", "action": {} });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: String::new(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 200);
}

#[tokio::test]
async fn card_handler_custom_json_variant() {
    let handler = CardActionHandler::new_custom("", "", |_action: CardAction| async {
        Ok(CardHandlerResult::Json(
            serde_json::json!({ "card": "data" }),
        ))
    })
    .skip_sign_verify();

    let body = serde_json::json!({ "open_id": "ou_1", "action": {} });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: String::new(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 200);
    let resp_body: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert_eq!(resp_body["card"], "data");
}

// ── EventDispatcher: parse_req and decrypt_event ──

#[test]
fn parse_req_returns_raw_body_when_no_encrypt_key() {
    let dispatcher = EventDispatcher::new("", "");
    let body = r#"{"schema":"2.0","event":{}}"#;
    let req = EventReq {
        headers: Default::default(),
        body: body.as_bytes().to_vec(),
        request_uri: String::new(),
    };
    let result = dispatcher.parse_req(&req).unwrap();
    assert_eq!(result, body);
}

#[test]
fn parse_req_extracts_encrypt_field_when_present() {
    let dispatcher = EventDispatcher::new("", "some_key");
    let body = serde_json::json!({ "encrypt": "cipher_text_here" });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: String::new(),
    };
    let result = dispatcher.parse_req(&req).unwrap();
    assert_eq!(result, "cipher_text_here");
}

#[test]
fn parse_req_returns_raw_when_no_encrypt_field() {
    let dispatcher = EventDispatcher::new("", "some_key");
    let body = r#"{"schema":"2.0","event":{}}"#;
    let req = EventReq {
        headers: Default::default(),
        body: body.as_bytes().to_vec(),
        request_uri: String::new(),
    };
    let result = dispatcher.parse_req(&req).unwrap();
    assert_eq!(result, body);
}

#[test]
fn decrypt_event_passthrough_when_no_encrypt_key() {
    let dispatcher = EventDispatcher::new("", "");
    let result = dispatcher.decrypt_event("plaintext_data").unwrap();
    assert_eq!(result, "plaintext_data");
}

#[test]
fn parse_req_then_decrypt_event_roundtrip() {
    use larksuite_oapi_sdk_rs::crypto::event_encrypt;

    let encrypt_key = "roundtrip_test_key";
    let inner = r#"{"schema":"2.0","header":{"event_type":"test"},"event":{}}"#;
    let encrypted = event_encrypt(encrypt_key, inner).unwrap();
    let outer = serde_json::json!({ "encrypt": encrypted });

    let dispatcher = EventDispatcher::new("", encrypt_key);
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&outer).unwrap(),
        request_uri: String::new(),
    };

    let cipher = dispatcher.parse_req(&req).unwrap();
    let decrypted = dispatcher.decrypt_event(&cipher).unwrap();
    assert_eq!(decrypted, inner);
}

// ── CardActionHandler: signature uses verification_token not encrypt_key ──

#[tokio::test]
async fn card_signature_uses_verification_token() {
    let verification_token = "my_verify_token";
    let timestamp = "1620000000";
    let nonce = "test_nonce";

    let received = Arc::new(Mutex::new(false));
    let received_clone = Arc::clone(&received);

    let handler = CardActionHandler::new(
        verification_token,
        "some_encrypt_key",
        move |_action: CardAction| {
            let r = Arc::clone(&received_clone);
            async move {
                *r.lock().unwrap() = true;
                Ok(serde_json::json!({}))
            }
        },
    );

    let body = serde_json::json!({
        "open_id": "ou_1",
        "action": {}
    });
    let body_str = serde_json::to_string(&body).unwrap();
    let sig = compute_sha1_sig(timestamp, nonce, verification_token, &body_str);

    let headers = HashMap::from([
        (
            "X-Lark-Request-Timestamp".to_string(),
            vec![timestamp.to_string()],
        ),
        ("X-Lark-Request-Nonce".to_string(), vec![nonce.to_string()]),
        ("X-Lark-Signature".to_string(), vec![sig]),
    ]);

    let req = EventReq {
        headers,
        body: body_str.into_bytes(),
        request_uri: "/card".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 200);
    assert!(*received.lock().unwrap());
}

#[tokio::test]
async fn card_empty_verification_token_skips_signature() {
    let received = Arc::new(Mutex::new(false));
    let received_clone = Arc::clone(&received);

    let handler = CardActionHandler::new("", "some_encrypt_key", move |_action: CardAction| {
        let r = Arc::clone(&received_clone);
        async move {
            *r.lock().unwrap() = true;
            Ok(serde_json::json!({}))
        }
    });

    let body = serde_json::json!({
        "open_id": "ou_1",
        "action": {}
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/card".to_string(),
    };
    let resp = handler.handle(req).await;
    assert_eq!(resp.status_code, 200);
    assert!(*received.lock().unwrap());
}

// ── EventDispatcher: on_card_action_trigger ──

#[tokio::test]
async fn on_card_action_trigger_dispatches_typed() {
    use larksuite_oapi_sdk_rs::event::{CardActionTriggerResponse, Toast};

    let dispatcher = EventDispatcher::new("", "")
        .skip_sign_verify()
        .on_card_action_trigger(|req| async move {
            assert_eq!(req.action.as_ref().unwrap().tag, "button");
            Ok(CardActionTriggerResponse {
                toast: Some(Toast {
                    toast_type: Some("success".to_string()),
                    content: Some("Done!".to_string()),
                    i18n: None,
                }),
                card: None,
            })
        });

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_trigger",
            "event_type": "card.action.trigger",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {
            "action": { "tag": "button", "value": {} },
            "token": "tok",
            "host": "lark",
            "delivery_type": "push"
        }
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: String::new(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
    let parsed: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert_eq!(parsed["toast"]["type"], "success");
    assert_eq!(parsed["toast"]["content"], "Done!");
}

#[tokio::test]
async fn on_card_action_trigger_rejects_malformed_payload() {
    use larksuite_oapi_sdk_rs::event::CardActionTriggerResponse;

    let called = Arc::new(Mutex::new(false));
    let called_clone = Arc::clone(&called);
    let dispatcher = EventDispatcher::new("", "")
        .skip_sign_verify()
        .on_card_action_trigger(move |_req| {
            let called = Arc::clone(&called_clone);
            async move {
                *called.lock().unwrap() = true;
                Ok(CardActionTriggerResponse::default())
            }
        });

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_trigger_bad",
            "event_type": "card.action.trigger",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {
            "action": 1,
            "token": "tok"
        }
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: String::new(),
    };
    let resp = dispatcher.handle(req).await;

    assert_eq!(resp.status_code, 500);
    assert!(!*called.lock().unwrap());
    let parsed: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert!(
        parsed["msg"]
            .as_str()
            .unwrap()
            .contains("failed to deserialize callback payload")
    );
}

// ── EventDispatcher: on_url_preview_get ──

#[tokio::test]
async fn on_url_preview_get_dispatches_typed() {
    use larksuite_oapi_sdk_rs::event::{InlinePreview, URLPreviewGetResponse};

    let dispatcher = EventDispatcher::new("", "")
        .skip_sign_verify()
        .on_url_preview_get(|req| async move {
            assert_eq!(req.context.as_ref().unwrap().url, "https://example.com");
            Ok(URLPreviewGetResponse {
                inline: Some(InlinePreview {
                    title: Some("Example".to_string()),
                    i18n_title: None,
                    image_key: Some("img_key".to_string()),
                    url: None,
                }),
                card: None,
            })
        });

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_preview",
            "event_type": "url.preview.get",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {
            "host": "lark",
            "context": {
                "url": "https://example.com",
                "preview_token": "ptok",
                "open_message_id": "om_1",
                "open_chat_id": "oc_1"
            }
        }
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: String::new(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
    let parsed: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert_eq!(parsed["inline"]["title"], "Example");
    assert_eq!(parsed["inline"]["image_key"], "img_key");
}

#[tokio::test]
async fn on_url_preview_get_rejects_malformed_payload() {
    use larksuite_oapi_sdk_rs::event::URLPreviewGetResponse;

    let called = Arc::new(Mutex::new(false));
    let called_clone = Arc::clone(&called);
    let dispatcher = EventDispatcher::new("", "")
        .skip_sign_verify()
        .on_url_preview_get(move |_req| {
            let called = Arc::clone(&called_clone);
            async move {
                *called.lock().unwrap() = true;
                Ok(URLPreviewGetResponse::default())
            }
        });

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_preview_bad",
            "event_type": "url.preview.get",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {
            "host": "lark",
            "context": 1
        }
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: String::new(),
    };
    let resp = dispatcher.handle(req).await;

    assert_eq!(resp.status_code, 500);
    assert!(!*called.lock().unwrap());
    let parsed: serde_json::Value = serde_json::from_slice(&resp.body).unwrap();
    assert!(
        parsed["msg"]
            .as_str()
            .unwrap()
            .contains("failed to deserialize callback payload")
    );
}

// ── Toast builder with ToastI18n ──

#[test]
fn toast_new_and_i18n() {
    use larksuite_oapi_sdk_rs::event::Toast;

    let toast = Toast::new("Operation complete")
        .toast_type("success")
        .i18n(ToastI18n {
            zh_cn: Some("操作完成".to_string()),
            en_us: Some("Operation complete".to_string()),
            ja_jp: Some("操作完了".to_string()),
            ..Default::default()
        });

    let json = serde_json::to_value(&toast).unwrap();
    assert_eq!(json["type"], "success");
    assert_eq!(json["content"], "Operation complete");
    assert_eq!(json["i18n"]["zh_cn"], "操作完成");
    assert_eq!(json["i18n"]["en_us"], "Operation complete");
    assert_eq!(json["i18n"]["ja_jp"], "操作完了");
    assert!(json["i18n"].get("ko_kr").is_none());
}

#[test]
fn toast_i18n_serde_roundtrip() {
    let i18n = ToastI18n {
        zh_cn: Some("你好".to_string()),
        en_us: Some("Hello".to_string()),
        ..Default::default()
    };
    let json = serde_json::to_string(&i18n).unwrap();
    let parsed: ToastI18n = serde_json::from_str(&json).unwrap();
    assert_eq!(parsed.zh_cn.as_deref(), Some("你好"));
    assert_eq!(parsed.en_us.as_deref(), Some("Hello"));
    assert!(parsed.ko_kr.is_none());
}

#[test]
fn toast_i18n_maps_all_supported_locales() {
    use larksuite_oapi_sdk_rs::event::Toast;

    let toast = Toast::new("localized").i18n(ToastI18n {
        zh_cn: Some("zh_cn".to_string()),
        en_us: Some("en_us".to_string()),
        ja_jp: Some("ja_jp".to_string()),
        zh_hk: Some("zh_hk".to_string()),
        zh_tw: Some("zh_tw".to_string()),
        id_id: Some("id_id".to_string()),
        vi_vn: Some("vi_vn".to_string()),
        th_th: Some("th_th".to_string()),
        pt_br: Some("pt_br".to_string()),
        es_es: Some("es_es".to_string()),
        ko_kr: Some("ko_kr".to_string()),
        de_de: Some("de_de".to_string()),
        fr_fr: Some("fr_fr".to_string()),
        it_it: Some("it_it".to_string()),
        ru_ru: Some("ru_ru".to_string()),
        ms_my: Some("ms_my".to_string()),
    });

    let json = serde_json::to_value(toast).unwrap();
    let i18n = json["i18n"].as_object().unwrap();
    let locales = [
        "zh_cn", "en_us", "ja_jp", "zh_hk", "zh_tw", "id_id", "vi_vn", "th_th", "pt_br", "es_es",
        "ko_kr", "de_de", "fr_fr", "it_it", "ru_ru", "ms_my",
    ];

    assert_eq!(i18n.len(), locales.len());
    for locale in locales {
        assert_eq!(i18n[locale], locale);
    }
}

// ── AppTicketEvent typed handler ──

#[tokio::test]
async fn on_app_ticket_event_dispatches_typed() {
    use larksuite_oapi_sdk_rs::events::p1::AppTicketEvent;

    let captured_ticket = Arc::new(Mutex::new(String::new()));
    let captured_app_id = Arc::new(Mutex::new(String::new()));
    let ticket_clone = captured_ticket.clone();
    let app_clone = captured_app_id.clone();

    let dispatcher = EventDispatcher::new("", "")
        .skip_sign_verify()
        .on_app_ticket_event(move |evt: AppTicketEvent| {
            let t = ticket_clone.clone();
            let a = app_clone.clone();
            async move {
                *t.lock().unwrap() = evt.app_ticket.clone();
                *a.lock().unwrap() = evt.app_id.clone();
                Ok(())
            }
        });

    let body = serde_json::json!({
        "schema": "2.0",
        "header": {
            "event_id": "ev_ticket_typed",
            "event_type": "app_ticket",
            "app_id": "cli_test",
            "tenant_key": "t1",
            "create_time": "0"
        },
        "event": {
            "type": "app_ticket",
            "app_id": "my_app_id",
            "app_ticket": "ticket_xyz_123"
        }
    });
    let req = EventReq {
        headers: Default::default(),
        body: serde_json::to_vec(&body).unwrap(),
        request_uri: "/webhook".to_string(),
    };
    let resp = dispatcher.handle(req).await;
    assert_eq!(resp.status_code, 200);
    assert_eq!(&*captured_ticket.lock().unwrap(), "ticket_xyz_123");
    assert_eq!(&*captured_app_id.lock().unwrap(), "my_app_id");
}

// ── Event base types ──

#[test]
fn event_v1_header_serde() {
    use larksuite_oapi_sdk_rs::event::EventV1Header;
    let json = r#"{"app_id":"app1","open_chat_id":"oc_123","open_id":"ou_456","tenant_key":"tk","type":"event_callback"}"#;
    let header: EventV1Header = serde_json::from_str(json).unwrap();
    assert_eq!(header.app_id, "app1");
    assert_eq!(header.open_chat_id, "oc_123");
    assert_eq!(header.open_id, "ou_456");
    assert_eq!(header.tenant_key, "tk");
    assert_eq!(header.event_type, "event_callback");
}

#[test]
fn event_v2_base_tenant_key() {
    use larksuite_oapi_sdk_rs::event::{EventHeader, EventV2Base};
    let base = EventV2Base {
        schema: "2.0".to_string(),
        header: Some(EventHeader {
            tenant_key: "tenant_abc".to_string(),
            ..Default::default()
        }),
    };
    assert_eq!(base.tenant_key(), "tenant_abc");

    let empty = EventV2Base::default();
    assert_eq!(empty.tenant_key(), "");
}

#[test]
fn event_base_serde() {
    use larksuite_oapi_sdk_rs::event::EventBase;
    let json = r#"{"ts":"1620000000","uuid":"uuid-123","token":"tok","type":"event_callback"}"#;
    let base: EventBase = serde_json::from_str(json).unwrap();
    assert_eq!(base.ts, "1620000000");
    assert_eq!(base.uuid, "uuid-123");
    assert_eq!(base.token, "tok");
    assert_eq!(base.event_type, "event_callback");
}
