"""Tests for HarvesterScopeFilter in tool_chaining module.

Tests scope filtering for theHarvester auto-chains to reduce noise from:
- Out-of-scope URLs (third-party services)
- CDN IPs (shared infrastructure)
- Marketing subdomains (third-party hosted)
- Duplicate URL patterns
"""

import pytest


class TestHarvesterScopeFilterBasic:
    """Basic tests for HarvesterScopeFilter initialization and domain matching."""

    def test_initialization(self):
        """Filter should initialize with target domain."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        assert f.target_domain == "example.com"
        assert f.base_domain == "example.com"

    def test_initialization_with_subdomain(self):
        """Filter should extract base domain from subdomain target."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("www.example.com")
        assert f.target_domain == "www.example.com"
        assert f.base_domain == "example.com"

    def test_initialization_with_multi_part_tld(self):
        """Filter should handle multi-part TLDs like .co.uk."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.co.uk")
        assert f.base_domain == "example.co.uk"

    def test_cdn_networks_loaded(self):
        """Filter should load CDN networks on init."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        assert len(f._cdn_networks) > 0


class TestUrlScopeMatching:
    """Test URL scope matching functionality."""

    def test_url_in_scope_exact_match(self):
        """URL with exact target domain is in scope."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("freshpaint.io")
        is_in, reason = f.is_url_in_scope("https://freshpaint.io/about")
        assert is_in is True

    def test_url_in_scope_subdomain(self):
        """URL with subdomain of target is in scope."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("freshpaint.io")
        is_in, reason = f.is_url_in_scope("https://app.freshpaint.io/login")
        assert is_in is True

    def test_url_in_scope_deep_subdomain(self):
        """URL with deep subdomain is in scope."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("freshpaint.io")
        is_in, reason = f.is_url_in_scope("https://api.v2.freshpaint.io/endpoint")
        assert is_in is True

    def test_url_out_of_scope_different_domain(self):
        """URL from different domain is out of scope."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("freshpaint.io")
        is_in, reason = f.is_url_in_scope("https://google-analytics.com/collect")
        assert is_in is False
        assert "not in scope" in reason

    def test_url_out_of_scope_similar_domain(self):
        """URL from similar but different domain is out of scope."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("freshpaint.io")
        # freshpaint.com is NOT the same as freshpaint.io
        is_in, reason = f.is_url_in_scope("https://freshpaint.com/page")
        assert is_in is False

    def test_url_with_port(self):
        """URLs with port numbers are handled correctly."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("https://example.com:8443/admin")
        assert is_in is True

    def test_url_with_trailing_dot(self):
        """URLs with trailing dots in hostname work."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("https://example.com./page")
        assert is_in is True


class TestMarketingSubdomainFiltering:
    """Test marketing subdomain filtering."""

    def test_info_subdomain_filtered(self):
        """info.* subdomain is filtered as marketing."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("freshpaint.io")
        is_in, reason = f.is_url_in_scope("https://info.freshpaint.io/landing-page")
        assert is_in is False
        assert "marketing" in reason.lower()

    def test_go_subdomain_filtered(self):
        """go.* subdomain is filtered as marketing."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("https://go.example.com/offer")
        assert is_in is False
        assert "marketing" in reason.lower()

    def test_marketing_subdomain_filtered(self):
        """marketing.* subdomain is filtered."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("https://marketing.example.com/campaign")
        assert is_in is False

    def test_track_subdomain_filtered(self):
        """track.* subdomain is filtered."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("https://track.example.com/click")
        assert is_in is False

    def test_app_subdomain_allowed(self):
        """app.* subdomain (application) is allowed."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("https://app.example.com/dashboard")
        assert is_in is True

    def test_api_subdomain_allowed(self):
        """api.* subdomain is allowed (valuable target)."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("https://api.example.com/v1/users")
        assert is_in is True


class TestTrackingUrlFiltering:
    """Test tracking URL pattern filtering."""

    def test_hubspot_event_filtered(self):
        """HubSpot event URLs are filtered."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("https://example.com/event-shA123")
        assert is_in is False
        assert "tracking" in reason.lower()

    def test_hubspot_hsenc_filtered(self):
        """HubSpot _hsenc parameter URLs are filtered."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("https://example.com/page?_hsenc=p2ANqtz123")
        assert is_in is False

    def test_regular_query_params_allowed(self):
        """Regular query parameters are allowed."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("https://example.com/search?q=test&page=1")
        assert is_in is True


class TestCdnIpFiltering:
    """Test CDN IP filtering."""

    def test_cloudfront_ip_filtered(self):
        """AWS CloudFront IPs are filtered."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        # 108.138.x.x is AWS CloudFront
        is_in, reason = f.is_ip_in_scope("108.138.100.50")
        assert is_in is False
        assert "CDN" in reason

    def test_cloudflare_ip_filtered(self):
        """CloudFlare IPs are filtered."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        # 104.16.x.x is CloudFlare
        is_in, reason = f.is_ip_in_scope("104.16.100.50")
        assert is_in is False
        assert "CDN" in reason

    def test_regular_public_ip_passes_cdn_check(self):
        """Regular public IPs pass CDN check (without DNS verify)."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        # Without DNS verify, should pass CDN check
        is_in, reason = f.is_ip_in_scope("203.0.113.50", verify_dns=False)
        assert is_in is True

    def test_private_ip_passes_cdn_check(self):
        """Private IPs pass CDN check (without DNS verify)."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        # Without DNS verify, should pass CDN check
        is_in, reason = f.is_ip_in_scope("192.168.1.100", verify_dns=False)
        assert is_in is True

    def test_invalid_ip_allowed(self):
        """Invalid IPs are allowed (fail-open)."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_ip_in_scope("not-an-ip")
        assert is_in is True


class TestUrlDeduplication:
    """Test URL pattern-based deduplication."""

    def test_dedupe_event_patterns(self):
        """Similar event URLs are deduplicated."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        urls = [
            "https://example.com/event-shA",
            "https://example.com/event-shB",
            "https://example.com/event-hmX",
            "https://example.com/different-page",
        ]

        deduped = HarvesterScopeFilter.dedupe_urls_by_pattern(urls)

        # event-shA, event-shB, event-hmX should all be grouped to one pattern
        # different-page should remain separate
        assert len(deduped) == 2

    def test_dedupe_hash_patterns(self):
        """URLs with hash-like segments are deduplicated."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        urls = [
            "https://example.com/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/page",  # 32 char hex
            "https://example.com/f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3/page",  # different hash
            "https://example.com/normal/page",
        ]

        deduped = HarvesterScopeFilter.dedupe_urls_by_pattern(urls)
        assert len(deduped) == 2  # hash patterns merged, normal kept

    def test_dedupe_preserves_first_url(self):
        """Deduplication keeps the first URL in a pattern group."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        urls = [
            "https://example.com/event-shFirst",
            "https://example.com/event-shSecond",
        ]

        deduped = HarvesterScopeFilter.dedupe_urls_by_pattern(urls)
        assert len(deduped) == 1
        assert "First" in deduped[0]

    def test_dedupe_different_hosts(self):
        """URLs from different hosts are not merged."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        urls = [
            "https://app.example.com/event-shA",
            "https://api.example.com/event-shA",
        ]

        deduped = HarvesterScopeFilter.dedupe_urls_by_pattern(urls)
        assert len(deduped) == 2  # Different hosts, both kept


class TestFilterMethods:
    """Test batch filtering methods."""

    def test_filter_urls_returns_tuple(self):
        """filter_urls returns (filtered, skipped_count, reasons)."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("freshpaint.io")
        urls = [
            "https://freshpaint.io/app",
            "https://google.com/analytics",
            "https://api.freshpaint.io/v1",
        ]

        filtered, skipped, reasons = f.filter_urls(urls)

        assert len(filtered) == 2  # freshpaint.io and api.freshpaint.io
        assert skipped == 1  # google.com
        assert "google.com" in str(reasons)

    def test_filter_ips_returns_tuple(self):
        """filter_ips returns (filtered, skipped_count, reasons)."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        ips = [
            "192.168.1.100",  # Private - passes CDN check
            "108.138.50.50",  # CloudFront - filtered
            "203.0.113.50",  # Public - passes CDN check
        ]

        # Without DNS verify, only CDN check applies
        filtered, skipped, reasons = f.filter_ips(ips, verify_dns=False)

        assert len(filtered) == 2
        assert skipped == 1
        assert "108.138.50.50" in reasons


class TestIpDnsVerification:
    """Test IP-to-hostname DNS verification."""

    def test_reverse_dns_lookup_returns_string(self):
        """Reverse DNS lookup should return a string (empty if no PTR)."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        # Test with localhost which usually has a PTR record
        result = f._reverse_dns_lookup("127.0.0.1")
        assert isinstance(result, str)

    def test_is_ip_in_scope_with_dns_verify(self):
        """is_ip_in_scope with verify_dns=True should check PTR records."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        # A random IP without PTR matching example.com should fail
        is_in, reason = f.is_ip_in_scope("203.0.113.50", verify_dns=True)
        # Should either fail CDN check or DNS verify
        assert isinstance(is_in, bool)
        assert isinstance(reason, str)

    def test_is_ip_in_scope_without_dns_verify(self):
        """is_ip_in_scope with verify_dns=False should only check CDN ranges."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        # A random public IP not in CDN ranges should pass without DNS verify
        is_in, reason = f.is_ip_in_scope("203.0.113.50", verify_dns=False)
        assert is_in is True
        assert "not in CDN" in reason

    def test_cdn_ip_filtered_before_dns_check(self):
        """CDN IPs should be filtered before DNS verification (fast path)."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        # CloudFront IP should fail immediately without DNS lookup
        is_in, reason = f.is_ip_in_scope("108.138.100.50", verify_dns=True)
        assert is_in is False
        assert "CDN" in reason  # Should be CDN reason, not DNS reason


class TestEdgeCases:
    """Edge case tests for HarvesterScopeFilter."""

    def test_empty_url_list(self):
        """Empty URL list returns empty result."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        filtered, skipped, reasons = f.filter_urls([])
        assert filtered == []
        assert skipped == 0

    def test_empty_ip_list(self):
        """Empty IP list returns empty result."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        filtered, skipped, reasons = f.filter_ips([])
        assert filtered == []
        assert skipped == 0

    def test_malformed_url_allowed(self):
        """Malformed URLs are allowed (fail-open)."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("not-a-valid-url")
        # Should not crash, behavior may vary
        assert isinstance(is_in, bool)

    def test_target_with_uppercase(self):
        """Target domain is normalized to lowercase."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("EXAMPLE.COM")
        assert f.target_domain == "example.com"

    def test_url_with_uppercase_host(self):
        """URLs with uppercase hosts are matched case-insensitively."""
        from souleyez.core.tool_chaining import HarvesterScopeFilter

        f = HarvesterScopeFilter("example.com")
        is_in, reason = f.is_url_in_scope("https://EXAMPLE.COM/page")
        assert is_in is True
