use grpctestify::assert::engine::{AssertionEngine, AssertionResult};
use serde_json::json;
use std::collections::HashMap;

#[test]
fn test_uuid_plugin() {
    let engine = AssertionEngine::new();
    let response = json!({
        "id": "123e4567-e89b-12d3-a456-426614174000",
        "bad_id": "not-a-uuid"
    });

    // Valid UUID
    let result = engine
        .evaluate("@uuid(.id)", &response, None, None)
        .unwrap();
    assert_eq!(result, AssertionResult::Pass);

    // Invalid UUID
    let result = engine
        .evaluate("@uuid(.bad_id)", &response, None, None)
        .unwrap();
    if let AssertionResult::Fail { message: msg, .. } = result {
        assert!(msg.contains("Expected valid UUID"));
    } else {
        panic!("Expected failure for invalid UUID");
    }
}

#[test]
fn test_email_plugin() {
    let engine = AssertionEngine::new();
    let response = json!({
        "email": "test@example.com",
        "bad_email": "invalid-email"
    });

    let result = engine
        .evaluate("@email(.email)", &response, None, None)
        .unwrap();
    assert_eq!(result, AssertionResult::Pass);

    let result = engine
        .evaluate("@email(.bad_email)", &response, None, None)
        .unwrap();
    assert!(matches!(result, AssertionResult::Fail { .. }));
}

#[test]
fn test_ip_plugin() {
    let engine = AssertionEngine::new();
    let response = json!({
        "ip": "192.168.1.1",
        "bad_ip": "999.999.999.999"
    });

    let result = engine.evaluate("@ip(.ip)", &response, None, None).unwrap();
    assert_eq!(result, AssertionResult::Pass);

    let result = engine
        .evaluate("@ip(.bad_ip)", &response, None, None)
        .unwrap();
    assert!(matches!(result, AssertionResult::Fail { .. }));
}

#[test]
fn test_url_plugin() {
    let engine = AssertionEngine::new();
    let response = json!({
        "url": "https://example.com",
        "bad_url": "not-a-url"
    });

    let result = engine
        .evaluate("@url(.url)", &response, None, None)
        .unwrap();
    assert_eq!(result, AssertionResult::Pass);

    let result = engine
        .evaluate("@url(.bad_url)", &response, None, None)
        .unwrap();
    assert!(matches!(result, AssertionResult::Fail { .. }));
}

#[test]
fn test_timestamp_plugin() {
    let engine = AssertionEngine::new();
    let response = json!({
        "ts": "2023-10-01T12:00:00Z",
        "bad_ts": "tomorrow"
    });

    let result = engine
        .evaluate("@timestamp(.ts)", &response, None, None)
        .unwrap();
    assert_eq!(result, AssertionResult::Pass);

    let result = engine
        .evaluate("@timestamp(.bad_ts)", &response, None, None)
        .unwrap();
    assert!(matches!(result, AssertionResult::Fail { .. }));
}

#[test]
fn test_len_plugin() {
    let engine = AssertionEngine::new();
    let response = json!({
        "list": [1, 2, 3],
        "text": "hello"
    });

    // Check array length
    let result = engine
        .evaluate("@len(.list) == 3", &response, None, None)
        .unwrap();
    assert_eq!(result, AssertionResult::Pass);

    // Check string length
    let result = engine
        .evaluate("@len(.text) == 5", &response, None, None)
        .unwrap();
    assert_eq!(result, AssertionResult::Pass);

    // Check failure
    let result = engine
        .evaluate("@len(.list) == 5", &response, None, None)
        .unwrap();
    assert!(matches!(result, AssertionResult::Fail { .. }));
}

#[test]
fn test_header_plugin() {
    let engine = AssertionEngine::new();
    let response = json!({});
    let mut headers = HashMap::new();
    headers.insert("content-type".to_string(), "application/json".to_string());

    let result = engine
        .evaluate("@header(\"content-type\")", &response, Some(&headers), None)
        .unwrap();
    assert_eq!(result, AssertionResult::Pass);

    let result = engine
        .evaluate("@header(\"x-missing\")", &response, Some(&headers), None)
        .unwrap();
    assert!(matches!(result, AssertionResult::Fail { .. }));
}

// ========================================
// Extended Plugin Tests (Edge Cases)
// ========================================

#[test]
fn test_uuid_edge_cases() {
    let engine = AssertionEngine::new();

    // UUID v4 format
    let response = json!({"uuid": "550e8400-e29b-41d4-a716-446655440000"});
    assert_eq!(
        engine
            .evaluate("@uuid(.uuid)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Nil UUID
    let response = json!({"uuid": "00000000-0000-0000-0000-000000000000"});
    assert_eq!(
        engine
            .evaluate("@uuid(.uuid)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // UUID without dashes (valid - uuid crate accepts simple hex format)
    let response = json!({"uuid": "550e8400e29b41d4a716446655440000"});
    assert_eq!(
        engine
            .evaluate("@uuid(.uuid)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Empty string
    let response = json!({"uuid": ""});
    assert!(matches!(
        engine
            .evaluate("@uuid(.uuid)", &response, None, None)
            .unwrap(),
        AssertionResult::Fail { .. }
    ));
}

#[test]
fn test_email_edge_cases() {
    let engine = AssertionEngine::new();

    // Email with subdomain
    let response = json!({"email": "user@mail.example.com"});
    assert_eq!(
        engine
            .evaluate("@email(.email)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Email with plus sign
    let response = json!({"email": "user+tag@example.com"});
    assert_eq!(
        engine
            .evaluate("@email(.email)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Email with numbers
    let response = json!({"email": "user123@example123.com"});
    assert_eq!(
        engine
            .evaluate("@email(.email)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Missing @
    let response = json!({"email": "userexample.com"});
    assert!(matches!(
        engine
            .evaluate("@email(.email)", &response, None, None)
            .unwrap(),
        AssertionResult::Fail { .. }
    ));

    // Empty
    let response = json!({"email": ""});
    assert!(matches!(
        engine
            .evaluate("@email(.email)", &response, None, None)
            .unwrap(),
        AssertionResult::Fail { .. }
    ));
}

#[test]
fn test_ip_edge_cases() {
    let engine = AssertionEngine::new();

    // IPv6 loopback
    let response = json!({"ip": "::1"});
    assert_eq!(
        engine.evaluate("@ip(.ip)", &response, None, None).unwrap(),
        AssertionResult::Pass
    );

    // IPv6 full
    let response = json!({"ip": "2001:0db8:85a3:0000:0000:8a2e:0370:7334"});
    assert_eq!(
        engine.evaluate("@ip(.ip)", &response, None, None).unwrap(),
        AssertionResult::Pass
    );

    // IPv4 min
    let response = json!({"ip": "0.0.0.0"});
    assert_eq!(
        engine.evaluate("@ip(.ip)", &response, None, None).unwrap(),
        AssertionResult::Pass
    );

    // IPv4 max
    let response = json!({"ip": "255.255.255.255"});
    assert_eq!(
        engine.evaluate("@ip(.ip)", &response, None, None).unwrap(),
        AssertionResult::Pass
    );

    // Out of range
    let response = json!({"ip": "256.0.0.1"});
    assert!(matches!(
        engine.evaluate("@ip(.ip)", &response, None, None).unwrap(),
        AssertionResult::Fail { .. }
    ));
}

#[test]
fn test_url_edge_cases() {
    let engine = AssertionEngine::new();

    // URL with port
    let response = json!({"url": "https://example.com:8080/path"});
    assert_eq!(
        engine
            .evaluate("@url(.url)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // URL with query and fragment
    let response = json!({"url": "https://example.com/path?query=1#anchor"});
    assert_eq!(
        engine
            .evaluate("@url(.url)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // FTP URL
    let response = json!({"url": "ftp://ftp.example.com"});
    assert_eq!(
        engine
            .evaluate("@url(.url)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Missing scheme
    let response = json!({"url": "example.com"});
    assert!(matches!(
        engine
            .evaluate("@url(.url)", &response, None, None)
            .unwrap(),
        AssertionResult::Fail { .. }
    ));

    // Empty
    let response = json!({"url": ""});
    assert!(matches!(
        engine
            .evaluate("@url(.url)", &response, None, None)
            .unwrap(),
        AssertionResult::Fail { .. }
    ));
}

#[test]
fn test_timestamp_edge_cases() {
    let engine = AssertionEngine::new();

    // Timestamp with timezone offset
    let response = json!({"ts": "2024-01-15T10:30:00+03:00"});
    assert_eq!(
        engine
            .evaluate("@timestamp(.ts)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // UTC with Z
    let response = json!({"ts": "2024-01-15T10:30:00Z"});
    assert_eq!(
        engine
            .evaluate("@timestamp(.ts)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Unix epoch
    let response = json!({"ts": "1970-01-01T00:00:00Z"});
    assert_eq!(
        engine
            .evaluate("@timestamp(.ts)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Date only (invalid RFC3339)
    let response = json!({"ts": "2024-01-15"});
    assert!(matches!(
        engine
            .evaluate("@timestamp(.ts)", &response, None, None)
            .unwrap(),
        AssertionResult::Fail { .. }
    ));

    // Time only (invalid)
    let response = json!({"ts": "10:30:00"});
    assert!(matches!(
        engine
            .evaluate("@timestamp(.ts)", &response, None, None)
            .unwrap(),
        AssertionResult::Fail { .. }
    ));
}

#[test]
fn test_len_edge_cases() {
    let engine = AssertionEngine::new();

    // Empty array
    let response = json!({"list": []});
    assert_eq!(
        engine
            .evaluate("@len(.list) == 0", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Empty string
    let response = json!({"text": ""});
    assert_eq!(
        engine
            .evaluate("@len(.text) == 0", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Unicode string (byte count, not char count: "привет" = 12 bytes in UTF-8)
    let response = json!({"text": "привет"});
    assert_eq!(
        engine
            .evaluate("@len(.text) == 12", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // ASCII string (byte count matches char count)
    let response = json!({"text": "hello"});
    assert_eq!(
        engine
            .evaluate("@len(.text) == 5", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Nested array
    let response = json!({"list": [[1, 2], [3, 4, 5]]});
    assert_eq!(
        engine
            .evaluate("@len(.list) == 2", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );

    // Number returns null (can't measure length)
    let response = json!({"num": 12345});
    assert!(matches!(
        engine
            .evaluate("@len(.num) == 5", &response, None, None)
            .unwrap(),
        AssertionResult::Fail { .. }
    ));
}

#[test]
fn test_header_case_insensitive() {
    let engine = AssertionEngine::new();
    let response = json!({});
    let mut headers = HashMap::new();
    headers.insert("Content-Type".to_string(), "application/json".to_string());

    // @has_header returns boolean - use explicit comparison
    let result = engine
        .evaluate(
            "@has_header(content-type) == true",
            &response,
            Some(&headers),
            None,
        )
        .unwrap();

    assert!(matches!(result, AssertionResult::Pass));
}

#[test]
fn test_trailer_plugin_basic() {
    let engine = AssertionEngine::new();
    let response = json!({});
    let mut trailers = HashMap::new();
    trailers.insert("grpc-status".to_string(), "0".to_string());

    let result = engine
        .evaluate(
            "@trailer(\"grpc-status\")",
            &response,
            None,
            Some(&trailers),
        )
        .unwrap();
    assert_eq!(result, AssertionResult::Pass);

    let result = engine
        .evaluate("@trailer(\"x-missing\")", &response, None, Some(&trailers))
        .unwrap();
    assert!(matches!(result, AssertionResult::Fail { .. }));
}

#[test]
fn test_multiple_plugins_in_one_assertion() {
    let engine = AssertionEngine::new();
    let response = json!({
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "email": "test@example.com",
        "url": "https://example.com",
        "items": [1, 2, 3]
    });

    // All should pass
    assert_eq!(
        engine
            .evaluate("@uuid(.id)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );
    assert_eq!(
        engine
            .evaluate("@email(.email)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );
    assert_eq!(
        engine
            .evaluate("@url(.url)", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );
    assert_eq!(
        engine
            .evaluate("@len(.items) == 3", &response, None, None)
            .unwrap(),
        AssertionResult::Pass
    );
}
