use cryochamber::channel::zulip::{
    parse_get_messages_response, parse_get_profile_response, parse_get_stream_id_response,
    ZulipClient,
};
use std::io::{Read, Write};
use std::net::TcpListener;

fn serve_one_zulip_response(body: &'static str) -> String {
    let listener = TcpListener::bind("127.0.0.1:0").unwrap();
    let addr = listener.local_addr().unwrap();
    std::thread::spawn(move || {
        let (mut stream, _) = listener.accept().unwrap();
        stream
            .set_read_timeout(Some(std::time::Duration::from_secs(2)))
            .unwrap();
        let mut request = [0_u8; 4096];
        let _ = stream.read(&mut request);
        let response = format!(
            "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
            body.len(),
            body
        );
        stream.write_all(response.as_bytes()).unwrap();
    });
    format!("http://{addr}")
}

fn client_for_site(site: &str) -> ZulipClient {
    let dir = tempfile::tempdir().unwrap();
    let rc_path = dir.path().join("zuliprc");
    std::fs::write(
        &rc_path,
        format!("[api]\nemail=bot@example.com\nkey=abc123secret\nsite={site}\n"),
    )
    .unwrap();
    ZulipClient::from_zuliprc(&rc_path).unwrap()
}

#[test]
fn test_parse_zuliprc() {
    let dir = tempfile::tempdir().unwrap();
    let rc_path = dir.path().join("zuliprc");
    std::fs::write(
        &rc_path,
        "[api]\nemail=bot@example.com\nkey=abc123secret\nsite=https://zulip.example.com\n",
    )
    .unwrap();

    let client = ZulipClient::from_zuliprc(&rc_path).unwrap();
    let creds = client.credentials();
    assert_eq!(creds.email, "bot@example.com");
    assert_eq!(creds.api_key, "abc123secret");
    assert_eq!(creds.site, "https://zulip.example.com");
}

#[test]
fn test_parse_zuliprc_with_spaces() {
    let dir = tempfile::tempdir().unwrap();
    let rc_path = dir.path().join("zuliprc");
    std::fs::write(
        &rc_path,
        "[api]\nemail = bot@example.com\nkey = abc123\nsite = https://zulip.example.com\n",
    )
    .unwrap();

    let client = ZulipClient::from_zuliprc(&rc_path).unwrap();
    let creds = client.credentials();
    assert_eq!(creds.email, "bot@example.com");
    assert_eq!(creds.api_key, "abc123");
}

#[test]
fn test_parse_zuliprc_missing_field() {
    let dir = tempfile::tempdir().unwrap();
    let rc_path = dir.path().join("zuliprc");
    std::fs::write(&rc_path, "[api]\nemail=bot@example.com\n").unwrap();

    let result = ZulipClient::from_zuliprc(&rc_path);
    assert!(result.is_err());
}

#[test]
fn test_parse_get_profile_response() {
    let json = serde_json::json!({
        "result": "success",
        "msg": "",
        "user_id": 42,
        "email": "bot@example.com",
        "full_name": "Test Bot"
    });
    let (user_id, email) = parse_get_profile_response(&json).unwrap();
    assert_eq!(user_id, 42);
    assert_eq!(email, "bot@example.com");
}

#[test]
fn test_parse_get_stream_id_response() {
    let json = serde_json::json!({
        "result": "success",
        "msg": "",
        "stream_id": 15
    });
    let stream_id = parse_get_stream_id_response(&json).unwrap();
    assert_eq!(stream_id, 15);
}

#[test]
fn test_parse_get_messages_response() {
    let json = serde_json::json!({
        "result": "success",
        "msg": "",
        "messages": [
            {
                "id": 100,
                "sender_id": 42,
                "sender_email": "alice@example.com",
                "sender_full_name": "Alice",
                "content": "Hello from Zulip",
                "subject": "general-topic",
                "timestamp": 1740700000,
                "type": "stream"
            },
            {
                "id": 101,
                "sender_id": 43,
                "sender_email": "bot@example.com",
                "sender_full_name": "Bot",
                "content": "I am the bot",
                "subject": "general-topic",
                "timestamp": 1740700060,
                "type": "stream"
            }
        ],
        "found_newest": true,
        "found_oldest": false
    });
    let (messages, found_newest, raw_max_id) =
        parse_get_messages_response(&json, Some("bot@example.com"), None).unwrap();
    // Should filter out bot's own message
    assert_eq!(messages.len(), 1);
    assert_eq!(messages[0].from, "Alice");
    assert_eq!(messages[0].body, "Hello from Zulip");
    assert_eq!(
        messages[0].metadata.get("source"),
        Some(&"zulip".to_string())
    );
    assert_eq!(
        messages[0].metadata.get("zulip_message_id"),
        Some(&"100".to_string())
    );
    assert!(found_newest);
    // Raw max ID should be 101 (the bot message), not 100 (the filtered result)
    assert_eq!(raw_max_id, Some(101));
}

#[test]
fn test_parse_get_messages_response_converts_utc_timestamp_to_local() {
    // Zulip delivers unix seconds (UTC). We store message timestamps as
    // naive *local* datetimes so the UI renders them in the operator's
    // wall-clock time — otherwise a message sent at 10:00 local in a UTC+8
    // zone would show up as 02:00 in the thread.
    let ts_unix: i64 = 1_740_700_000;
    let json = serde_json::json!({
        "result": "success",
        "msg": "",
        "messages": [
            {
                "id": 100,
                "sender_id": 42,
                "sender_email": "alice@example.com",
                "sender_full_name": "Alice",
                "content": "Hello",
                "subject": "general-topic",
                "timestamp": ts_unix,
                "type": "stream"
            }
        ],
        "found_newest": true,
        "found_oldest": false
    });
    let (messages, _, _) = parse_get_messages_response(&json, None, None).unwrap();
    assert_eq!(messages.len(), 1);
    let expected = chrono::DateTime::from_timestamp(ts_unix, 0)
        .unwrap()
        .with_timezone(&chrono::Local)
        .naive_local();
    assert_eq!(
        messages[0].timestamp, expected,
        "zulip timestamps must be converted from UTC to local naive time"
    );
}

#[test]
fn test_parse_get_messages_response_empty() {
    let json = serde_json::json!({
        "result": "success",
        "msg": "",
        "messages": [],
        "found_newest": true,
        "found_oldest": true
    });
    let (messages, found_newest, raw_max_id) =
        parse_get_messages_response(&json, None, None).unwrap();
    assert!(messages.is_empty());
    assert!(found_newest);
    assert_eq!(raw_max_id, None);
}

#[test]
fn test_zulip_fetch_messages_since_returns_messages_and_newest_seen_id_without_work_dir() {
    let site = serve_one_zulip_response(
        r#"{
  "result": "success",
  "msg": "",
  "messages": [
    {
      "id": 100,
      "sender_email": "alice@example.com",
      "sender_full_name": "Alice",
      "content": "Anchor should be skipped",
      "subject": "cryochamber",
      "timestamp": 1740700000,
      "type": "stream"
    },
    {
      "id": 101,
      "sender_email": "alice@example.com",
      "sender_full_name": "Alice",
      "content": "Please check the run",
      "subject": "cryochamber",
      "timestamp": 1740700060,
      "type": "stream"
    },
    {
      "id": 102,
      "sender_email": "bot@example.com",
      "sender_full_name": "Bot",
      "content": "Bot echo",
      "subject": "cryochamber",
      "timestamp": 1740700120,
      "type": "stream"
    }
  ],
  "found_newest": true,
  "found_oldest": false
}"#,
    );
    let client = client_for_site(&site);

    let result: cryochamber::channel::zulip::ZulipPullResult = client
        .fetch_messages_since(15, Some("cryochamber"), Some(100), Some("bot@example.com"))
        .unwrap();

    assert_eq!(result.newest_seen_id, Some(102));
    assert_eq!(result.messages.len(), 1);
    assert_eq!(result.messages[0].from, "Alice");
    assert_eq!(result.messages[0].body, "Please check the run");
}

#[test]
fn test_zulip_channel_does_not_write_local_inbox_or_sync_state() {
    let source = std::fs::read_to_string(
        std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/channel/zulip.rs"),
    )
    .unwrap();
    assert!(
        !source.contains("crate::message::ensure_dirs")
            && !source.contains("crate::message::write_message")
            && !source.contains("crate::zulip_sync"),
        "channel::zulip must stay transport-only; inbox persistence and sync state belong in cryo_zulip"
    );
}

#[test]
fn test_parse_get_messages_no_self_filter() {
    let json = serde_json::json!({
        "result": "success",
        "msg": "",
        "messages": [
            {
                "id": 100,
                "sender_id": 42,
                "sender_email": "alice@example.com",
                "sender_full_name": "Alice",
                "content": "Hello",
                "subject": "topic",
                "timestamp": 1740700000,
                "type": "stream"
            }
        ],
        "found_newest": false,
        "found_oldest": false
    });
    let (messages, found_newest, raw_max_id) =
        parse_get_messages_response(&json, None, None).unwrap();
    assert_eq!(messages.len(), 1);
    assert!(!found_newest);
    assert_eq!(raw_max_id, Some(100));
}

#[test]
fn test_parse_get_messages_cursor_advances_when_all_filtered() {
    // When all messages are from the skip_email sender, the filtered messages
    // list is empty but raw_max_id still tracks the highest ID for cursor advancement.
    let json = serde_json::json!({
        "result": "success",
        "msg": "",
        "messages": [
            {
                "id": 200,
                "sender_id": 42,
                "sender_email": "bot@example.com",
                "sender_full_name": "Bot",
                "content": "Bot message 1",
                "subject": "topic",
                "timestamp": 1740700000,
                "type": "stream"
            },
            {
                "id": 201,
                "sender_id": 42,
                "sender_email": "bot@example.com",
                "sender_full_name": "Bot",
                "content": "Bot message 2",
                "subject": "topic",
                "timestamp": 1740700060,
                "type": "stream"
            }
        ],
        "found_newest": false,
        "found_oldest": false
    });
    let (messages, found_newest, raw_max_id) =
        parse_get_messages_response(&json, Some("bot@example.com"), None).unwrap();
    // All messages filtered out
    assert!(messages.is_empty());
    assert!(!found_newest);
    // But raw_max_id advances to 201 so pagination can continue
    assert_eq!(raw_max_id, Some(201));
}

#[test]
fn test_parse_get_messages_filters_to_topic() {
    let json = serde_json::json!({
        "result": "success",
        "msg": "",
        "messages": [
            {
                "id": 300,
                "sender_id": 42,
                "sender_email": "alice@example.com",
                "sender_full_name": "Alice",
                "content": "Keep this",
                "subject": "cryochamber",
                "timestamp": 1740700000,
                "type": "stream"
            },
            {
                "id": 301,
                "sender_id": 43,
                "sender_email": "bob@example.com",
                "sender_full_name": "Bob",
                "content": "Do not import this",
                "subject": "random",
                "timestamp": 1740700060,
                "type": "stream"
            }
        ],
        "found_newest": true,
        "found_oldest": false
    });

    let (messages, found_newest, raw_max_id) =
        parse_get_messages_response(&json, None, Some("cryochamber")).unwrap();

    assert_eq!(messages.len(), 1);
    assert_eq!(messages[0].subject, "cryochamber");
    assert_eq!(messages[0].body, "Keep this");
    assert!(found_newest);
    assert_eq!(raw_max_id, Some(301));
}
