"""
Tests for the check-target-blank-rel pre-commit hook.

The hook flags any ``<a target="_blank">`` that is not provably same-origin
and is missing ``rel="noopener noreferrer"`` — without ``noopener`` the opened
page can reach back via ``window.opener`` (tabnabbing), and without
``noreferrer`` the Referer header leaks the LDR URL to the destination.

The hook uses a "flag unless proven internal" policy: a new-tab link must
carry the rel unless we can statically prove it stays same-origin. That means
dynamic hrefs (JS ``${...}`` and Jinja ``{{ ... }}`` that are not
``url_for()``) and bare scheme-less hosts ARE flagged — those are exactly the
shapes where past regressions lived. Provably same-origin hrefs (leading
``/``, ``#``, ``?``, a Jinja ``url_for``, or a ``mailto:``/``tel:`` scheme)
are skipped.
"""

import subprocess
import sys
import tempfile
from pathlib import Path

import pytest


HOOK_SCRIPT = (
    Path(__file__).parent.parent.parent
    / ".pre-commit-hooks"
    / "check-target-blank-rel.py"
)


def _run_hook(
    content: str, filename: str = "x.html"
) -> subprocess.CompletedProcess:
    """Write content to a temp file and run the hook against it."""
    with tempfile.TemporaryDirectory() as d:
        path = Path(d) / filename
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(content)
        return subprocess.run(
            [sys.executable, str(HOOK_SCRIPT), str(path)],
            capture_output=True,
            text=True,
        )


class TestFlagsStaticExternalMissingRel:
    """Static external new-tab anchors missing either rel token are flagged."""

    def test_external_no_rel_is_flagged(self):
        result = _run_hook(
            '<a href="https://example.com" target="_blank">x</a>\n'
        )
        assert result.returncode == 1
        assert "noopener noreferrer" in result.stdout

    def test_external_noopener_only_is_flagged(self):
        """`rel="noopener"` alone still leaks the Referer — must fail."""
        result = _run_hook(
            '<a href="https://example.com" target="_blank" '
            'rel="noopener">x</a>\n'
        )
        assert result.returncode == 1

    def test_external_noreferrer_only_is_flagged(self):
        result = _run_hook(
            '<a href="https://example.com" target="_blank" '
            'rel="noreferrer">x</a>\n'
        )
        assert result.returncode == 1

    def test_http_external_is_flagged(self):
        result = _run_hook(
            '<a href="http://example.com" target="_blank">x</a>\n'
        )
        assert result.returncode == 1

    def test_protocol_relative_external_is_flagged(self):
        result = _run_hook('<a href="//example.com" target="_blank">x</a>\n')
        assert result.returncode == 1


class TestFlagsDynamicMissingRel:
    """The point of the flip: hrefs not provably internal are flagged too."""

    def test_js_template_literal_href_is_flagged(self):
        """`${...}` is not provably same-origin — it must carry rel."""
        result = _run_hook(
            '<a href="${escapeAttr(resource.url)}" target="_blank">x</a>\n',
            "x.js",
        )
        assert result.returncode == 1

    def test_jinja_expression_href_is_flagged(self):
        """A non-url_for `{{ ... }}` (e.g. an external source URL) needs rel."""
        result = _run_hook(
            '<a href="{{ document.original_url }}" target="_blank">x</a>\n'
        )
        assert result.returncode == 1

    def test_jinja_statement_wrapped_href_is_flagged(self):
        """`{% if %}...{% endif %}` could resolve external — not provable."""
        result = _run_hook(
            '<a href="{% if x %}https://a{% else %}/b{% endif %}" '
            'target="_blank">x</a>\n'
        )
        assert result.returncode == 1

    def test_bare_scheme_less_host_is_flagged(self):
        result = _run_hook('<a href="example.com" target="_blank">x</a>\n')
        assert result.returncode == 1

    def test_literal_scheme_prefix_before_template_expr_is_flagged(self):
        result = _run_hook(
            '<a href="https://${host}/p" target="_blank">x</a>\n', "x.js"
        )
        assert result.returncode == 1


class TestCompliantAnchorsPass:
    """Correctly-protected anchors pass regardless of static/dynamic href."""

    def test_both_tokens_present_passes(self):
        result = _run_hook(
            '<a href="https://example.com" target="_blank" '
            'rel="noopener noreferrer">x</a>\n'
        )
        assert result.returncode == 0

    def test_reversed_token_order_passes(self):
        result = _run_hook(
            '<a href="https://example.com" target="_blank" '
            'rel="noreferrer noopener">x</a>\n'
        )
        assert result.returncode == 0

    def test_uppercase_rel_tokens_pass(self):
        result = _run_hook(
            '<a href="https://example.com" target="_blank" '
            'rel="NOOPENER NOREFERRER">x</a>\n'
        )
        assert result.returncode == 0

    def test_extra_rel_tokens_pass(self):
        result = _run_hook(
            '<a href="https://example.com" target="_blank" '
            'rel="nofollow noopener noreferrer">x</a>\n'
        )
        assert result.returncode == 0

    def test_dynamic_href_with_rel_passes(self):
        """A `${...}` href is fine once it carries the rel."""
        result = _run_hook(
            '<a href="${u}" target="_blank" rel="noopener noreferrer">x</a>\n',
            "x.js",
        )
        assert result.returncode == 0


class TestProvablyInternalSkipped:
    """Provably same-origin hrefs are skipped even without rel."""

    @pytest.mark.parametrize(
        "href",
        [
            "/library/document/1/pdf",
            "/progress/${encodeURIComponent(id)}",  # dynamic but same-origin
            "#section",
            "?tab=settings",
            "{{ url_for('main.index') }}",
            "{{- url_for('main.index') }}",
            "mailto:hi@example.com",
            "tel:+123",
            "javascript:void(0)",
        ],
    )
    def test_internal_href_with_blank_target_passes(self, href: str):
        result = _run_hook(f'<a href="{href}" target="_blank">x</a>\n')
        assert result.returncode == 0, result.stdout

    def test_anchor_without_blank_target_is_ignored(self):
        result = _run_hook('<a href="https://example.com">x</a>\n')
        assert result.returncode == 0


class TestSchemeCaseInsensitive:
    """Uppercase URL schemes must not evade the check."""

    @pytest.mark.parametrize(
        "href",
        ["HTTPS://example.com", "HTTP://example.com", "HtTpS://example.com"],
    )
    def test_uppercase_scheme_external_is_flagged(self, href: str):
        result = _run_hook(f'<a href="{href}" target="_blank">x</a>\n')
        assert result.returncode == 1, result.stdout


class TestParsingRobustness:
    """Attribute order, quoting, casing, and multi-line tags all parse."""

    def test_target_before_href_is_flagged(self):
        result = _run_hook(
            '<a target="_blank" href="https://example.com">x</a>\n'
        )
        assert result.returncode == 1

    def test_single_quoted_attrs_are_flagged(self):
        result = _run_hook(
            "<a href='https://example.com' target='_blank'>x</a>\n"
        )
        assert result.returncode == 1

    def test_mixed_case_blank_target_is_flagged(self):
        result = _run_hook(
            '<a href="https://example.com" TARGET="_BLANK">x</a>\n'
        )
        assert result.returncode == 1

    def test_multiline_tag_missing_rel_is_flagged(self):
        """Multi-line attribute lists must still be parsed (no DOTALL needed)."""
        result = _run_hook(
            '<a href="https://example.com"\n'
            '   target="_blank"\n'
            '   class="x">link</a>\n'
        )
        assert result.returncode == 1

    def test_multiline_tag_with_rel_passes(self):
        result = _run_hook(
            '<a href="https://example.com"\n'
            '   target="_blank"\n'
            '   rel="noopener noreferrer">link</a>\n'
        )
        assert result.returncode == 0


class TestExitCodes:
    """No anchors / no violations → clean exit."""

    def test_no_anchors_passes(self):
        result = _run_hook("<div>no anchors here</div>\n")
        assert result.returncode == 0

    def test_multiple_violations_reported(self):
        content = (
            '<a href="https://a.com" target="_blank">a</a>\n'
            '<a href="https://b.com" target="_blank" rel="noopener">b</a>\n'
        )
        result = _run_hook(content)
        assert result.returncode == 1
        assert "line 1" in result.stdout
        assert "line 2" in result.stdout
