from __future__ import annotations

import pytest

from scopewrit.errors import ResourceError
from scopewrit.models import ActionProposal, Effect, ScopeRule
from scopewrit.resources import normalize_resource, rule_matches


def _proposal(resource: str, *, resolved_ips=None) -> ActionProposal:
    return ActionProposal(
        request_id="resource-test",
        run_id="resource-run",
        action="network.http",
        resource=resource,
        method="GET",
        attributes={"resolved_ips": resolved_ips or []},
    )


def test_http_canonicalization_removes_default_port_and_sorts_query() -> None:
    normalized = normalize_resource("HTTPS://Example.COM.:443/a/b?z=2&a=1")
    assert normalized.value == "https://example.com/a/b?a=1&z=2"


@pytest.mark.parametrize(
    "value",
    (
        "https://user@example.com/path",
        "https://example.com/a%2fb",
        "https://example.com/a/%2e./b",
        "https://example.com/a\\..\\b",
        "https://example.com/a/../b",
        "https://exa*mple.com/path",
        "file:///etc/passwd",
    ),
)
def test_ambiguous_or_unsafe_resources_are_rejected(value: str) -> None:
    with pytest.raises(ResourceError):
        normalize_resource(value)


def test_leftmost_host_wildcard_does_not_match_apex() -> None:
    rule = ScopeRule(
        rule_id="wildcard-rule",
        effect=Effect.ALLOW,
        action="network.http",
        resource="https://*.example.com/api/**",
        methods=("GET",),
    )
    child = _proposal("https://api.example.com/api/value")
    apex = _proposal("https://example.com/api/value")
    assert rule_matches(rule, child, normalize_resource(child.resource))
    assert not rule_matches(rule, apex, normalize_resource(apex.resource))


def test_private_addresses_require_explicit_private_scope_and_cidr() -> None:
    target = _proposal(
        "http://127.0.0.1:9000/value",
        resolved_ips=["127.0.0.1"],
    )
    blocked = ScopeRule(
        rule_id="private-blocked",
        effect=Effect.ALLOW,
        action="network.http",
        resource="http://127.0.0.1:9000/**",
        methods=("GET",),
    )
    allowed = ScopeRule(
        rule_id="private-explicit",
        effect=Effect.ALLOW,
        action="network.http",
        resource="http://127.0.0.1:9000/**",
        methods=("GET",),
        allow_private_network=True,
        resolved_ip_cidrs=("127.0.0.0/8",),
    )
    assert not rule_matches(blocked, target, normalize_resource(target.resource))
    assert rule_matches(allowed, target, normalize_resource(target.resource))


def test_cidr_pinned_rule_requires_resolver_evidence() -> None:
    target = _proposal("https://service.example/value", resolved_ips=[])
    pinned = ScopeRule(
        rule_id="pinned-public-service",
        effect=Effect.ALLOW,
        action="network.http",
        resource="https://service.example/**",
        methods=("GET",),
        resolved_ip_cidrs=("203.0.113.0/24",),
    )
    assert not rule_matches(pinned, target, normalize_resource(target.resource))


def test_nested_attribute_allowlist_fails_closed_without_hashing() -> None:
    target = ActionProposal(
        request_id="nested-attributes",
        run_id="resource-run",
        action="mcp.call",
        resource="mcp://records/read",
        attributes={"filters": [{"field": "name"}]},
    )
    rule = ScopeRule(
        rule_id="nested-allowlist",
        effect=Effect.ALLOW,
        action="mcp.call",
        resource="mcp://records/read",
        required_attributes={"filters": [{"field": "name"}, {"field": "status"}]},
    )
    assert rule_matches(rule, target, normalize_resource(target.resource))
