"""Tests for HTML extraction: links, metadata, hidden content, JSON-LD."""
import pytest
from scanner.extractor import extract_page

BASE_URL = "https://bredelar.info/"


def _extract(html: str, url: str = BASE_URL) -> dict:
    return extract_page(html, url)


class TestVisibleText:
    def test_strips_scripts(self):
        result = _extract("<html><script>var x=1;</script><body>Hallo Welt</body></html>")
        assert "var x" not in result["text"]
        assert "Hallo Welt" in result["text"]

    def test_strips_style(self):
        result = _extract("<html><style>.cls{color:red}</style><body>Text</body></html>")
        assert ".cls" not in result["text"]
        assert "Text" in result["text"]

    def test_collapses_whitespace(self):
        result = _extract("<body><p>Hello   World</p></body>")
        assert "Hello World" in result["text"]


class TestLinkExtraction:
    def test_extracts_anchor_links(self):
        result = _extract('<body><a href="/page">Link</a></body>')
        urls = [l["url"] for l in result["links"]["a"]]
        assert any("/page" in u for u in urls)

    def test_classifies_internal_vs_external(self):
        html = (
            '<body>'
            '<a href="/internal">intern</a>'
            '<a href="https://spam.example.com/x">extern</a>'
            '</body>'
        )
        result = _extract(html)
        classes = {l["url"]: l["class"] for l in result["links"]["a"]}
        assert any(v == "internal" for v in classes.values())
        assert any(v == "external" for v in classes.values())

    def test_extracts_canonical(self):
        html = '<head><link rel="canonical" href="https://bredelar.info/page"/></head>'
        result = _extract(html)
        assert result["links"]["canonical"] == "https://bredelar.info/page"

    def test_extracts_meta_refresh(self):
        html = '<head><meta http-equiv="refresh" content="0; url=https://malware.example.com"/></head>'
        result = _extract(html)
        assert any("malware.example.com" in u for u in result["links"]["meta_refresh"])

    def test_ignores_javascript_hrefs(self):
        result = _extract('<body><a href="javascript:void(0)">JS</a></body>')
        assert result["links"]["a"] == []

    def test_extracts_iframe_src(self):
        result = _extract('<body><iframe src="https://evil.com/x"></iframe></body>')
        iframe_urls = [l["url"] for l in result["links"]["iframe"]]
        assert any("evil.com" in u for u in iframe_urls)


class TestMetadata:
    def test_extracts_title(self):
        result = _extract("<html><head><title>Bredelar</title></head></html>")
        assert result["metadata"]["title"] == "Bredelar"

    def test_extracts_description(self):
        html = '<head><meta name="description" content="Ein Dorf in NRW"/></head>'
        result = _extract(html)
        assert result["metadata"]["description"] == "Ein Dorf in NRW"

    def test_extracts_og_tags(self):
        html = '<head><meta property="og:title" content="OG-Titel"/></head>'
        result = _extract(html)
        assert result["metadata"].get("og:title") == "OG-Titel"

    def test_extracts_h1(self):
        result = _extract("<body><h1>Hauptüberschrift</h1></body>")
        assert "Hauptüberschrift" in result["metadata"]["h1"]


class TestHiddenContent:
    def test_detects_display_none(self):
        html = '<body><div style="display:none"><a href="https://spam.com">Spam</a></div></body>'
        result = _extract(html)
        assert len(result["hidden_content"]) > 0
        assert any("display:none" in str(h.get("indicators", [])) for h in result["hidden_content"])

    def test_detects_visibility_hidden(self):
        html = '<body><span style="visibility:hidden">Spam Text</span></body>'
        result = _extract(html)
        assert len(result["hidden_content"]) > 0

    def test_detects_offscreen_position(self):
        html = '<body><div style="position:absolute; left:-9999px">Hidden</div></body>'
        result = _extract(html)
        assert len(result["hidden_content"]) > 0

    def test_detects_noscript_links(self):
        html = '<body><noscript><a href="https://spam.com/x">Spam</a></noscript></body>'
        result = _extract(html)
        noscript = [h for h in result["hidden_content"] if h.get("type") == "noscript_links"]
        assert len(noscript) > 0

    def test_visible_element_not_flagged(self):
        html = '<body><div style="color:red">Sichtbarer Text</div></body>'
        result = _extract(html)
        # color:red should NOT be flagged (not a hiding technique)
        assert not any(
            "red" in str(h) for h in result["hidden_content"]
        )


class TestCommentLinks:
    def test_finds_link_in_comment(self):
        html = '<!-- <a href="https://hidden-spam.com/x">click</a> --><body></body>'
        result = _extract(html)
        assert any("hidden-spam.com" in u for u in result["comment_links"])

    def test_no_false_positives(self):
        html = '<!-- This is a normal comment --><body>Text</body>'
        result = _extract(html)
        assert result["comment_links"] == []


class TestJsonLd:
    def test_extracts_known_type(self):
        html = (
            '<script type="application/ld+json">'
            '{"@type": "Organization", "name": "Bredelar"}'
            '</script>'
        )
        result = _extract(html)
        assert any(j["type"] == "Organization" for j in result["jsonld"])

    def test_handles_parse_error(self):
        html = '<script type="application/ld+json">{broken json}</script>'
        result = _extract(html)
        assert any(j["type"] == "PARSE_ERROR" for j in result["jsonld"])

    def test_extracts_unexpected_type(self):
        html = (
            '<script type="application/ld+json">'
            '{"@type": "Pharmacy", "name": "SpamShop"}'
            '</script>'
        )
        result = _extract(html)
        assert any(j["type"] == "Pharmacy" for j in result["jsonld"])

    def test_non_dict_jsonld_does_not_crash(self):
        # Valid JSON that is not an object (scalar / array of scalars) must be
        # tolerated, not raise AttributeError.
        for body in ("[1, 2, 3]", '"just a string"', "42"):
            html = f'<script type="application/ld+json">{body}</script>'
            result = _extract(html)
            assert isinstance(result["jsonld"], list)


class TestCacheBusterNormalization:
    def test_link_rel_cache_param_stripped(self):
        html = '<head><link rel="stylesheet" href="/css/app.css?v=1781223996"/></head>'
        result = _extract(html)
        urls = [l["url"] for l in result["links"]["link_rel"]]
        assert any(u.endswith("/css/app.css") for u in urls)
        assert all("v=" not in u for u in urls)

    def test_script_cache_param_stripped(self):
        html = '<body><script src="/js/app.js?ver=42"></script></body>'
        result = _extract(html)
        urls = [l["url"] for l in result["links"]["script"]]
        assert any(u.endswith("/js/app.js") for u in urls)

    def test_non_cache_param_kept_on_asset(self):
        html = '<body><img src="/img/pic.png?size=large"/></body>'
        result = _extract(html)
        urls = [l["url"] for l in result["links"]["img"]]
        assert any("size=large" in u for u in urls)
