use super::*;
use std::sync::mpsc;

struct TokenizableProbe {
    id: i64,
}

#[async_trait::async_trait]
impl Tokenizable for TokenizableProbe {
    type TokenPrimaryKey = i64;

    fn token_model_name() -> &'static str {
        "TokenizableProbe"
    }

    fn token_primary_key(&self) -> i64 {
        self.id
    }

    async fn from_token(token: &str) -> Result<Self> {
        Ok(Self {
            id: Self::decode_token(token)?,
        })
    }
}

fn init_test_key() {
    TokenConfig::set_encryption_key("test-encryption-key-for-unit-tests-32");
}

#[test]
fn test_base64_url_encode_decode() {
    let data = b"Hello, World!";
    let encoded = base64_url_encode(data);
    let decoded = base64_url_decode(&encoded).unwrap();
    assert_eq!(data.to_vec(), decoded);
}

#[test]
fn test_base64_url_various_lengths() {
    for len in 1..=32 {
        let data: Vec<u8> = (0..len).map(|i| i as u8).collect();
        let encoded = base64_url_encode(&data);
        let decoded = base64_url_decode(&encoded).unwrap();
        assert_eq!(data, decoded, "Failed for length {}", len);
    }
}

#[test]
fn test_default_encode_decode() {
    init_test_key();

    let record_id = "12345";
    let model_name = "User";

    let token = default_encode(record_id, model_name).unwrap();
    let decoded = default_decode(&token, model_name).unwrap();

    assert_eq!(decoded, Some(record_id.to_string()));
}

#[test]
fn test_encode_decode_negative_id() {
    init_test_key();

    let record_id = "-99999";
    let model_name = "NegativeModel";

    let token = default_encode(record_id, model_name).unwrap();
    let decoded = default_decode(&token, model_name).unwrap();

    assert_eq!(decoded, Some(record_id.to_string()));
}

#[test]
fn test_encode_decode_zero() {
    init_test_key();

    let record_id = "0";
    let model_name = "ZeroModel";

    let token = default_encode(record_id, model_name).unwrap();
    let decoded = default_decode(&token, model_name).unwrap();

    assert_eq!(decoded, Some(record_id.to_string()));
}

#[test]
fn test_encode_decode_max_i64() {
    init_test_key();

    let record_id = "9223372036854775807";
    let model_name = "MaxModel";

    let token = default_encode(record_id, model_name).unwrap();
    let decoded = default_decode(&token, model_name).unwrap();

    assert_eq!(decoded, Some(record_id.to_string()));
}

#[test]
fn test_wrong_model_fails() {
    init_test_key();

    let record_id = "42";
    let token = default_encode(record_id, "User").unwrap();

    let decoded = default_decode(&token, "Product").unwrap();
    assert_eq!(decoded, None);
}

#[test]
fn test_tampered_token_fails() {
    init_test_key();

    let record_id = "42";
    let token = default_encode(record_id, "User").unwrap();

    let mut chars: Vec<char> = token.chars().collect();
    if let Some(c) = chars.get_mut(10) {
        *c = if *c == 'A' { 'B' } else { 'A' };
    }
    let tampered: String = chars.into_iter().collect();

    let decoded = default_decode(&tampered, "User").unwrap();
    assert_eq!(decoded, None);
}

#[test]
fn test_invalid_base64_fails() {
    init_test_key();

    let decoded = default_decode("not-valid-base64!!!", "User").unwrap();
    assert_eq!(decoded, None);
}

#[test]
fn test_too_short_token_fails() {
    init_test_key();

    let decoded = default_decode("abc", "User").unwrap();
    assert_eq!(decoded, None);
}

#[test]
fn test_token_is_url_safe() {
    init_test_key();

    let record_id = "999999999";
    let token = default_encode(record_id, "User").unwrap();

    assert!(
        token
            .chars()
            .all(|c| { c.is_ascii_alphanumeric() || c == '-' || c == '_' })
    );
}

#[test]
fn test_different_ids_different_tokens() {
    init_test_key();

    let token1 = default_encode("1", "User").unwrap();
    let token2 = default_encode("2", "User").unwrap();

    assert_ne!(token1, token2);
}

#[test]
fn test_same_id_generates_different_tokens() {
    init_test_key();

    let token1 = default_encode("42", "User").unwrap();
    let token2 = default_encode("42", "User").unwrap();

    assert_ne!(token1, token2);
    assert_eq!(
        default_decode(&token1, "User").unwrap(),
        Some("42".to_string())
    );
    assert_eq!(
        default_decode(&token2, "User").unwrap(),
        Some("42".to_string())
    );
}

#[test]
fn test_token_config_encode_decode() {
    init_test_key();

    let token = TokenConfig::encode("123", "TestModel").unwrap();
    let decoded = TokenConfig::decode(&token, "TestModel").unwrap();

    assert_eq!(decoded, Some("123".to_string()));
}

#[test]
fn test_token_config_setters_overwrite_previous_values() {
    TokenConfig::reset();

    TokenConfig::set_encryption_key("first-key");
    TokenConfig::set_encryption_key("second-key");
    assert_eq!(TokenConfig::get_encryption_key().unwrap(), "second-key");

    fn encoder_one(record_id: &str, _model_name: &str) -> Result<String> {
        Ok(format!("one-{record_id}"))
    }

    fn encoder_two(record_id: &str, _model_name: &str) -> Result<String> {
        Ok(format!("two-{record_id}"))
    }

    fn decoder_one(token: &str, _model_name: &str) -> Result<Option<String>> {
        Ok(token.strip_prefix("one-").map(ToOwned::to_owned))
    }

    fn decoder_two(token: &str, _model_name: &str) -> Result<Option<String>> {
        Ok(token.strip_prefix("two-").map(ToOwned::to_owned))
    }

    TokenConfig::set_encoder(encoder_one);
    TokenConfig::set_encoder(encoder_two);
    TokenConfig::set_decoder(decoder_one);
    TokenConfig::set_decoder(decoder_two);

    let token = TokenConfig::encode("7", "User").unwrap();
    assert_eq!(token, "two-7");
    assert_eq!(
        TokenConfig::decode(&token, "User").unwrap(),
        Some("7".to_string())
    );
}

#[test]
fn test_tokenizable_decode_token_uses_global_decoder_override() {
    TokenConfig::reset();

    fn decoder(token: &str, _model_name: &str) -> Result<Option<String>> {
        Ok(token.strip_prefix("global-").map(ToOwned::to_owned))
    }

    TokenConfig::set_decoder(decoder);

    assert_eq!(TokenizableProbe::decode_token("global-42").unwrap(), 42);
}

#[test]
fn test_token_config_reset_clears_global_state() {
    TokenConfig::set_encryption_key("transient-key");
    assert!(TokenConfig::has_encryption_key());

    TokenConfig::reset();

    assert!(!TokenConfig::has_encryption_key());
    assert!(TokenConfig::get_encryption_key().is_err());
}

#[test]
fn test_token_config_reset_clears_state_set_on_another_thread() {
    TokenConfig::reset();

    fn custom_encoder(record_id: &str, _model_name: &str) -> Result<String> {
        Ok(format!("custom-{record_id}"))
    }

    let (ready_tx, ready_rx) = mpsc::channel();
    let (check_tx, check_rx) = mpsc::channel();
    let (result_tx, result_rx) = mpsc::channel();

    let worker = std::thread::spawn(move || {
        TokenConfig::set_encryption_key("thread-key");
        TokenConfig::set_encoder(custom_encoder);
        ready_tx
            .send(())
            .expect("worker should report that token state is configured");

        check_rx.recv().expect("worker should receive reset signal");

        result_tx
            .send((
                TokenConfig::has_encryption_key(),
                TokenConfig::get_encryption_key(),
                TokenConfig::encode("7", "User"),
            ))
            .expect("worker should report token state after reset");
    });

    ready_rx
        .recv()
        .expect("main thread should observe configured token state");

    TokenConfig::reset();

    check_tx
        .send(())
        .expect("main thread should signal worker to validate reset state");

    let (has_key, key_result, encode_result) = result_rx
        .recv()
        .expect("main thread should receive worker validation results");

    worker
        .join()
        .expect("token config worker thread should finish successfully");

    assert!(!has_key);
    assert!(key_result.is_err());
    assert!(encode_result.is_err());
}

#[test]
fn test_derive_encryption_key_is_deterministic_for_same_secret() {
    let first = derive_encryption_key("same-secret");
    let second = derive_encryption_key("same-secret");

    assert_eq!(first, second);
}

#[test]
fn test_derive_encryption_key_changes_with_secret() {
    let first = derive_encryption_key("first-secret");
    let second = derive_encryption_key("second-secret");

    assert_ne!(first, second);
}
