//! Tests for el-platform backends.

#[cfg(test)]
mod tests {
    use crate::{
        backend_for, backends::{server::ServerBackend, web::WebBackend},
        config::{PlatformConfig, PlatformTarget},
        node::PlatformNode,
        PlatformBackend,
    };

    // ── Test 1: PlatformTarget::from_str parses all targets ──────────────────
    #[test]
    fn test_platform_target_from_str() {
        assert_eq!(PlatformTarget::from_str("web"), Some(PlatformTarget::Web));
        assert_eq!(PlatformTarget::from_str("server"), Some(PlatformTarget::Server));
        assert_eq!(PlatformTarget::from_str("ios"), Some(PlatformTarget::Ios));
        assert_eq!(PlatformTarget::from_str("android"), Some(PlatformTarget::Android));
        assert_eq!(PlatformTarget::from_str("macos"), Some(PlatformTarget::Macos));
        assert_eq!(PlatformTarget::from_str("linux"), Some(PlatformTarget::Linux));
        assert_eq!(PlatformTarget::from_str("windows"), Some(PlatformTarget::Windows));
        assert_eq!(PlatformTarget::from_str("unknown"), None);
    }

    // ── Test 2: PlatformTarget::as_str round-trips ───────────────────────────
    #[test]
    fn test_platform_target_as_str() {
        assert_eq!(PlatformTarget::Web.as_str(), "web");
        assert_eq!(PlatformTarget::Server.as_str(), "server");
        assert_eq!(PlatformTarget::Ios.as_str(), "ios");
    }

    // ── Test 3: is_native() identifies native targets ────────────────────────
    #[test]
    fn test_is_native() {
        assert!(!PlatformTarget::Web.is_native());
        assert!(!PlatformTarget::Server.is_native());
        assert!(PlatformTarget::Ios.is_native());
        assert!(PlatformTarget::Android.is_native());
        assert!(PlatformTarget::Macos.is_native());
        assert!(PlatformTarget::Linux.is_native());
        assert!(PlatformTarget::Windows.is_native());
    }

    // ── Test 4: PlatformConfig defaults to web ───────────────────────────────
    #[test]
    fn test_platform_config_default() {
        let cfg = PlatformConfig::default();
        assert_eq!(cfg.target, PlatformTarget::Web);
        assert!(!cfg.ssr);
    }

    // ── Test 5: PlatformConfig with_ssr ──────────────────────────────────────
    #[test]
    fn test_platform_config_with_ssr() {
        let cfg = PlatformConfig::new(PlatformTarget::Server).with_ssr(true);
        assert_eq!(cfg.target, PlatformTarget::Server);
        assert!(cfg.ssr);
    }

    // ── Test 6: WebBackend renders element to HTML ───────────────────────────
    #[test]
    fn test_web_backend_renders_element() {
        let backend = WebBackend::new();
        let mut div = backend.create_element("div").unwrap();
        backend.set_attribute(&mut div, "class", "container").unwrap();
        backend.append_child(&mut div, backend.create_text("Hello").unwrap()).unwrap();

        let html = backend.render_to_string(&div).unwrap();
        assert!(html.contains("<div"), "should contain div tag");
        assert!(html.contains("container"), "should contain class name");
        assert!(html.contains("Hello"), "should contain text content");
        assert!(html.contains("</div>"), "should close div tag");
    }

    // ── Test 7: ServerBackend supports SSR ───────────────────────────────────
    #[test]
    fn test_server_backend_supports_ssr() {
        let backend = ServerBackend::new();
        assert!(backend.supports_ssr());
    }

    // ── Test 8: ServerBackend renders full HTML page ─────────────────────────
    #[test]
    fn test_server_backend_render_page() {
        let backend = ServerBackend::new();
        let root = PlatformNode::element("div")
            .with_attr("id", "content")
            .with_child(PlatformNode::text("Hello World"));

        let page = backend.render_page(&root, "My App", "/app.js").unwrap();
        assert!(page.contains("<!DOCTYPE html>"), "should be full HTML doc");
        assert!(page.contains("My App"), "should include title");
        assert!(page.contains("Hello World"), "should include content");
        assert!(page.contains("/app.js"), "should include script src");
    }

    // ── Test 9: PlatformNode::to_html renders nested tree ────────────────────
    #[test]
    fn test_platform_node_to_html_nested() {
        let node = PlatformNode::element("ul")
            .with_child(PlatformNode::element("li").with_child(PlatformNode::text("item 1")))
            .with_child(PlatformNode::element("li").with_child(PlatformNode::text("item 2")));

        let html = node.to_html();
        assert!(html.contains("<ul>"));
        assert!(html.contains("<li>item 1</li>"));
        assert!(html.contains("<li>item 2</li>"));
        assert!(html.contains("</ul>"));
    }

    // ── Test 10: PlatformNode::to_html escapes text content ──────────────────
    #[test]
    fn test_html_escaping() {
        let node = PlatformNode::element("span")
            .with_child(PlatformNode::text("<script>alert('xss')</script>"));
        let html = node.to_html();
        assert!(!html.contains("<script>"), "should escape <script>");
        assert!(html.contains("&lt;script&gt;"), "should contain escaped form");
    }

    // ── Test 11: Void elements render correctly ───────────────────────────────
    #[test]
    fn test_void_elements() {
        let node = PlatformNode::element("input").with_attr("type", "text");
        let html = node.to_html();
        assert!(html.contains("<input"), "should have input");
        assert!(!html.contains("</input>"), "void element should not have closing tag");
        assert!(html.contains("/>"), "should self-close");
    }

    // ── Test 12: Fragment node renders children inline ────────────────────────
    #[test]
    fn test_fragment_node() {
        let frag = PlatformNode::fragment()
            .with_child(PlatformNode::element("span").with_child(PlatformNode::text("A")))
            .with_child(PlatformNode::element("span").with_child(PlatformNode::text("B")));
        let html = frag.to_html();
        assert!(html.contains("<span>A</span>"));
        assert!(html.contains("<span>B</span>"));
        // No wrapping element
        assert!(!html.starts_with('<') || html.starts_with("<span>"));
    }

    // ── Test 13: WebBackend remove_child works ───────────────────────────────
    #[test]
    fn test_web_backend_remove_child() {
        let backend = WebBackend::new();
        let mut parent = PlatformNode::element("div");
        backend
            .append_child(&mut parent, PlatformNode::text("first"))
            .unwrap();
        backend
            .append_child(&mut parent, PlatformNode::text("second"))
            .unwrap();
        assert_eq!(parent.children.len(), 2);

        backend.remove_child(&mut parent, 0).unwrap();
        assert_eq!(parent.children.len(), 1);
        assert_eq!(parent.children[0].text_content(), Some("second"));
    }

    // ── Test 14: WebBackend replace_child works ──────────────────────────────
    #[test]
    fn test_web_backend_replace_child() {
        let backend = WebBackend::new();
        let mut parent = PlatformNode::element("div");
        backend
            .append_child(&mut parent, PlatformNode::text("old"))
            .unwrap();
        backend
            .replace_child(&mut parent, 0, PlatformNode::text("new"))
            .unwrap();
        assert_eq!(parent.children[0].text_content(), Some("new"));
    }

    // ── Test 15: backend_for() returns correct backend names ─────────────────
    #[test]
    fn test_backend_for_names() {
        assert_eq!(backend_for(&PlatformTarget::Web).name(), "web");
        assert_eq!(backend_for(&PlatformTarget::Server).name(), "server");
        assert_eq!(backend_for(&PlatformTarget::Ios).name(), "ios");
        assert_eq!(backend_for(&PlatformTarget::Android).name(), "android");
        assert_eq!(backend_for(&PlatformTarget::Macos).name(), "macos");
        assert_eq!(backend_for(&PlatformTarget::Linux).name(), "linux");
        assert_eq!(backend_for(&PlatformTarget::Windows).name(), "windows");
    }

    // ── Test 16: WebBackend bind_event emits data attribute ──────────────────
    #[test]
    fn test_web_backend_bind_event() {
        let backend = WebBackend::new();
        let mut btn = backend.create_element("button").unwrap();
        backend
            .bind_event(&mut btn, "click", Box::new(|_| {}))
            .unwrap();
        let has_attr = btn.attributes.iter().any(|a| a.name == "data-el-click");
        assert!(has_attr, "should emit data-el-click attribute");
    }

    // ── Test 17: ServerBackend bind_event emits hydration marker ─────────────
    #[test]
    fn test_server_backend_bind_event() {
        let backend = ServerBackend::new();
        let mut btn = PlatformNode::element("button");
        backend
            .bind_event(&mut btn, "click", Box::new(|_| {}))
            .unwrap();
        let has_attr = btn.attributes.iter().any(|a| a.name.contains("data-el-click"));
        assert!(has_attr, "should emit hydration marker for click event");
    }

    // ── Test 18: WebBackend backend does not support raw SSR flag ────────────
    #[test]
    fn test_web_backend_supports_ssr() {
        // Web backend supports ssr (renders to HTML for hydration)
        let backend = WebBackend::new();
        assert!(backend.supports_ssr());
    }

    // ── Test 19: remove_child out of bounds returns error ────────────────────
    #[test]
    fn test_remove_child_out_of_bounds() {
        let backend = WebBackend::new();
        let mut parent = PlatformNode::element("div");
        let result = backend.remove_child(&mut parent, 5);
        assert!(result.is_err(), "out-of-bounds remove should return error");
    }

    // ── Test 20: All native backends render to HTML for testing ──────────────
    #[test]
    fn test_native_backends_render_to_html() {
        // Native backends support render_to_string for testing and SSR fallback.
        let targets = [
            PlatformTarget::Ios,
            PlatformTarget::Android,
            PlatformTarget::Macos,
            PlatformTarget::Linux,
            PlatformTarget::Windows,
        ];
        for target in &targets {
            let backend = backend_for(target);
            let node = PlatformNode::element("div")
                .with_child(PlatformNode::text("test"));
            let html = backend.render_to_string(&node).unwrap();
            assert!(
                html.contains("test"),
                "{} backend should render text content",
                target.as_str()
            );
        }
    }
}
