"""Tests for HTTP Request node."""

import socket
from unittest.mock import patch

import pytest

from flowire_sdk import validate_url

from fw_nodes_core.nodes.http_request import HTTPRequestNode


def test_http_method_field_has_default_value():
    """Test that the HTTP method field has a default value of GET in the schema.

    This ensures that:
    1. The schema exposes a default value for the method field
    2. The default value is GET
    3. The frontend will pre-select GET in the dropdown (no "Select..." placeholder)
    """
    # Get the input schema
    input_schema = HTTPRequestNode.get_input_schema()
    schema_dict = input_schema.model_json_schema()

    # Check that 'method' field exists in properties
    assert 'method' in schema_dict['properties'], "method field should be in schema properties"

    # Get the method field schema
    method_field = schema_dict['properties']['method']

    # Check that default value exists and is GET
    assert 'default' in method_field, "method field should have a default value in schema"
    assert method_field['default'] == 'GET', \
        f"method field default should be GET, got {method_field.get('default')}"

    # Resolve the enum values (may be in $ref or allOf)
    enum_values = []
    ref_path = None

    if '$ref' in method_field:
        # Direct $ref at field level
        ref_path = method_field['$ref']
    elif 'allOf' in method_field and '$ref' in method_field['allOf'][0]:
        # $ref wrapped in allOf
        ref_path = method_field['allOf'][0]['$ref']
    elif 'enum' in method_field:
        enum_values = method_field['enum']

    if ref_path:
        ref_name = ref_path.split('/')[-1]  # e.g., 'HTTPMethod' from '#/$defs/HTTPMethod'
        if '$defs' in schema_dict and ref_name in schema_dict['$defs']:
            enum_values = schema_dict['$defs'][ref_name].get('enum', [])

    # Verify that GET is a valid enum value
    assert 'GET' in enum_values, f"GET should be in enum values, got {enum_values}"


def test_http_method_field_not_in_required():
    """Test that method field is not in required fields (since it has a default)."""
    input_schema = HTTPRequestNode.get_input_schema()
    schema_dict = input_schema.model_json_schema()

    # A field with a default value should not be in the required list
    required_fields = schema_dict.get('required', [])
    assert 'method' not in required_fields, \
        "method field should not be required since it has a default value"


# --- validate_url tests ---


class TestValidateUrlAllowed:
    """URLs that should be allowed (Flowire is self-hosted, internal is fine)."""

    def test_regular_https_url(self):
        validate_url("https://api.example.com/v1/data")

    def test_regular_http_url(self):
        validate_url("http://example.com")

    def test_internal_ip(self):
        validate_url("http://192.168.1.100:8080/api")

    def test_localhost(self):
        validate_url("http://localhost:3000")

    def test_loopback(self):
        validate_url("http://127.0.0.1:8000/health")

    def test_private_10_range(self):
        validate_url("http://10.0.0.1/internal")

    def test_url_with_path_and_query(self):
        validate_url("https://api.example.com/v1?key=value&other=123")


class TestValidateUrlBlockedMetadata:
    """Cloud metadata endpoints that must be blocked."""

    def test_aws_metadata_ip(self):
        with pytest.raises(ValueError, match="blocked"):
            validate_url("http://169.254.169.254/latest/meta-data/")

    def test_aws_metadata_no_path(self):
        with pytest.raises(ValueError, match="blocked"):
            validate_url("http://169.254.169.254")

    def test_aws_ecs_metadata(self):
        with pytest.raises(ValueError, match="blocked"):
            validate_url("http://169.254.170.2/v2/metadata")

    def test_gcp_metadata_hostname(self):
        with pytest.raises(ValueError, match="blocked"):
            validate_url("http://metadata.google.internal/computeMetadata/v1/")

    def test_aws_ipv6_metadata(self):
        with pytest.raises(ValueError, match="blocked"):
            validate_url("http://[fd00:ec2::254]/latest/meta-data/")


class TestValidateUrlBypassPrevention:
    """Bypass techniques that must be caught."""

    def test_trailing_dot_bypass(self):
        """FQDN trailing dot: 169.254.169.254. should still be blocked."""
        with pytest.raises(ValueError, match="blocked"):
            validate_url("http://169.254.169.254./latest/meta-data/")

    def test_ipv6_mapped_ipv4(self):
        """IPv6-mapped IPv4: ::ffff:169.254.169.254 should be blocked."""
        with pytest.raises(ValueError, match="blocked"):
            validate_url("http://[::ffff:169.254.169.254]/latest/meta-data/")

    def test_decimal_ip_representation(self):
        """Decimal IP: 2852039166 = 169.254.169.254 should be blocked."""
        with pytest.raises(ValueError, match="blocked"):
            validate_url("http://2852039166/latest/meta-data/")

    def test_hex_ip_representation(self):
        """Hex IP: 0xa9fea9fe = 169.254.169.254 should be blocked."""
        with pytest.raises(ValueError, match="blocked"):
            validate_url("http://0xa9fea9fe/latest/meta-data/")

    def test_octal_ip_representation(self):
        """Octal IP: 0251.0376.0251.0376 = 169.254.169.254 should be blocked."""
        with pytest.raises(ValueError, match="blocked"):
            validate_url("http://0251.0376.0251.0376/latest/meta-data/")

    def test_dns_cname_to_metadata(self):
        """DNS name that resolves to metadata IP should be blocked."""
        # Mock DNS resolution to return the metadata IP
        fake_result = [(socket.AF_INET, socket.SOCK_STREAM, 6, "", ("169.254.169.254", 0))]
        with patch("flowire_sdk.security.socket.getaddrinfo", return_value=fake_result):
            with pytest.raises(ValueError, match="blocked.*resolves"):
                validate_url("http://evil.attacker.com/steal-creds")

    def test_dns_cname_to_ecs_metadata(self):
        """DNS name that resolves to ECS metadata IP should be blocked."""
        fake_result = [(socket.AF_INET, socket.SOCK_STREAM, 6, "", ("169.254.170.2", 0))]
        with patch("flowire_sdk.security.socket.getaddrinfo", return_value=fake_result):
            with pytest.raises(ValueError, match="blocked.*resolves"):
                validate_url("http://attacker.com/ecs")


class TestValidateUrlScheme:
    """Scheme validation."""

    def test_ftp_blocked(self):
        with pytest.raises(ValueError, match="Unsupported URL scheme"):
            validate_url("ftp://example.com/file")

    def test_javascript_blocked(self):
        with pytest.raises(ValueError, match="Unsupported URL scheme"):
            validate_url("javascript:alert(1)")

    def test_file_blocked(self):
        with pytest.raises(ValueError, match="Unsupported URL scheme"):
            validate_url("file:///etc/passwd")

    def test_empty_scheme_blocked(self):
        """URLs without a scheme should be rejected."""
        with pytest.raises(ValueError, match="Unsupported URL scheme"):
            validate_url("//example.com/path")

    def test_data_blocked(self):
        with pytest.raises(ValueError, match="Unsupported URL scheme"):
            validate_url("data:text/html,<h1>hi</h1>")


class TestValidateUrlMissingHostname:
    """URLs with no hostname."""

    def test_no_hostname(self):
        with pytest.raises(ValueError, match="missing a hostname"):
            validate_url("http://")

    def test_only_scheme(self):
        with pytest.raises(ValueError, match="missing a hostname"):
            validate_url("http:///path")
