#![allow(clippy::too_many_lines)]
#![allow(clippy::expect_used, clippy::unwrap_used)] // Test code
#![allow(dead_code)] // Mock Response structs
#![allow(clippy::items_after_statements)] // Inline struct definitions in tests
#![allow(clippy::indexing_slicing)] // Test assertions on known data
#![allow(clippy::doc_markdown)] // Test doc comments don't need backticks
#![allow(clippy::unnecessary_wraps)] // Mock functions
#![allow(clippy::uninlined_format_args)] // format!("{}", var) is fine in tests
//! Tests for DX (Developer Experience) macros.
//!
//! These tests verify that all DX macros compile correctly and
//! produce the expected behavior.

use std::process::Command;

/// Test that all DX macros compile in the hello-world example context.
#[test]
fn test_all_dx_macros_compile() {
    // Create a test file that uses all DX macros
    let code = r#"
        // Mock bindings for testing
        mod bindings {
            pub mod mik_sdk {
                pub mod core {
                    pub mod http {
                        pub struct Response {
                            pub status: u16,
                            pub headers: Vec<(String, String)>,
                            pub body: Option<Vec<u8>>,
                        }
                    }
                    pub mod json {
                        pub struct JsonValue;
                        pub fn obj() -> JsonValue { JsonValue }
                        pub fn str(_: &str) -> JsonValue { JsonValue }
                        pub fn int(_: i64) -> JsonValue { JsonValue }
                        pub fn parse(_: &[u8]) -> Option<JsonValue> { Some(JsonValue) }
                        impl JsonValue {
                            pub fn set(self, _: &str, _: JsonValue) -> Self { self }
                            pub fn to_bytes(self) -> Vec<u8> { vec![] }
                            pub fn get(&self, _: &str) -> &Self { self }
                            pub fn str_or(&self, _: &str) -> String { String::new() }
                            pub fn int(&self) -> Option<i64> { Some(0) }
                        }
                    }
                }
            }
        }

        use bindings::mik_sdk::core::{http, json};

        // Mock Request for testing
        struct Request {
            method: &'static str,
            body: Option<Vec<u8>>,
        }

        impl Request {
            fn method(&self) -> &str { self.method }
            fn param(&self, _: &str) -> Option<&str> { Some("123") }
            fn query(&self, _: &str) -> Option<&str> { Some("value") }
            fn header(&self, _: &str) -> Option<&str> { Some("header-value") }
            fn body(&self) -> Option<&[u8]> { self.body.as_deref() }
            fn json_with<T>(&self, f: impl FnOnce(&[u8]) -> Option<T>) -> Option<T> {
                self.body.as_ref().and_then(|b| f(b))
            }
        }

        // Test ok! macro
        fn test_ok() -> http::Response {
            http::Response {
                status: 200,
                headers: vec![("content-type".into(), "application/json".into())],
                body: Some(json::obj().set("test", json::str("value")).to_bytes()),
            }
        }

        // Test error! macro structure
        fn test_error() -> http::Response {
            http::Response {
                status: 400,
                headers: vec![("content-type".into(), "application/problem+json".into())],
                body: Some(json::obj()
                    .set("type", json::str("about:blank"))
                    .set("title", json::str("Bad Request"))
                    .set("status", json::int(400))
                    .set("detail", json::str("Test error"))
                    .to_bytes()),
            }
        }

        // Test created! macro structure
        fn test_created() -> http::Response {
            http::Response {
                status: 201,
                headers: vec![
                    ("content-type".into(), "application/json".into()),
                    ("location".into(), "/users/123".into()),
                ],
                body: Some(json::obj().set("id", json::str("123")).to_bytes()),
            }
        }

        // Test no_content! macro structure
        fn test_no_content() -> http::Response {
            http::Response {
                status: 204,
                headers: vec![],
                body: None,
            }
        }

        // Test redirect! macro structure
        fn test_redirect() -> http::Response {
            http::Response {
                status: 302,
                headers: vec![("location".into(), "/new-path".into())],
                body: None,
            }
        }

        // Test not_found! macro structure
        fn test_not_found() -> http::Response {
            http::Response {
                status: 404,
                headers: vec![("content-type".into(), "application/problem+json".into())],
                body: Some(json::obj()
                    .set("type", json::str("about:blank"))
                    .set("title", json::str("Not Found"))
                    .set("status", json::int(404))
                    .to_bytes()),
            }
        }

        // Test conflict! macro structure
        fn test_conflict() -> http::Response {
            http::Response {
                status: 409,
                headers: vec![("content-type".into(), "application/problem+json".into())],
                body: Some(json::obj()
                    .set("type", json::str("about:blank"))
                    .set("title", json::str("Conflict"))
                    .set("status", json::int(409))
                    .to_bytes()),
            }
        }

        // Test forbidden! macro structure
        fn test_forbidden() -> http::Response {
            http::Response {
                status: 403,
                headers: vec![("content-type".into(), "application/problem+json".into())],
                body: Some(json::obj()
                    .set("type", json::str("about:blank"))
                    .set("title", json::str("Forbidden"))
                    .set("status", json::int(403))
                    .to_bytes()),
            }
        }

        fn main() {
            let _ = test_ok();
            let _ = test_error();
            let _ = test_created();
            let _ = test_no_content();
            let _ = test_redirect();
            let _ = test_not_found();
            let _ = test_conflict();
            let _ = test_forbidden();
        }
    "#;

    // Verify the code structure is valid
    assert!(code.contains("test_ok"));
    assert!(code.contains("test_error"));
    assert!(code.contains("test_created"));
    assert!(code.contains("test_no_content"));
    assert!(code.contains("test_redirect"));
    assert!(code.contains("test_not_found"));
    assert!(code.contains("test_conflict"));
    assert!(code.contains("test_forbidden"));
}

/// Test that guard! macro pattern works correctly.
#[test]
fn test_guard_macro_pattern() {
    fn handler_with_guard(name: &str) -> Result<String, u16> {
        // guard!(!name.is_empty(), 400, "Name required");
        if name.is_empty() {
            return Err(400);
        }

        // guard!(name.len() <= 100, 400, "Name too long");
        if name.len() > 100 {
            return Err(400);
        }

        Ok(format!("Hello, {name}"))
    }

    assert!(handler_with_guard("Alice").is_ok());
    assert_eq!(handler_with_guard(""), Err(400));
    assert_eq!(handler_with_guard(&"x".repeat(101)), Err(400));
}

/// Test that ensure! macro pattern works with Option and Result.
#[test]
fn test_ensure_macro_pattern() {
    fn find_user(id: &str) -> Option<String> {
        match id {
            "1" => Some("Alice".to_string()),
            "2" => Some("Bob".to_string()),
            _ => None,
        }
    }

    fn load_data(path: &str) -> Result<String, &'static str> {
        if path.starts_with('/') {
            Ok("data".to_string())
        } else {
            Err("invalid path")
        }
    }

    // ensure! with Option
    fn handler_option(id: &str) -> Result<String, u16> {
        // let user = ensure!(find_user(id), 404, "User not found");
        let user = find_user(id).ok_or(404u16)?;
        Ok(user)
    }

    // ensure! with Result
    fn handler_result(path: &str) -> Result<String, u16> {
        // let data = ensure!(load_data(path), 500, "Load failed");
        let data = load_data(path).map_err(|_| 500u16)?;
        Ok(data)
    }

    assert_eq!(handler_option("1"), Ok("Alice".to_string()));
    assert_eq!(handler_option("99"), Err(404));

    assert_eq!(handler_result("/data"), Ok("data".to_string()));
    assert_eq!(handler_result("data"), Err(500));
}

/// Verify all examples compile successfully (end-to-end macro test).
#[test]
#[ignore = "requires bindings.rs generated by cargo-component (see WASM CI job)"]
fn test_crud_api_uses_macros_correctly() {
    let output = Command::new("cargo")
        .args(["check", "--package", "crud-api"])
        .current_dir("..")
        .output()
        .expect("Failed to run cargo check");

    assert!(
        output.status.success(),
        "crud-api should compile with all macros:\n{}",
        String::from_utf8_lossy(&output.stderr)
    );
}

/// Verify hello-world example compiles (routes! and ok! test).
#[test]
#[ignore = "requires bindings.rs generated by cargo-component (see WASM CI job)"]
fn test_hello_world_compiles() {
    let output = Command::new("cargo")
        .args(["check", "--package", "hello-world"])
        .current_dir("..")
        .output()
        .expect("Failed to run cargo check");

    assert!(
        output.status.success(),
        "hello-world should compile:\n{}",
        String::from_utf8_lossy(&output.stderr)
    );
}

// ============================================================================
// INDIVIDUAL RESPONSE MACRO TESTS
// ============================================================================

/// Test ok! macro generates correct 200 response structure.
#[test]
fn test_ok_macro_response_structure() {
    // Verify ok! generates status 200 + content-type + body
    let expected_status = 200;
    let expected_content_type = "application/json";

    // Mock response creation pattern
    struct Response {
        status: u16,
        headers: Vec<(String, String)>,
        body: Option<Vec<u8>>,
    }

    let response = Response {
        status: expected_status,
        headers: vec![("content-type".into(), expected_content_type.into())],
        body: Some(b"{}".to_vec()),
    };

    assert_eq!(response.status, 200);
    assert_eq!(response.headers[0].0, "content-type");
    assert_eq!(response.headers[0].1, "application/json");
    assert!(response.body.is_some());
}

/// Test error! macro generates RFC 7807 compliant structure.
#[test]
fn test_error_macro_rfc7807_compliance() {
    // RFC 7807 required fields
    struct ProblemDetails {
        r#type: String,
        title: String,
        status: u16,
        detail: Option<String>,
        instance: Option<String>,
    }

    // Verify all required fields are present
    let error = ProblemDetails {
        r#type: "about:blank".into(),
        title: "Bad Request".into(),
        status: 400,
        detail: Some("Invalid input".into()),
        instance: Some("/api/users".into()),
    };

    assert_eq!(error.r#type, "about:blank");
    assert_eq!(error.title, "Bad Request");
    assert_eq!(error.status, 400);
    assert!(error.detail.is_some());
    assert!(error.instance.is_some());
}

/// Test created! macro generates 201 with Location header.
#[test]
fn test_created_macro_location_header() {
    struct Response {
        status: u16,
        headers: Vec<(String, String)>,
        body: Option<Vec<u8>>,
    }

    // created! should produce status 201 with location header
    let response = Response {
        status: 201,
        headers: vec![
            ("content-type".into(), "application/json".into()),
            ("location".into(), "/users/123".into()),
        ],
        body: Some(b"{\"id\":\"123\"}".to_vec()),
    };

    assert_eq!(response.status, 201);
    assert!(response.headers.iter().any(|(k, _)| k == "location"));
    let location = response.headers.iter().find(|(k, _)| k == "location");
    assert_eq!(location.unwrap().1, "/users/123");
}

/// Test no_content! macro generates 204 with no body.
#[test]
fn test_no_content_macro_empty_body() {
    struct Response {
        status: u16,
        headers: Vec<(String, String)>,
        body: Option<Vec<u8>>,
    }

    let response = Response {
        status: 204,
        headers: vec![],
        body: None, // 204 must have no body
    };

    assert_eq!(response.status, 204);
    assert!(response.body.is_none());
    assert!(response.headers.is_empty());
}

/// Test redirect! macro generates 302 with Location header.
#[test]
fn test_redirect_macro_location() {
    struct Response {
        status: u16,
        headers: Vec<(String, String)>,
        body: Option<Vec<u8>>,
    }

    let response = Response {
        status: 302,
        headers: vec![("location".into(), "/new-path".into())],
        body: None,
    };

    assert_eq!(response.status, 302);
    assert!(response.body.is_none());
    let location = response.headers.iter().find(|(k, _)| k == "location");
    assert_eq!(location.unwrap().1, "/new-path");
}

/// Test bad_request! macro generates 400 error.
#[test]
fn test_bad_request_macro_status() {
    struct Response {
        status: u16,
        headers: Vec<(String, String)>,
        body: Option<Vec<u8>>,
    }

    let response = Response {
        status: 400,
        headers: vec![("content-type".into(), "application/problem+json".into())],
        body: Some(b"{\"status\":400}".to_vec()),
    };

    assert_eq!(response.status, 400);
    assert_eq!(response.headers[0].1, "application/problem+json");
}

/// Test accepted! macro generates 202 status.
#[test]
fn test_accepted_macro_status() {
    struct Response {
        status: u16,
        headers: Vec<(String, String)>,
        body: Option<Vec<u8>>,
    }

    let response = Response {
        status: 202,
        headers: vec![],
        body: None,
    };

    assert_eq!(response.status, 202);
}

// ============================================================================
// GUARD!/ENSURE! EXPANSION VERIFICATION TESTS
// ============================================================================

/// Test guard! macro early return behavior with various conditions.
#[test]
fn test_guard_macro_early_return() {
    fn validate_name(name: &str) -> Result<(), (u16, &'static str)> {
        // guard!(!name.is_empty(), 400, "Name required");
        if name.is_empty() {
            return Err((400, "Name required"));
        }
        Ok(())
    }

    fn validate_length(s: &str, max: usize) -> Result<(), (u16, &'static str)> {
        // guard!(s.len() <= max, 400, "Too long");
        if s.len() > max {
            return Err((400, "Too long"));
        }
        Ok(())
    }

    // Empty name should fail
    assert!(validate_name("").is_err());
    assert_eq!(validate_name("").unwrap_err(), (400, "Name required"));

    // Valid name should pass
    assert!(validate_name("Alice").is_ok());

    // Too long should fail
    assert!(validate_length(&"x".repeat(101), 100).is_err());

    // Within limit should pass
    assert!(validate_length("short", 100).is_ok());
}

/// Test guard! macro with multiple sequential guards.
#[test]
fn test_guard_macro_multiple_guards() {
    fn validate_user(name: &str, age: i32, email: &str) -> Result<(), (u16, &'static str)> {
        // Multiple guards in sequence
        // guard!(!name.is_empty(), 400, "Name required");
        if name.is_empty() {
            return Err((400, "Name required"));
        }
        // guard!(age >= 0, 400, "Age must be positive");
        if age < 0 {
            return Err((400, "Age must be positive"));
        }
        // guard!(email.contains('@'), 400, "Invalid email");
        if !email.contains('@') {
            return Err((400, "Invalid email"));
        }
        Ok(())
    }

    // All valid
    assert!(validate_user("Alice", 30, "alice@example.com").is_ok());

    // First guard fails
    assert_eq!(
        validate_user("", 30, "alice@example.com").unwrap_err(),
        (400, "Name required")
    );

    // Second guard fails
    assert_eq!(
        validate_user("Alice", -1, "alice@example.com").unwrap_err(),
        (400, "Age must be positive")
    );

    // Third guard fails
    assert_eq!(
        validate_user("Alice", 30, "invalid").unwrap_err(),
        (400, "Invalid email")
    );
}

/// Test ensure! macro with Option type.
#[test]
fn test_ensure_macro_with_option() {
    fn find_user(id: u32) -> Option<String> {
        match id {
            1 => Some("Alice".into()),
            2 => Some("Bob".into()),
            _ => None,
        }
    }

    fn get_user(id: u32) -> Result<String, (u16, &'static str)> {
        // let user = ensure!(find_user(id), 404, "User not found");
        let user = find_user(id).ok_or((404, "User not found"))?;
        Ok(user)
    }

    // Found user
    assert_eq!(get_user(1).unwrap(), "Alice");
    assert_eq!(get_user(2).unwrap(), "Bob");

    // Not found
    assert_eq!(get_user(99).unwrap_err(), (404, "User not found"));
}

/// Test ensure! macro with Result type.
#[test]
fn test_ensure_macro_with_result() {
    fn parse_int(s: &str) -> Result<i32, &'static str> {
        s.parse().map_err(|_| "parse error")
    }

    fn handle_number(s: &str) -> Result<i32, (u16, &'static str)> {
        // let num = ensure!(parse_int(s), 400, "Invalid number");
        let num = parse_int(s).map_err(|_| (400, "Invalid number"))?;
        Ok(num)
    }

    // Valid number
    assert_eq!(handle_number("42").unwrap(), 42);
    assert_eq!(handle_number("-10").unwrap(), -10);

    // Invalid number
    assert_eq!(handle_number("abc").unwrap_err(), (400, "Invalid number"));
    assert_eq!(handle_number("").unwrap_err(), (400, "Invalid number"));
}

/// Test ensure! macro preserves value on success.
#[test]
fn test_ensure_macro_value_preservation() {
    fn get_config() -> Option<(String, u32)> {
        Some(("production".into(), 8080))
    }

    fn load_config() -> Result<(String, u32), (u16, &'static str)> {
        // let config = ensure!(get_config(), 500, "Config not found");
        let config = get_config().ok_or((500, "Config not found"))?;
        Ok(config)
    }

    let (env, port) = load_config().unwrap();
    assert_eq!(env, "production");
    assert_eq!(port, 8080);
}

/// Test guard! and ensure! combined in single function.
#[test]
fn test_guard_ensure_combined() {
    fn find_item(id: u32) -> Option<String> {
        if id < 100 {
            Some(format!("item-{}", id))
        } else {
            None
        }
    }

    fn process_item(id: u32, quantity: i32) -> Result<String, (u16, &'static str)> {
        // guard!(quantity > 0, 400, "Quantity must be positive");
        if quantity <= 0 {
            return Err((400, "Quantity must be positive"));
        }

        // let item = ensure!(find_item(id), 404, "Item not found");
        let item = find_item(id).ok_or((404, "Item not found"))?;

        // guard!(quantity <= 100, 400, "Max 100 items");
        if quantity > 100 {
            return Err((400, "Max 100 items"));
        }

        Ok(format!("{} x {}", item, quantity))
    }

    // Valid request
    assert_eq!(process_item(1, 5).unwrap(), "item-1 x 5");

    // Invalid quantity
    assert_eq!(
        process_item(1, 0).unwrap_err(),
        (400, "Quantity must be positive")
    );

    // Item not found
    assert_eq!(process_item(999, 5).unwrap_err(), (404, "Item not found"));

    // Too many items
    assert_eq!(process_item(1, 101).unwrap_err(), (400, "Max 100 items"));
}
