# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

from dataclasses import dataclass, field

from pyrit.registry.registry import _matches_filters
from pyrit.registry.registry_metadata import RegistryMetadata


@dataclass(frozen=True)
class MetadataWithTags(RegistryMetadata):
    """Test metadata with a tags field for list filtering tests."""

    tags: tuple[str, ...] = field(kw_only=True)


class TestDescriptionFromDocstring:
    """Tests for RegistryMetadata.description_from_docstring."""

    def test_extracts_docstring_and_normalizes_whitespace(self):
        class MyClass:
            """This  is\n  a   docstring."""

        result = RegistryMetadata.description_from_docstring(MyClass)
        assert result == "This is a docstring."

    def test_returns_fallback_when_no_docstring(self):
        class NoDoc:
            pass

        result = RegistryMetadata.description_from_docstring(NoDoc, fallback="default")
        assert result == "default"

    def test_returns_fallback_when_empty_docstring(self):
        class EmptyDoc:
            """ """

        result = RegistryMetadata.description_from_docstring(EmptyDoc, fallback="fallback")
        assert result == "fallback"

    def test_returns_empty_string_when_no_docstring_and_no_fallback(self):
        class NoDoc:
            pass

        result = RegistryMetadata.description_from_docstring(NoDoc)
        assert result == ""


class TestMarkdownFromDocstring:
    """Tests for structurally preserved catalog descriptions."""

    def test_preserves_markdown_and_untrusted_html_as_source_text(self) -> None:
        class MarkdownDoc:
            """
            First paragraph with ``literal`` text.

            - First item
            - [Split link](
              https://example.com)

            <script>alert("untrusted")</script>
            """

        result = RegistryMetadata.markdown_from_docstring(MarkdownDoc)

        assert result == (
            "First paragraph with ``literal`` text.\n\n"
            "- First item\n"
            "- [Split link](\n"
            "  https://example.com)\n\n"
            '<script>alert("untrusted")</script>'
        )

    def test_returns_fallback_for_missing_docstring(self) -> None:
        class NoDoc:
            pass

        assert RegistryMetadata.markdown_from_docstring(NoDoc, fallback="fallback") == "fallback"


class TestMatchesFilters:
    """Tests for the _matches_filters function."""

    def test_matches_filters_exact_match_string(self):
        """Test that exact string matches work."""
        metadata = RegistryMetadata(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
        )
        assert _matches_filters(metadata, include_filters={"class_name": "TestClass"}) is True
        assert _matches_filters(metadata, include_filters={"class_module": "test.module"}) is True

    def test_matches_filters_no_match_string(self):
        """Test that non-matching strings return False."""
        metadata = RegistryMetadata(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
        )
        assert _matches_filters(metadata, include_filters={"class_name": "OtherClass"}) is False
        assert _matches_filters(metadata, include_filters={"class_module": "other.module"}) is False

    def test_matches_filters_multiple_filters_all_match(self):
        """Test that all filters must match."""
        metadata = RegistryMetadata(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
        )
        assert (
            _matches_filters(metadata, include_filters={"class_name": "TestClass", "class_module": "test.module"})
            is True
        )

    def test_matches_filters_multiple_filters_partial_match(self):
        """Test that partial matches return False when not all filters match."""
        metadata = RegistryMetadata(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
        )
        assert (
            _matches_filters(metadata, include_filters={"class_name": "TestClass", "class_module": "other.module"})
            is False
        )

    def test_matches_filters_key_not_in_metadata(self):
        """Test that filtering on a non-existent key returns False."""
        metadata = RegistryMetadata(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
        )
        assert _matches_filters(metadata, include_filters={"nonexistent_key": "value"}) is False

    def test_matches_filters_empty_filters(self):
        """Test that empty filters return True."""
        metadata = RegistryMetadata(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
        )
        assert _matches_filters(metadata) is True

    def test_matches_filters_list_value_contains_filter(self):
        """Test filtering when metadata value is a list and filter value is in the list."""
        metadata = MetadataWithTags(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
            tags=("tag1", "tag2", "tag3"),
        )
        assert _matches_filters(metadata, include_filters={"tags": "tag1"}) is True
        assert _matches_filters(metadata, include_filters={"tags": "tag2"}) is True

    def test_matches_filters_list_value_not_contains_filter(self):
        """Test filtering when metadata value is a list and filter value is not in the list."""
        metadata = MetadataWithTags(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
            tags=("tag1", "tag2", "tag3"),
        )
        assert _matches_filters(metadata, include_filters={"tags": "missing_tag"}) is False

    def test_matches_filters_exclude_exact_match(self):
        """Test that exclude filters work for exact matches."""
        metadata = RegistryMetadata(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
        )
        assert _matches_filters(metadata, exclude_filters={"class_name": "TestClass"}) is False
        assert _matches_filters(metadata, exclude_filters={"class_name": "OtherClass"}) is True

    def test_matches_filters_exclude_list_value(self):
        """Test exclude filters work for list values."""
        metadata = MetadataWithTags(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
            tags=("tag1", "tag2", "tag3"),
        )
        assert _matches_filters(metadata, exclude_filters={"tags": "tag1"}) is False
        assert _matches_filters(metadata, exclude_filters={"tags": "missing_tag"}) is True

    def test_matches_filters_exclude_nonexistent_key(self):
        """Test that exclude filters for non-existent keys don't exclude the item."""
        metadata = RegistryMetadata(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
        )
        # Non-existent key in exclude filter should not exclude the item
        assert _matches_filters(metadata, exclude_filters={"nonexistent_key": "value"}) is True

    def test_matches_filters_combined_include_and_exclude(self):
        """Test combined include and exclude filters."""
        metadata = RegistryMetadata(
            class_name="TestClass",
            class_module="test.module",
            class_description="A test item",
        )
        # Include matches, exclude doesn't -> should pass
        assert (
            _matches_filters(
                metadata, include_filters={"class_name": "TestClass"}, exclude_filters={"class_module": "other.module"}
            )
            is True
        )
        # Include matches, exclude also matches -> should fail
        assert (
            _matches_filters(
                metadata, include_filters={"class_name": "TestClass"}, exclude_filters={"class_module": "test.module"}
            )
            is False
        )
        # Include doesn't match, exclude doesn't match -> should fail (include takes precedence)
        assert (
            _matches_filters(
                metadata, include_filters={"class_name": "OtherClass"}, exclude_filters={"class_module": "other.module"}
            )
            is False
        )
