"""
Tests for the StyledHTMLExporter class.

Note on Implementation:
    The current implementation generates CSS rules (e.g., #cell-0, #cell-0-input,
    #cell-0-output) and injects them into the HTML <head> section. However, the
    actual HTML body generated by nbconvert's default templates does NOT include
    elements with these IDs.

    This means the CSS rules have no effect on the output. To make the styles
    functional, the exporter would need to:
    1. Override the cell template to add the appropriate id attributes to cell
       container elements
    2. Create a custom Jinja2 template that includes these IDs in the rendered HTML

    These tests verify that the CSS is correctly generated and injected, but they
    cannot verify that the styles are actually applied to elements since those
    elements don't currently have the matching IDs.
"""

import base64
import os
import re
import tempfile

import nbformat as nbf
from bs4 import BeautifulSoup
from nbformat.v4 import new_code_cell, new_markdown_cell, new_notebook

from jupyter_export_html_style import StyledHTMLExporter

# Test image data: 1x1 red pixel PNG (89 50 4E 47 = PNG signature)
# This is a minimal valid PNG file for testing purposes
TEST_IMAGE_PNG = bytes.fromhex(
    "89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489"
    "0000000d49444154789c63f8cfc03f00050201055fc8f1d20000000049454e44ae426082"
)


def _parse_html(html_string):
    """Parse HTML string and return BeautifulSoup object.

    Args:
        html_string (str): HTML content to parse.

    Returns:
        (BeautifulSoup): Parsed HTML document.
    """
    return BeautifulSoup(html_string, "html.parser")


def _extract_css_rules(html_string):
    """Extract CSS rules from HTML style blocks.

    Args:
        html_string (str): HTML content containing style blocks.

    Returns:
        (dict): Dictionary mapping CSS selectors to their style properties.
            For example: {"#cell-0": {"background-color": "#f0f0f0"}}
    """
    soup = _parse_html(html_string)
    css_rules = {}

    # Find all style tags
    for style_tag in soup.find_all("style"):
        style_content = style_tag.string
        if style_content:
            # Parse CSS rules using regex
            # Matches patterns like: #cell-0 { background-color: #fff; padding: 10px }
            rule_pattern = r"([#.\w-]+)\s*\{([^}]+)\}"
            for match in re.finditer(rule_pattern, style_content):
                selector = match.group(1).strip()
                properties_str = match.group(2).strip()

                # Parse individual properties
                properties = {}
                for prop in properties_str.split(";"):
                    prop = prop.strip()
                    if ":" in prop:
                        key, value = prop.split(":", 1)
                        properties[key.strip()] = value.strip()

                css_rules[selector] = properties

    return css_rules


def _create_notebook_with_image(tmpdir, image_filename="test.png"):
    """Helper function to create a test notebook with an image reference.

    Args:
        tmpdir (str): Temporary directory path.
        image_filename (str): Name of the image file. Defaults to "test.png".

    Returns:
        (str): Path to the created notebook file.
    """
    # Create image file
    img_path = os.path.join(tmpdir, image_filename)
    with open(img_path, "wb") as f:
        f.write(TEST_IMAGE_PNG)

    # Create notebook with markdown cell referencing the image
    nb = new_notebook()
    md_cell = new_markdown_cell(f"![Test Image]({image_filename})")
    nb.cells.append(md_cell)

    # Write notebook to file
    nb_path = os.path.join(tmpdir, "test.ipynb")
    with open(nb_path, "w") as f:
        nbf.write(nb, f)

    return nb_path


def test_styled_html_exporter_initialization():
    """Test that StyledHTMLExporter can be initialized."""
    exporter = StyledHTMLExporter()
    assert exporter.template_name == "styled"
    assert exporter.export_from_notebook == "HTML (with styles)"


def test_export_notebook_without_styles():
    """Test exporting a notebook without style metadata."""
    exporter = StyledHTMLExporter()

    nb = new_notebook(cells=[new_code_cell("print('hello')"), new_markdown_cell("# Title")])

    output, resources = exporter.from_notebook_node(nb)

    assert output is not None
    assert isinstance(output, str)
    assert len(output) > 0


def test_export_notebook_with_styles():
    """Test exporting a notebook with style metadata."""
    exporter = StyledHTMLExporter()

    cell = new_code_cell("print('styled')")
    cell.metadata["style"] = {"background-color": "#f0f0f0"}

    nb = new_notebook(cells=[cell])

    output, resources = exporter.from_notebook_node(nb)

    assert output is not None
    assert isinstance(output, str)

    # Parse HTML and extract CSS rules
    soup = _parse_html(output)
    css_rules = _extract_css_rules(output)

    # Verify that custom style block exists
    style_tags = soup.find_all("style")
    assert len(style_tags) > 0, "No style tags found in HTML"

    # Check for the custom cell styles comment
    has_custom_comment = False
    for style_tag in style_tags:
        if style_tag.string and "Custom cell styles" in style_tag.string:
            has_custom_comment = True
            break
    assert has_custom_comment, "Custom cell styles comment not found"

    # Verify the specific CSS rule exists with correct property
    assert "#cell-0" in css_rules, "CSS rule for #cell-0 not found"
    assert "background-color" in css_rules["#cell-0"], "background-color property not found"
    assert css_rules["#cell-0"]["background-color"] == "#f0f0f0"


def test_generate_style_block_with_dict():
    """Test generating style block from dictionary styles."""
    exporter = StyledHTMLExporter()

    styles = {"cell-0": {"background-color": "#fff", "padding": "10px"}, "cell-1": {"color": "red"}}

    style_block = exporter._generate_style_block(styles)

    # Parse the generated style block
    css_rules = _extract_css_rules(style_block)

    # Verify structure
    assert "<style>" in style_block
    assert "</style>" in style_block

    # Verify specific CSS rules
    assert "#cell-0" in css_rules
    assert "#cell-1" in css_rules

    # Check properties for cell-0
    assert "background-color" in css_rules["#cell-0"]
    assert css_rules["#cell-0"]["background-color"] == "#fff"
    assert "padding" in css_rules["#cell-0"]
    assert css_rules["#cell-0"]["padding"] == "10px"

    # Check properties for cell-1
    assert "color" in css_rules["#cell-1"]
    assert css_rules["#cell-1"]["color"] == "red"


def test_generate_style_block_with_string():
    """Test generating style block from string styles."""
    exporter = StyledHTMLExporter()

    styles = {"cell-0": "background-color: #eee; padding: 5px;"}

    style_block = exporter._generate_style_block(styles)

    # Parse the generated style block
    css_rules = _extract_css_rules(style_block)

    # Verify structure
    assert "<style>" in style_block

    # Verify specific CSS rule and properties
    assert "#cell-0" in css_rules
    assert "background-color" in css_rules["#cell-0"]
    assert css_rules["#cell-0"]["background-color"] == "#eee"
    assert "padding" in css_rules["#cell-0"]
    assert css_rules["#cell-0"]["padding"] == "5px"


def test_generate_style_block_empty():
    """Test generating style block with no styles."""
    exporter = StyledHTMLExporter()

    styles = {}

    style_block = exporter._generate_style_block(styles)

    assert style_block == ""


def test_export_with_custom_template():
    """Test exporting with a custom template name."""
    exporter = StyledHTMLExporter()
    exporter.template_name = "lab"

    nb = new_notebook(cells=[new_code_cell("x = 1")])

    # This should not raise an error even if template doesn't exist
    # (nbconvert will handle template resolution)
    try:
        output, resources = exporter.from_notebook_node(nb)
        assert output is not None
    except Exception:
        # Template might not exist, which is okay for this test
        pass


def test_export_notebook_with_input_style():
    """Test exporting a notebook with input-style metadata."""
    exporter = StyledHTMLExporter()

    cell = new_code_cell("x = 1")
    cell.metadata["input-style"] = {"background-color": "#ffe"}

    nb = new_notebook(cells=[cell])

    output, resources = exporter.from_notebook_node(nb)

    # Parse and extract CSS rules
    css_rules = _extract_css_rules(output)

    # Verify the CSS rule for input styling
    assert output is not None
    assert "#cell-0-input" in css_rules, "CSS rule for #cell-0-input not found"
    assert "background-color" in css_rules["#cell-0-input"]
    assert css_rules["#cell-0-input"]["background-color"] == "#ffe"


def test_export_notebook_with_output_style():
    """Test exporting a notebook with output-style metadata."""
    exporter = StyledHTMLExporter()

    cell = new_code_cell("print('output')")
    cell.metadata["output-style"] = {"border": "2px solid blue"}

    nb = new_notebook(cells=[cell])

    output, resources = exporter.from_notebook_node(nb)

    # Parse and extract CSS rules
    css_rules = _extract_css_rules(output)

    # Verify the CSS rule for output styling
    assert output is not None
    assert "#cell-0-output" in css_rules, "CSS rule for #cell-0-output not found"
    assert "border" in css_rules["#cell-0-output"]
    assert css_rules["#cell-0-output"]["border"] == "2px solid blue"


def test_export_notebook_with_all_cell_styles():
    """Test exporting a notebook with cell, input, and output styles."""
    exporter = StyledHTMLExporter()

    cell = new_code_cell("y = 2")
    cell.metadata["style"] = {"padding": "10px"}
    cell.metadata["input-style"] = {"color": "red"}
    cell.metadata["output-style"] = {"font-weight": "bold"}

    nb = new_notebook(cells=[cell])

    output, resources = exporter.from_notebook_node(nb)

    # Parse and extract CSS rules
    css_rules = _extract_css_rules(output)

    # Verify all CSS rules are present
    assert output is not None
    assert "#cell-0" in css_rules, "CSS rule for #cell-0 not found"
    assert "#cell-0-input" in css_rules, "CSS rule for #cell-0-input not found"
    assert "#cell-0-output" in css_rules, "CSS rule for #cell-0-output not found"

    # Verify specific properties
    assert css_rules["#cell-0"]["padding"] == "10px"
    assert css_rules["#cell-0-input"]["color"] == "red"
    assert css_rules["#cell-0-output"]["font-weight"] == "bold"


def test_export_notebook_with_notebook_level_style():
    """Test exporting a notebook with notebook-level style metadata."""
    exporter = StyledHTMLExporter()

    nb = new_notebook(cells=[new_code_cell("z = 3")])
    nb.metadata["style"] = ".custom-class { color: green; }"

    output, resources = exporter.from_notebook_node(nb)

    # Parse HTML
    soup = _parse_html(output)

    # Verify notebook-level styles are present
    assert output is not None

    # Check for the custom notebook styles comment
    has_custom_comment = False
    for style_tag in soup.find_all("style"):
        if style_tag.string and "Custom notebook styles" in style_tag.string:
            has_custom_comment = True
            # Also verify the actual style content
            assert ".custom-class { color: green; }" in style_tag.string
            break

    assert has_custom_comment, "Custom notebook styles comment not found"


def test_export_notebook_with_stylesheet():
    """Test exporting a notebook with stylesheet metadata."""
    exporter = StyledHTMLExporter()

    nb = new_notebook(cells=[new_code_cell("a = 4")])
    nb.metadata["stylesheet"] = "https://example.com/style.css"

    output, resources = exporter.from_notebook_node(nb)

    # Parse HTML and find link tags
    soup = _parse_html(output)

    assert output is not None

    # Find the stylesheet link
    link_tags = soup.find_all("link", rel="stylesheet")
    stylesheet_urls = [link.get("href") for link in link_tags]

    assert "https://example.com/style.css" in stylesheet_urls, "Stylesheet link not found in HTML"


def test_export_notebook_with_multiple_stylesheets():
    """Test exporting a notebook with multiple stylesheets."""
    exporter = StyledHTMLExporter()

    nb = new_notebook(cells=[new_code_cell("b = 5")])
    nb.metadata["stylesheet"] = [
        "https://example.com/style1.css",
        "https://example.com/style2.css",
    ]

    output, resources = exporter.from_notebook_node(nb)

    # Parse HTML and find link tags
    soup = _parse_html(output)

    assert output is not None

    # Find all stylesheet links
    link_tags = soup.find_all("link", rel="stylesheet")
    stylesheet_urls = [link.get("href") for link in link_tags]

    assert "https://example.com/style1.css" in stylesheet_urls, "First stylesheet link not found"
    assert "https://example.com/style2.css" in stylesheet_urls, "Second stylesheet link not found"


def test_generate_notebook_style_block_with_style():
    """Test generating notebook style block with inline CSS."""
    exporter = StyledHTMLExporter()

    notebook_styles = {"style": "body { background: white; }"}
    style_block = exporter._generate_notebook_style_block(notebook_styles)

    # Parse the generated style block
    soup = _parse_html(style_block)

    assert "<style>" in style_block

    # Check for the custom notebook styles comment
    style_tags = soup.find_all("style")
    assert len(style_tags) > 0

    has_custom_comment = False
    for style_tag in style_tags:
        if style_tag.string and "Custom notebook styles" in style_tag.string:
            has_custom_comment = True
            assert "body { background: white; }" in style_tag.string
            break

    assert has_custom_comment, "Custom notebook styles comment not found"


def test_generate_notebook_style_block_with_stylesheet():
    """Test generating notebook style block with external stylesheet."""
    exporter = StyledHTMLExporter()

    notebook_styles = {"stylesheet": "https://cdn.example.com/theme.css"}
    style_block = exporter._generate_notebook_style_block(notebook_styles)

    # Parse and find link tags
    soup = _parse_html(style_block)
    link_tags = soup.find_all("link", rel="stylesheet")

    assert len(link_tags) > 0, "No stylesheet link found"
    assert link_tags[0].get("href") == "https://cdn.example.com/theme.css"


def test_generate_notebook_style_block_empty():
    """Test generating notebook style block with no styles."""
    exporter = StyledHTMLExporter()

    notebook_styles = {}
    style_block = exporter._generate_notebook_style_block(notebook_styles)

    assert style_block == ""


def test_embed_local_css_file():
    """Test that local CSS files are embedded as inline styles."""
    with tempfile.TemporaryDirectory() as tmpdir:
        # Create a test CSS file
        css_content = "body { background-color: #f0f0f0; }\n.custom { color: red; }"
        css_path = os.path.join(tmpdir, "custom.css")
        with open(css_path, "w", encoding="utf-8") as f:
            f.write(css_content)

        # Create resources with the base path
        resources = {"metadata": {"path": tmpdir}}

        exporter = StyledHTMLExporter()
        notebook_styles = {"stylesheet": "custom.css"}
        style_block = exporter._generate_notebook_style_block(notebook_styles, resources)

        # Verify the CSS is embedded as a style tag, not a link tag
        assert "<style>" in style_block, "CSS should be embedded as style tag"
        assert "Embedded stylesheet: custom.css" in style_block
        assert "body { background-color: #f0f0f0; }" in style_block
        assert ".custom { color: red; }" in style_block

        # Verify it's NOT a link tag
        assert '<link rel="stylesheet"' not in style_block


def test_embed_multiple_local_css_files():
    """Test that multiple local CSS files are embedded as inline styles."""
    with tempfile.TemporaryDirectory() as tmpdir:
        # Create two test CSS files
        css1_content = "body { margin: 0; }"
        css1_path = os.path.join(tmpdir, "style1.css")
        with open(css1_path, "w", encoding="utf-8") as f:
            f.write(css1_content)

        css2_content = ".header { padding: 10px; }"
        css2_path = os.path.join(tmpdir, "style2.css")
        with open(css2_path, "w", encoding="utf-8") as f:
            f.write(css2_content)

        # Create resources with the base path
        resources = {"metadata": {"path": tmpdir}}

        exporter = StyledHTMLExporter()
        notebook_styles = {"stylesheet": ["style1.css", "style2.css"]}
        style_block = exporter._generate_notebook_style_block(notebook_styles, resources)

        # Verify both CSS files are embedded
        assert style_block.count("<style>") == 2, "Should have two style tags"
        assert "body { margin: 0; }" in style_block
        assert ".header { padding: 10px; }" in style_block
        assert "Embedded stylesheet: style1.css" in style_block
        assert "Embedded stylesheet: style2.css" in style_block


def test_remote_css_remains_as_link():
    """Test that remote CSS URLs remain as link tags (not embedded)."""
    exporter = StyledHTMLExporter()
    resources = {"metadata": {"path": "."}}

    notebook_styles = {
        "stylesheet": ["https://example.com/style.css", "http://cdn.example.com/theme.css"]
    }
    style_block = exporter._generate_notebook_style_block(notebook_styles, resources)

    # Parse HTML to find link tags
    soup = _parse_html(style_block)
    link_tags = soup.find_all("link", rel="stylesheet")

    assert len(link_tags) == 2, "Should have two link tags for remote URLs"
    assert link_tags[0].get("href") == "https://example.com/style.css"
    assert link_tags[1].get("href") == "http://cdn.example.com/theme.css"

    # Verify they are NOT embedded as style tags
    assert "Embedded stylesheet:" not in style_block


def test_mixed_local_and_remote_css():
    """Test mixing local CSS files (embedded) and remote URLs (linked)."""
    with tempfile.TemporaryDirectory() as tmpdir:
        # Create a local CSS file
        css_content = ".local-style { color: blue; }"
        css_path = os.path.join(tmpdir, "local.css")
        with open(css_path, "w", encoding="utf-8") as f:
            f.write(css_content)

        # Create resources with the base path
        resources = {"metadata": {"path": tmpdir}}

        exporter = StyledHTMLExporter()
        notebook_styles = {"stylesheet": ["local.css", "https://example.com/remote.css"]}
        style_block = exporter._generate_notebook_style_block(notebook_styles, resources)

        # Verify local CSS is embedded
        assert ".local-style { color: blue; }" in style_block
        assert "Embedded stylesheet: local.css" in style_block

        # Verify remote CSS is linked
        soup = _parse_html(style_block)
        link_tags = soup.find_all("link", rel="stylesheet")
        assert len(link_tags) == 1, "Should have one link tag for remote URL"
        assert link_tags[0].get("href") == "https://example.com/remote.css"


def test_nonexistent_local_css_fallback_to_link():
    """Test that non-existent local CSS files fallback to link tags."""
    resources = {"metadata": {"path": tempfile.gettempdir()}}

    exporter = StyledHTMLExporter()
    notebook_styles = {"stylesheet": "nonexistent.css"}
    style_block = exporter._generate_notebook_style_block(notebook_styles, resources)

    # Should fallback to link tag since file doesn't exist
    soup = _parse_html(style_block)
    link_tags = soup.find_all("link", rel="stylesheet")
    assert len(link_tags) == 1, "Should have link tag as fallback"
    assert link_tags[0].get("href") == "nonexistent.css"


def test_embed_local_css_in_notebook_export():
    """Test end-to-end: local CSS file is embedded when exporting a notebook."""
    with tempfile.TemporaryDirectory() as tmpdir:
        # Create a test CSS file
        css_content = ".notebook-custom { font-size: 16px; }"
        css_path = os.path.join(tmpdir, "notebook.css")
        with open(css_path, "w", encoding="utf-8") as f:
            f.write(css_content)

        # Create a simple notebook with stylesheet metadata
        nb = new_notebook(cells=[new_code_cell("x = 1")])
        nb.metadata["stylesheet"] = "notebook.css"

        # Write notebook to file
        nb_path = os.path.join(tmpdir, "test.ipynb")
        with open(nb_path, "w") as f:
            nbf.write(nb, f)

        # Export notebook
        exporter = StyledHTMLExporter()
        output, resources = exporter.from_filename(nb_path)

        # Verify CSS is embedded in the output
        assert ".notebook-custom { font-size: 16px; }" in output
        assert "Embedded stylesheet: notebook.css" in output
        # Verify it's not a link tag
        assert 'href="notebook.css"' not in output


def test_path_traversal_protection():
    """Test that path traversal attacks are prevented."""
    with tempfile.TemporaryDirectory() as tmpdir:
        # Create a CSS file outside the base directory
        parent_dir = os.path.dirname(tmpdir)
        malicious_css_path = os.path.join(parent_dir, "malicious.css")
        with open(malicious_css_path, "w", encoding="utf-8") as f:
            f.write("body { malicious: content; }")

        # Try to access the file using path traversal
        resources = {"metadata": {"path": tmpdir}}

        exporter = StyledHTMLExporter()
        notebook_styles = {"stylesheet": "../malicious.css"}
        style_block = exporter._generate_notebook_style_block(notebook_styles, resources)

        # Should fallback to link tag (not embed the file)
        soup = _parse_html(style_block)
        link_tags = soup.find_all("link", rel="stylesheet")
        assert len(link_tags) == 1, "Should have link tag as fallback"
        assert link_tags[0].get("href") == "../malicious.css"

        # Verify the malicious content is NOT embedded
        assert "malicious: content" not in style_block

        # Clean up
        os.remove(malicious_css_path)


def test_embed_images_enabled_by_default():
    """Test that embed_images is enabled by default."""
    exporter = StyledHTMLExporter()

    assert exporter.embed_images is True


def test_embed_images_can_be_disabled():
    """Test that embed_images can be explicitly disabled."""
    exporter = StyledHTMLExporter(embed_images=False)

    assert exporter.embed_images is False


def test_embed_images_with_markdown_image():
    """Test that markdown images are embedded when embed_images is True."""
    with tempfile.TemporaryDirectory() as tmpdir:
        # Create test notebook with image
        nb_path = _create_notebook_with_image(tmpdir)

        # Export with default settings (embed_images=True)
        exporter = StyledHTMLExporter()
        output, resources = exporter.from_filename(nb_path)

        # Parse HTML and find img tags
        soup = _parse_html(output)
        img_tags = soup.find_all("img")

        # Verify image is embedded as data URI
        assert "data:image/png;base64," in output
        # Verify that at least one img tag has a data URI
        has_data_uri = False
        for img in img_tags:
            src = img.get("src", "")
            if src.startswith("data:image/png;base64,"):
                has_data_uri = True
                break

        assert has_data_uri, "No img tag with data URI found"

        # Verify file reference is NOT present (replaced with data URI)
        assert 'src="test.png"' not in output


def test_embed_images_disabled_keeps_file_reference():
    """Test that markdown images remain as file references when embed_images is False."""
    with tempfile.TemporaryDirectory() as tmpdir:
        # Create test notebook with image
        nb_path = _create_notebook_with_image(tmpdir)

        # Export with embed_images=False
        exporter = StyledHTMLExporter(embed_images=False)
        output, resources = exporter.from_filename(nb_path)

        # Verify file reference is present
        assert 'src="test.png"' in output, "File reference to test.png not found in output"


def test_embed_attachment_in_html_img_tag():
    """Test that attachments in HTML img tags are embedded when embed_images is True.

    This test verifies the fix for the issue where <img src="attachment:...">
    tags in markdown cells were not being embedded even when embed_images=True.
    """
    # Create a notebook with markdown cell containing HTML img tag with attachment
    nb = new_notebook()
    md_cell = new_markdown_cell('Test image: <img src="attachment:test.png" />')

    # Add the image as an attachment to the cell
    md_cell["attachments"] = {
        "test.png": {"image/png": base64.b64encode(TEST_IMAGE_PNG).decode("ascii")}
    }

    nb.cells.append(md_cell)

    # Export with default settings (embed_images=True)
    exporter = StyledHTMLExporter()
    output, resources = exporter.from_notebook_node(nb)

    # Parse HTML and find img tags
    soup = _parse_html(output)
    img_tags = soup.find_all("img")

    # Verify image is embedded as data URI
    assert len(img_tags) > 0, "No img tags found in output"

    # Verify that at least one img tag has a data URI
    has_data_uri = False
    for img in img_tags:
        src = img.get("src", "")
        if src.startswith("data:image/png;base64,"):
            has_data_uri = True
            break

    assert has_data_uri, "No img tag with data URI found"

    # Verify attachment URL is NOT present (replaced with data URI)
    assert 'src="attachment:test.png"' not in output, "Attachment URL still present in output"
    assert "attachment:test.png" not in output, "Attachment reference still present in output"


def test_attachment_in_html_img_tag_disabled():
    """Test that attachments in HTML img tags remain when embed_images is False.

    This test verifies that when embed_images=False and attachment URLs are
    in HTML img tags (not markdown syntax), they remain as attachment URLs
    because the HTML embedding patch only runs when embed_images=True.
    """
    # Create a notebook with markdown cell containing HTML img tag with attachment
    nb = new_notebook()
    md_cell = new_markdown_cell('Test image: <img src="attachment:test.png" />')

    # Add the image as an attachment to the cell
    md_cell["attachments"] = {
        "test.png": {"image/png": base64.b64encode(TEST_IMAGE_PNG).decode("ascii")}
    }

    nb.cells.append(md_cell)

    # Export with embed_images=False
    exporter = StyledHTMLExporter(embed_images=False)
    output, resources = exporter.from_notebook_node(nb)

    # When embed_images=False and using HTML img tags, the attachment URL remains
    # because the HTML embedding only runs when embed_images=True
    # Note: If using markdown syntax ![](attachment:...), the ExtractAttachmentsPreprocessor
    # would convert it to a file path
    assert (
        'src="attachment:test.png"' in output
    ), "Attachment URL should remain when embed_images=False"


def test_css_selectors_match_html_elements():
    """Test that CSS selectors match actual HTML element IDs.

    This test verifies the fix for the issue where CSS rules were generated
    but didn't match any elements in the HTML output.
    """
    exporter = StyledHTMLExporter()

    # Create notebook with various style types
    cell1 = new_code_cell("x = 1")
    cell1.metadata["style"] = {"background-color": "#f0f0f0"}

    cell2 = new_code_cell("y = 2")
    cell2.metadata["input-style"] = {"color": "red"}

    cell3 = new_code_cell("z = 3")
    cell3.metadata["output-style"] = {"border": "1px solid blue"}

    nb = new_notebook(cells=[cell1, cell2, cell3])

    output, resources = exporter.from_notebook_node(nb)

    # Parse HTML
    soup = _parse_html(output)
    css_rules = _extract_css_rules(output)

    # Verify CSS rules exist
    assert "#cell-0" in css_rules, "CSS rule for #cell-0 not found"
    assert "#cell-1-input" in css_rules, "CSS rule for #cell-1-input not found"
    assert "#cell-2-output" in css_rules, "CSS rule for #cell-2-output not found"

    # Verify matching HTML elements exist
    assert soup.find(id="cell-0") is not None, "HTML element with id='cell-0' not found"
    assert soup.find(id="cell-1") is not None, "HTML element with id='cell-1' not found"
    assert soup.find(id="cell-1-input") is not None, "HTML element with id='cell-1-input' not found"
    assert soup.find(id="cell-2") is not None, "HTML element with id='cell-2' not found"
    # Note: cell-2-output element won't exist without actual output

    # Verify the styles are applied with correct values
    assert css_rules["#cell-0"]["background-color"] == "#f0f0f0"
    assert css_rules["#cell-1-input"]["color"] == "red"
    assert css_rules["#cell-2-output"]["border"] == "1px solid blue"


def test_explicit_div_with_class_preserves_content():
    """Test that explicit div tags with class attributes preserve their content.

    This is a regression test for the issue where explicit HTML div tags
    in markdown cells had their content stripped when embed_images=True.
    The issue was caused by BeautifulSoup parsing incomplete HTML fragments
    (opening tags only) and auto-closing them, which moved content outside.
    """
    exporter = StyledHTMLExporter()

    # Create notebook with markdown cell containing explicit div with class
    md_content = """Some text in *normal* markdown

<div class="alert">

An explicit div contains text. The div has a class attribute.

</div>

More text outside the div"""

    nb = new_notebook(cells=[new_markdown_cell(md_content)])

    output, resources = exporter.from_notebook_node(nb)

    # Parse HTML
    soup = _parse_html(output)

    # Find the div with class="alert"
    alert_div = soup.find("div", class_="alert")

    # Verify the div exists
    assert alert_div is not None, "div with class='alert' not found in output"

    # Verify the div contains the expected content
    div_text = alert_div.get_text(strip=True)
    assert (
        "An explicit div contains text" in div_text
    ), f"Expected content not found in div. Found: {div_text}"
    assert "class attribute" in div_text, f"Expected content not found in div. Found: {div_text}"

    # Verify the content is INSIDE the div, not outside it
    # Get the parent of the paragraph
    alert_paragraphs = alert_div.find_all("p")
    assert len(alert_paragraphs) > 0, "No paragraphs found inside the alert div"

    # Verify the specific paragraph exists inside the div
    found_content = False
    for p in alert_paragraphs:
        if "explicit div contains text" in p.get_text():
            found_content = True
            break

    assert found_content, "Content paragraph not found inside the alert div"
