# Copyright (c) Microsoft. All rights reserved.

"""Tests for MAML model classes."""

import sys
from typing import Any, cast

import pytest

from agent_framework_declarative._models import (
    AgentDefinition,
    AgentManifest,
    AnonymousConnection,
    ApiKeyConnection,
    ArrayProperty,
    Binding,
    CodeInterpreterTool,
    Connection,
    CustomTool,
    EnvironmentVariable,
    FileSearchTool,
    Format,
    FunctionTool,
    McpServerApprovalMode,
    McpServerToolAlwaysRequireApprovalMode,
    McpServerToolNeverRequireApprovalMode,
    McpServerToolSpecifyApprovalMode,
    McpTool,
    Model,
    ModelOptions,
    ModelResource,
    ObjectProperty,
    OpenApiTool,
    Parser,
    PromptAgent,
    Property,
    PropertySchema,
    ProtocolVersionRecord,
    ReferenceConnection,
    RemoteConnection,
    Resource,
    Template,
    ToolResource,
    WebSearchTool,
    _safe_mode_context,
    _try_powerfx_eval,
)

pytestmark = pytest.mark.skipif(sys.version_info >= (3, 14), reason="Skipping on Python 3.14+")


class TestBinding:
    """Tests for Binding class."""

    def test_binding_creation(self):
        binding = Binding(name="arg1", input="value1")
        assert binding.name == "arg1"
        assert binding.input == "value1"

    def test_binding_from_dict(self):
        data = {"name": "arg1", "input": "value1"}
        binding = Binding.from_dict(data)
        assert binding.name == "arg1"
        assert binding.input == "value1"

    def test_binding_to_dict(self):
        binding = Binding(name="arg1", input="value1")
        result = binding.to_dict()
        assert result["name"] == "arg1"
        assert result["input"] == "value1"


class TestProperty:
    """Tests for Property class."""

    def test_property_creation(self):
        prop = Property(
            name="test_prop",
            kind="string",
            description="A test property",
            required=True,
            default="default_value",
            example="example_value",
            enum=["val1", "val2"],
        )
        assert prop.name == "test_prop"
        assert prop.kind == "string"
        assert prop.description == "A test property"
        assert prop.required is True
        assert prop.default == "default_value"
        assert prop.example == "example_value"
        assert prop.enum == ["val1", "val2"]

    def test_property_from_dict(self):
        data = {
            "name": "test_prop",
            "kind": "string",
            "description": "A test property",
            "required": True,
        }
        prop = Property.from_dict(data)
        assert prop.name == "test_prop"
        assert prop.kind == "string"
        assert prop.description == "A test property"
        assert prop.required is True

    def test_property_from_dict_type_maps_to_kind(self):
        """Test that 'type' field in YAML is mapped to 'kind' internally."""
        data = {
            "name": "test_prop",
            "type": "string",
            "description": "A test property",
            "required": True,
        }
        prop = Property.from_dict(data)
        assert prop.name == "test_prop"
        assert prop.kind == "string"

    def test_property_from_dict_kind_takes_precedence_over_type(self):
        """Test that 'kind' takes precedence when both 'type' and 'kind' are present."""
        data = {
            "name": "test_prop",
            "type": "integer",
            "kind": "string",
        }
        prop = Property.from_dict(data)
        assert prop.kind == "string"

    def test_property_from_dict_type_dispatches_to_array(self):
        """Test that 'type: array' correctly dispatches to ArrayProperty."""
        data = {
            "name": "test_array",
            "type": "array",
            "items": {"type": "string"},
        }
        prop = Property.from_dict(data)
        assert isinstance(prop, ArrayProperty)
        assert prop.kind == "array"

    def test_property_from_dict_type_dispatches_to_object(self):
        """Test that 'type: object' correctly dispatches to ObjectProperty."""
        data = {
            "name": "test_object",
            "type": "object",
            "properties": {"field": {"type": "string"}},
        }
        prop = Property.from_dict(data)
        assert isinstance(prop, ObjectProperty)
        assert prop.kind == "object"


class TestArrayProperty:
    """Tests for ArrayProperty class."""

    def test_array_property_creation(self):
        items = Property(name="item", kind="string")
        array_prop = ArrayProperty(name="test_array", kind="array", items=items, required=True)
        assert array_prop.name == "test_array"
        assert array_prop.kind == "array"
        assert array_prop.items is not None
        assert array_prop.items.name == "item"
        assert array_prop.required is True

    def test_array_property_from_dict(self):
        data = {
            "name": "test_array",
            "kind": "array",
            "items": {"name": "item", "kind": "string"},
            "required": True,
        }
        array_prop = ArrayProperty.from_dict(data)
        assert isinstance(array_prop, ArrayProperty)
        assert array_prop.name == "test_array"
        assert array_prop.kind == "array"
        assert isinstance(array_prop.items, Property)
        assert array_prop.items.name == "item"


class TestObjectProperty:
    """Tests for ObjectProperty class."""

    def test_object_property_creation(self):
        props = [
            Property(name="prop1", kind="string"),
            Property(name="prop2", kind="integer"),
        ]
        obj_prop = ObjectProperty(name="test_object", kind="object", properties=props, required=True)
        assert obj_prop.name == "test_object"
        assert obj_prop.kind == "object"
        assert len(obj_prop.properties) == 2
        assert obj_prop.properties[0].name == "prop1"

    def test_object_property_from_dict(self):
        data = {
            "name": "test_object",
            "kind": "object",
            "properties": [
                {"name": "prop1", "kind": "string"},
                {"name": "prop2", "kind": "integer"},
            ],
            "required": True,
        }
        obj_prop = ObjectProperty.from_dict(data)
        assert isinstance(obj_prop, ObjectProperty)
        assert obj_prop.name == "test_object"
        assert obj_prop.kind == "object"
        assert len(obj_prop.properties) == 2
        assert all(isinstance(p, Property) for p in obj_prop.properties)

    def test_object_property_with_dict_properties(self):
        """Test ObjectProperty with dict format for properties (MAML YAML dict syntax)."""
        data = {
            "name": "person",
            "kind": "object",
            "properties": {
                "name": {"kind": "string", "required": True},
                "email": {"kind": "string"},
                "age": {"kind": "integer"},
            },
        }
        obj_prop = ObjectProperty.from_dict(data)
        assert isinstance(obj_prop, ObjectProperty)
        assert obj_prop.name == "person"
        assert obj_prop.kind == "object"
        assert len(obj_prop.properties) == 3

        # Check that all properties were converted correctly
        prop_names = {p.name for p in obj_prop.properties}
        assert prop_names == {"name", "email", "age"}

        # Check specific property
        name_prop = next(p for p in obj_prop.properties if p.name == "name")  # pyrefly: ignore[not-iterable]
        assert name_prop.kind == "string"
        assert name_prop.required is True


class TestPropertySchema:
    """Tests for PropertySchema class."""

    def test_property_schema_creation(self):
        props = [Property(name="prop1", kind="string")]
        schema = PropertySchema(properties=props, strict=True)
        assert schema.strict is True
        assert len(schema.properties) == 1

    def test_property_schema_from_dict(self):
        data = {
            "strict": False,
            "properties": [{"name": "prop1", "kind": "string"}],
        }
        schema = PropertySchema.from_dict(data)
        assert schema.strict is False
        assert len(schema.properties) == 1
        # Properties are properly converted to Property instances
        assert isinstance(schema.properties[0], Property)
        assert schema.properties[0].name == "prop1"
        assert schema.properties[0].kind == "string"

    def test_property_schema_with_dict_properties(self):
        """Test PropertySchema with dict format for properties (MAML YAML dict syntax)."""
        data = {
            "strict": True,
            "properties": {
                "firstName": {"kind": "string", "description": "First name"},
                "lastName": {"kind": "string", "description": "Last name"},
                "age": {"kind": "integer", "required": True},
            },
        }
        schema = PropertySchema.from_dict(data)
        assert schema.strict is True
        assert len(schema.properties) == 3

        # Check that all properties were converted correctly
        prop_names = {p.name for p in schema.properties}
        assert prop_names == {"firstName", "lastName", "age"}

        # Check specific property details
        age_prop = next(p for p in schema.properties if p.name == "age")
        assert age_prop.kind == "integer"
        assert age_prop.required is True

    def test_property_schema_with_type_field_produces_correct_json_schema(self):
        """Test that PropertySchema with 'type' fields (YAML spec format) produces valid JSON schema."""
        data = {
            "properties": {
                "language": {"type": "string", "required": True, "description": "The language."},
                "answer": {"type": "string", "required": False, "description": "The answer."},
            },
        }
        schema = PropertySchema.from_dict(data)
        assert len(schema.properties) == 2

        lang_prop = next(p for p in schema.properties if p.name == "language")
        assert lang_prop.kind == "string"

        json_schema = schema.to_json_schema()
        assert json_schema["type"] == "object"
        assert json_schema["properties"]["language"]["type"] == "string"
        assert json_schema["properties"]["answer"]["type"] == "string"
        # required is a top-level array, not a per-property boolean
        assert json_schema["required"] == ["language"]
        assert "required" not in json_schema["properties"]["language"]
        assert "required" not in json_schema["properties"]["answer"]


class TestConnection:
    """Tests for Connection base class."""

    def test_connection_creation(self):
        conn = Connection(kind=cast("Any", "base"))
        assert conn.kind == "base"

    def test_connection_from_dict(self):
        data = {"kind": "base"}
        conn = Connection.from_dict(data)
        assert conn.kind == "base"


class TestReferenceConnection:
    """Tests for ReferenceConnection class."""

    def test_reference_connection_creation(self):
        conn = ReferenceConnection(name="my-connection", target="target-connection")
        assert conn.kind == "reference"
        assert conn.name == "my-connection"
        assert conn.target == "target-connection"

    def test_reference_connection_from_dict(self):
        data = {"kind": "reference", "name": "my-connection", "target": "target-connection"}
        conn = ReferenceConnection.from_dict(data)
        assert conn.kind == "reference"
        assert conn.name == "my-connection"
        assert conn.target == "target-connection"


class TestRemoteConnection:
    """Tests for RemoteConnection class."""

    def test_remote_connection_creation(self):
        conn = RemoteConnection(name="my-remote", endpoint="https://api.example.com")
        assert conn.kind == "remote"
        assert conn.endpoint == "https://api.example.com"

    def test_remote_connection_from_dict(self):
        data = {"kind": "remote", "endpoint": "https://api.example.com"}
        conn = RemoteConnection.from_dict(data)
        assert conn.kind == "remote"
        assert conn.endpoint == "https://api.example.com"


class TestApiKeyConnection:
    """Tests for ApiKeyConnection class."""

    def test_api_key_connection_creation(self):
        conn = ApiKeyConnection(apiKey="secret-key", endpoint="https://api.example.com")
        assert conn.kind == "key"
        assert conn.apiKey == "secret-key"
        assert conn.endpoint == "https://api.example.com"

    def test_api_key_connection_from_dict(self):
        data = {"kind": "key", "apiKey": "secret-key", "endpoint": "https://api.example.com"}
        conn = ApiKeyConnection.from_dict(data)
        assert conn.kind == "key"
        assert conn.apiKey == "secret-key"


class TestAnonymousConnection:
    """Tests for AnonymousConnection class."""

    def test_anonymous_connection_creation(self):
        conn = AnonymousConnection(endpoint="https://api.example.com")
        assert conn.kind == "anonymous"
        assert conn.endpoint == "https://api.example.com"

    def test_anonymous_connection_from_dict(self):
        data = {"kind": "anonymous", "endpoint": "https://api.example.com"}
        conn = AnonymousConnection.from_dict(data)
        assert conn.kind == "anonymous"
        assert conn.endpoint == "https://api.example.com"


class TestModelOptions:
    """Tests for ModelOptions class."""

    def test_model_options_creation(self):
        options = ModelOptions(temperature=0.7, maxOutputTokens=1000, topP=0.9)
        assert options.temperature == 0.7
        assert options.maxOutputTokens == 1000
        assert options.topP == 0.9

    def test_model_options_from_dict(self):
        data = {"temperature": 0.7, "maxOutputTokens": 1000, "topP": 0.9}
        options = ModelOptions.from_dict(data)
        assert options.temperature == 0.7
        assert options.maxOutputTokens == 1000
        assert options.topP == 0.9


class TestModel:
    """Tests for Model class."""

    def test_model_creation(self):
        model = Model(id="gpt-4", provider="openai")
        assert model.id == "gpt-4"
        assert model.provider == "openai"

    def test_model_from_dict(self):
        data = {"id": "gpt-4", "provider": "openai"}
        model = Model.from_dict(data)
        assert model.id == "gpt-4"
        assert model.provider == "openai"

    def test_model_with_connection(self):
        data = {
            "id": "gpt-4",
            "connection": {"kind": "reference", "name": "my-connection"},
        }
        model = Model.from_dict(data)
        assert model.id == "gpt-4"
        assert model.connection is not None
        assert model.connection.kind == "reference"


class TestFormat:
    """Tests for Format class."""

    def test_format_creation(self):
        fmt = Format(kind="json", strict=True, options={"type": "object"})
        assert fmt.kind == "json"
        assert fmt.strict is True
        assert fmt.options == {"type": "object"}

    def test_format_from_dict(self):
        data = {"kind": "json", "strict": False, "options": {"type": "object"}}
        fmt = Format.from_dict(data)
        assert fmt.kind == "json"
        assert fmt.strict is False


class TestParser:
    """Tests for Parser class."""

    def test_parser_creation(self):
        parser = Parser(kind="json", options={"strict": True})
        assert parser.kind == "json"
        assert parser.options == {"strict": True}

    def test_parser_from_dict(self):
        data = {"kind": "json", "options": {"strict": True}}
        parser = Parser.from_dict(data)
        assert parser.kind == "json"
        assert parser.options == {"strict": True}


class TestTemplate:
    """Tests for Template class."""

    def test_template_creation(self):
        template = Template(
            format=Format(kind="text"),
            parser=Parser(kind="text"),
        )
        assert isinstance(template.format, Format)
        assert isinstance(template.parser, Parser)

    def test_template_from_dict(self):
        data = {
            "format": {"kind": "text"},
            "parser": {"kind": "text"},
        }
        template = Template.from_dict(data)
        assert isinstance(template.format, Format)
        assert isinstance(template.parser, Parser)


class TestAgentDefinition:
    """Tests for AgentDefinition class."""

    def test_agent_definition_creation(self):
        agent = AgentDefinition(
            name="test-agent",
            description="A test agent",
        )
        assert agent.name == "test-agent"
        assert agent.description == "A test agent"

    def test_agent_definition_from_dict(self):
        data = {
            "name": "test-agent",
            "description": "A test agent",
        }
        agent = AgentDefinition.from_dict(data)
        assert agent.name == "test-agent"
        assert agent.description == "A test agent"


class TestFunctionTool:
    """Tests for FunctionTool class."""

    def test_function_tool_creation(self):
        tool = FunctionTool(
            name="my_function",
            description="A test function",
            kind="function",
        )
        assert tool.name == "my_function"
        assert tool.kind == "function"

    def test_function_tool_from_dict(self):
        data = {
            "name": "my_function",
            "description": "A test function",
            "kind": "function",
            "strict": False,
        }
        tool = FunctionTool.from_dict(data)
        assert tool.name == "my_function"
        assert tool.kind == "function"

    def test_function_tool_with_dict_bindings(self):
        """Test FunctionTool with dict format for bindings (MAML YAML dict syntax)."""
        data = {
            "name": "calculate",
            "kind": "function",
            "description": "Calculate something",
            "bindings": {
                "x": "input.x",
                "y": "input.y",
                "operation": "input.op",
            },
        }
        tool = FunctionTool.from_dict(data)
        assert tool.name == "calculate"
        assert len(tool.bindings) == 3

        # Check that all bindings were converted correctly
        binding_names = {b.name for b in tool.bindings}
        assert binding_names == {"x", "y", "operation"}

        # Check specific binding
        x_binding = next(b for b in tool.bindings if b.name == "x")
        assert x_binding.input == "input.x"


class TestCustomTool:
    """Tests for CustomTool class."""

    def test_custom_tool_creation(self):
        tool = CustomTool(
            name="custom_tool",
            description="A custom tool",
            kind="custom",
            options={"endpoint": "https://tool.example.com"},
        )
        assert tool.name == "custom_tool"
        assert tool.kind == "custom"
        assert tool.options == {"endpoint": "https://tool.example.com"}

    def test_custom_tool_from_dict(self):
        data = {
            "name": "custom_tool",
            "description": "A custom tool",
            "kind": "custom",
            "options": {"endpoint": "https://tool.example.com"},
        }
        tool = CustomTool.from_dict(data)
        assert tool.name == "custom_tool"
        assert tool.kind == "custom"


class TestWebSearchTool:
    """Tests for WebSearchTool class."""

    def test_web_search_tool_creation(self):
        tool = WebSearchTool(
            name="web_search",
            description="Search the web",
            kind="web_search",
            options={"maxResults": 10},
        )
        assert tool.name == "web_search"
        assert tool.kind == "web_search"
        assert tool.options == {"maxResults": 10}

    def test_web_search_tool_from_dict(self):
        data = {
            "name": "web_search",
            "description": "Search the web",
            "kind": "web_search",
            "options": {"maxResults": 10},
        }
        tool = WebSearchTool.from_dict(data)
        assert tool.name == "web_search"
        assert tool.kind == "web_search"
        assert tool.options == {"maxResults": 10}


class TestFileSearchTool:
    """Tests for FileSearchTool class."""

    def test_file_search_tool_creation(self):
        tool = FileSearchTool(
            name="file_search",
            description="Search files",
            kind="file_search",
            vectorStoreIds=["vs1", "vs2"],
        )
        assert tool.name == "file_search"
        assert tool.kind == "file_search"
        assert tool.vectorStoreIds == ["vs1", "vs2"]

    def test_file_search_tool_from_dict(self):
        data = {
            "name": "file_search",
            "description": "Search files",
            "kind": "file_search",
            "vectorStoreIds": ["vs1", "vs2"],
        }
        tool = FileSearchTool.from_dict(data)
        assert tool.name == "file_search"
        assert tool.kind == "file_search"
        assert tool.vectorStoreIds == ["vs1", "vs2"]


class TestMcpServerApprovalMode:
    """Tests for MCP Server Approval Mode classes."""

    def test_always_approval_mode(self):
        mode = McpServerToolAlwaysRequireApprovalMode()
        assert mode.kind == "always"

    def test_always_approval_mode_from_dict(self):
        data = {"kind": "always"}
        mode = McpServerToolAlwaysRequireApprovalMode.from_dict(data)
        assert mode.kind == "always"

    def test_never_approval_mode(self):
        mode = McpServerToolNeverRequireApprovalMode()
        assert mode.kind == "never"

    def test_never_approval_mode_from_dict(self):
        data = {"kind": "never"}
        mode = McpServerToolNeverRequireApprovalMode.from_dict(data)
        assert mode.kind == "never"

    def test_specify_approval_mode(self):
        mode = McpServerToolSpecifyApprovalMode(
            alwaysRequireApprovalTools=["tool1"],
            neverRequireApprovalTools=["tool2"],
        )
        assert mode.kind == "specify"
        assert mode.alwaysRequireApprovalTools == ["tool1"]
        assert mode.neverRequireApprovalTools == ["tool2"]

    def test_specify_approval_mode_from_dict(self):
        data = {
            "kind": "specify",
            "alwaysRequireApprovalTools": ["tool1"],
            "neverRequireApprovalTools": ["tool2"],
        }
        mode = McpServerToolSpecifyApprovalMode.from_dict(data)
        assert mode.kind == "specify"
        assert mode.alwaysRequireApprovalTools == ["tool1"]
        assert mode.neverRequireApprovalTools == ["tool2"]


class TestMcpTool:
    """Tests for McpTool class."""

    def test_mcp_tool_creation(self):
        tool = McpTool(
            name="mcp_tool",
            description="An MCP tool",
            kind="mcp",
            serverName="test-server",
        )
        assert tool.name == "mcp_tool"
        assert tool.kind == "mcp"
        assert tool.serverName == "test-server"

    def test_mcp_tool_from_dict(self):
        data = {
            "name": "mcp_tool",
            "description": "An MCP tool",
            "kind": "mcp",
            "serverName": "test-server",
            "approvalMode": {"kind": "always"},
        }
        tool = McpTool.from_dict(data)
        assert tool.name == "mcp_tool"
        assert tool.kind == "mcp"
        assert isinstance(tool.approvalMode, McpServerApprovalMode)

    def test_mcp_tool_with_simplified_approval_mode(self):
        """Test McpTool with simplified string format for approvalMode."""
        # Test simplified string format: approvalMode: "always"
        data = {
            "name": "mcp_tool",
            "description": "An MCP tool",
            "kind": "mcp",
            "serverName": "test-server",
            "approvalMode": "always",
        }
        tool = McpTool.from_dict(data)
        assert tool.name == "mcp_tool"
        assert tool.kind == "mcp"
        assert isinstance(tool.approvalMode, McpServerApprovalMode)
        assert tool.approvalMode.kind == "always"

    def test_mcp_tool_approval_mode_equivalence(self):
        """Test that simplified and full format produce equivalent results."""
        # Simplified format
        data_simplified = {
            "name": "mcp_tool",
            "kind": "mcp",
            "approvalMode": "never",
        }
        tool_simplified = McpTool.from_dict(data_simplified)

        # Full format
        data_full = {
            "name": "mcp_tool",
            "kind": "mcp",
            "approvalMode": {"kind": "never"},
        }
        tool_full = McpTool.from_dict(data_full)

        # Both should produce the same result
        assert tool_simplified.approvalMode is not None
        assert tool_full.approvalMode is not None
        assert tool_simplified.approvalMode.kind == tool_full.approvalMode.kind
        assert tool_simplified.approvalMode.kind == "never"


class TestOpenApiTool:
    """Tests for OpenApiTool class."""

    def test_openapi_tool_creation(self):
        tool = OpenApiTool(
            name="openapi_tool",
            description="An OpenAPI tool",
            kind="openapi",
            specification="https://api.example.com/openapi.json",
        )
        assert tool.name == "openapi_tool"
        assert tool.kind == "openapi"
        assert tool.specification == "https://api.example.com/openapi.json"

    def test_openapi_tool_from_dict(self):
        data = {
            "name": "openapi_tool",
            "description": "An OpenAPI tool",
            "kind": "openapi",
            "specification": "https://api.example.com/openapi.json",
        }
        tool = OpenApiTool.from_dict(data)
        assert tool.name == "openapi_tool"
        assert tool.kind == "openapi"


class TestCodeInterpreterTool:
    """Tests for CodeInterpreterTool class."""

    def test_code_interpreter_tool_creation(self):
        tool = CodeInterpreterTool(
            name="code_interpreter",
            description="Execute code",
            kind="code_interpreter",
            fileIds=["file1", "file2"],
        )
        assert tool.name == "code_interpreter"
        assert tool.kind == "code_interpreter"
        assert tool.fileIds == ["file1", "file2"]

    def test_code_interpreter_tool_from_dict(self):
        data = {
            "name": "code_interpreter",
            "description": "Execute code",
            "kind": "code_interpreter",
            "fileIds": ["file1", "file2"],
        }
        tool = CodeInterpreterTool.from_dict(data)
        assert tool.name == "code_interpreter"
        assert tool.kind == "code_interpreter"
        assert tool.fileIds == ["file1", "file2"]


class TestPromptAgent:
    """Tests for PromptAgent class."""

    def test_prompt_agent_creation(self):
        agent = PromptAgent(
            name="prompt-agent",
            description="A prompt-based agent",
            instructions="You are a helpful assistant",
            kind="Prompt",
        )
        assert agent.name == "prompt-agent"
        assert agent.kind == "Prompt"
        assert agent.instructions == "You are a helpful assistant"

    def test_prompt_agent_from_dict(self):
        data = {
            "name": "prompt-agent",
            "description": "A prompt-based agent",
            "instructions": "You are a helpful assistant",
            "kind": "Prompt",
            "model": {"id": "gpt-4"},
        }
        agent = PromptAgent.from_dict(data)
        assert isinstance(agent, PromptAgent)
        assert agent.name == "prompt-agent"
        assert isinstance(agent.model, Model)

    def test_prompt_agent_with_tools(self):
        data = {
            "name": "prompt-agent",
            "kind": "Prompt",
            "tools": [
                {"name": "search", "kind": "web_search"},
                {"name": "calc", "kind": "function"},
            ],
        }
        agent = PromptAgent.from_dict(data)
        assert isinstance(agent, PromptAgent)
        assert agent.tools is not None
        assert len(agent.tools) == 2
        # Tools are converted via Tool.from_dict, type depends on 'kind'
        assert agent.tools[0].kind == "web_search"
        assert agent.tools[1].kind == "function"


class TestResource:
    """Tests for Resource base class."""

    def test_resource_creation(self):
        resource = Resource(name="test-resource", kind="Resource")
        assert resource.name == "test-resource"
        assert resource.kind == "Resource"

    def test_resource_from_dict(self):
        data = {"name": "test-resource", "kind": "Resource"}
        resource = Resource.from_dict(data)
        assert resource.name == "test-resource"


class TestModelResource:
    """Tests for ModelResource class."""

    def test_model_resource_creation(self):
        resource = ModelResource(name="my-model", kind="model", id="gpt-4")
        assert resource.name == "my-model"
        assert resource.kind == "model"
        assert resource.id == "gpt-4"

    def test_model_resource_from_dict(self):
        data = {
            "name": "my-model",
            "kind": "model",
            "id": "gpt-4",
        }
        resource = ModelResource.from_dict(data)
        assert isinstance(resource, ModelResource)
        assert resource.name == "my-model"
        assert resource.kind == "model"
        assert resource.id == "gpt-4"


class TestToolResource:
    """Tests for ToolResource class."""

    def test_tool_resource_creation(self):
        resource = ToolResource(name="my-tool", kind="tool", id="search-tool")
        assert resource.name == "my-tool"
        assert resource.kind == "tool"
        assert resource.id == "search-tool"

    def test_tool_resource_from_dict(self):
        data = {
            "name": "my-tool",
            "kind": "tool",
            "id": "search-tool",
        }
        resource = ToolResource.from_dict(data)
        assert isinstance(resource, ToolResource)
        assert resource.name == "my-tool"
        assert resource.kind == "tool"
        assert resource.id == "search-tool"


class TestProtocolVersionRecord:
    """Tests for ProtocolVersionRecord class."""

    def test_protocol_version_record_creation(self):
        record = ProtocolVersionRecord(protocol="mcp", version="1.0.0")
        assert record.protocol == "mcp"
        assert record.version == "1.0.0"

    def test_protocol_version_record_from_dict(self):
        data = {"protocol": "mcp", "version": "1.0.0"}
        record = ProtocolVersionRecord.from_dict(data)
        assert record.protocol == "mcp"
        assert record.version == "1.0.0"


class TestEnvironmentVariable:
    """Tests for EnvironmentVariable class."""

    def test_environment_variable_creation(self):
        env_var = EnvironmentVariable(name="API_KEY", value="secret123")
        assert env_var.name == "API_KEY"
        assert env_var.value == "secret123"

    def test_environment_variable_from_dict(self):
        data = {"name": "API_KEY", "value": "secret123"}
        env_var = EnvironmentVariable.from_dict(data)
        assert env_var.name == "API_KEY"
        assert env_var.value == "secret123"


# Check if PowerFx is available
try:
    from powerfx import Engine as _PfxEngine

    _PfxEngine()
    _powerfx_available = True
except (ImportError, RuntimeError):
    _powerfx_available = False


class TestTryPowerfxEval:
    """Tests for _try_powerfx_eval function."""

    def test_no_evaluation_without_equals_prefix(self):
        """Test that strings without '=' prefix are returned as-is."""
        assert _try_powerfx_eval("hello") == "hello"
        assert _try_powerfx_eval("test value") == "test value"
        assert _try_powerfx_eval("123") == "123"

    def test_none_value_returns_none(self):
        """Test that None values are returned as None."""
        assert _try_powerfx_eval(cast("str", None)) is None

    def test_empty_string_returns_empty(self):
        """Test that empty strings are returned as empty."""
        assert _try_powerfx_eval("") == ""

    @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
    def test_simple_powerfx_expressions(self):
        """Test simple PowerFx expressions."""
        from decimal import Decimal

        # Simple math - returns Decimal
        assert _try_powerfx_eval("=1 + 2") == Decimal("3")
        assert _try_powerfx_eval("=10 * 5") == Decimal("50")

        # String literals
        assert _try_powerfx_eval('="hello"') == "hello"
        assert _try_powerfx_eval('="test value"') == "test value"

    @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
    def test_env_variable_access(self, monkeypatch):
        """Test accessing environment variables using =Env.<name> pattern."""
        # Set up test environment variables
        monkeypatch.setenv("TEST_VAR", "test_value")
        monkeypatch.setenv("API_KEY", "secret123")
        monkeypatch.setenv("PORT", "8080")

        # Set safe_mode=False to allow environment variable access
        token = _safe_mode_context.set(False)
        try:
            # Test basic env access
            assert _try_powerfx_eval("=Env.TEST_VAR") == "test_value"
            assert _try_powerfx_eval("=Env.API_KEY") == "secret123"
            assert _try_powerfx_eval("=Env.PORT") == "8080"
        finally:
            _safe_mode_context.reset(token)

    @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
    def test_env_variable_with_string_concatenation(self, monkeypatch):
        """Test env variables with string concatenation operator."""
        monkeypatch.setenv("BASE_URL", "https://api.example.com")
        monkeypatch.setenv("API_VERSION", "v1")

        # Set safe_mode=False to allow environment variable access
        token = _safe_mode_context.set(False)
        try:
            # Test concatenation with &
            result = _try_powerfx_eval('=Env.BASE_URL & "/" & Env.API_VERSION')
            assert result == "https://api.example.com/v1"

            # Test concatenation with literals
            result = _try_powerfx_eval('="API Key: " & Env.API_VERSION')
            assert result == "API Key: v1"
        finally:
            _safe_mode_context.reset(token)

    @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
    def test_string_comparison_operators(self, monkeypatch):
        """Test PowerFx string comparison operators."""
        monkeypatch.setenv("ENV_MODE", "production")

        # Set safe_mode=False to allow environment variable access
        token = _safe_mode_context.set(False)
        try:
            # Equal to - returns bool
            assert _try_powerfx_eval('=Env.ENV_MODE = "production"') is True
            assert _try_powerfx_eval('=Env.ENV_MODE = "development"') is False

            # Not equal to - returns bool
            assert _try_powerfx_eval('=Env.ENV_MODE <> "development"') is True
            assert _try_powerfx_eval('=Env.ENV_MODE <> "production"') is False
        finally:
            _safe_mode_context.reset(token)

    @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
    def test_string_in_operator(self):
        """Test PowerFx 'in' operator for substring testing (case-insensitive)."""
        # Substring test - case insensitive - returns bool
        assert _try_powerfx_eval('="the" in "The keyboard and the monitor"') is True
        assert _try_powerfx_eval('="THE" in "The keyboard and the monitor"') is True
        assert _try_powerfx_eval('="xyz" in "The keyboard and the monitor"') is False

    @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
    def test_string_exactin_operator(self):
        """Test PowerFx 'exactin' operator for substring testing (case-sensitive)."""
        # Substring test - case sensitive - returns bool
        assert _try_powerfx_eval('="Windows" exactin "To display windows in the Windows operating system"') is True
        assert _try_powerfx_eval('="windows" exactin "To display windows in the Windows operating system"') is True
        assert _try_powerfx_eval('="WINDOWS" exactin "To display windows in the Windows operating system"') is False

    @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
    def test_logical_operators_with_strings(self):
        """Test PowerFx logical operators (And, Or, Not) with string comparisons."""
        # And operator - returns bool
        assert _try_powerfx_eval('="a" = "a" And "b" = "b"') is True
        assert _try_powerfx_eval('="a" = "a" And "b" = "c"') is False

        # && operator (alternative syntax) - returns bool
        assert _try_powerfx_eval('="a" = "a" && "b" = "b"') is True

        # Or operator - returns bool
        assert _try_powerfx_eval('="a" = "b" Or "c" = "c"') is True
        assert _try_powerfx_eval('="a" = "b" Or "c" = "d"') is False

        # || operator (alternative syntax) - returns bool
        assert _try_powerfx_eval('="a" = "b" || "c" = "c"') is True

        # Not operator - returns bool
        assert _try_powerfx_eval('=Not("a" = "b")') is True
        assert _try_powerfx_eval('=Not("a" = "a")') is False

        # ! operator (alternative syntax) - returns bool
        assert _try_powerfx_eval('=!("a" = "b")') is True

    @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
    def test_parentheses_for_precedence(self):
        """Test using parentheses to control operator precedence."""
        from decimal import Decimal

        # Test arithmetic precedence - returns Decimal
        assert _try_powerfx_eval("=(1 + 2) * 3") == Decimal("9")
        assert _try_powerfx_eval("=1 + 2 * 3") == Decimal("7")

        # Test logical precedence - returns bool
        result = _try_powerfx_eval('=("a" = "a" Or "b" = "c") And "d" = "d"')
        assert result is True

    @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
    def test_env_with_special_characters(self, monkeypatch):
        """Test env variables containing special characters in values."""
        monkeypatch.setenv("URL_WITH_QUERY", "https://example.com?param=value")
        monkeypatch.setenv("PATH_WITH_SPACES", "C:\\Program Files\\App")

        # Set safe_mode=False to allow environment variable access
        token = _safe_mode_context.set(False)
        try:
            result = _try_powerfx_eval("=Env.URL_WITH_QUERY")
            assert result == "https://example.com?param=value"

            result = _try_powerfx_eval("=Env.PATH_WITH_SPACES")
            assert result == "C:\\Program Files\\App"
        finally:
            _safe_mode_context.reset(token)

    def test_safe_mode_blocks_env_access(self, monkeypatch):
        """Test that safe_mode=True (default) blocks environment variable access."""
        monkeypatch.setenv("SECRET_VAR", "secret_value")

        # Set safe_mode=True (default)
        token = _safe_mode_context.set(True)
        try:
            # When safe_mode=True, Env is not available and the expression fails,
            # returning the original value
            result = _try_powerfx_eval("=Env.SECRET_VAR")
            assert result == "=Env.SECRET_VAR"
        finally:
            _safe_mode_context.reset(token)

    @pytest.mark.skipif(not _powerfx_available, reason="PowerFx engine not available")
    def test_safe_mode_context_isolation(self, monkeypatch):
        """Test that safe_mode context variable properly isolates env access."""
        monkeypatch.setenv("TEST_VAR", "test_value")

        # First, set safe_mode=True - should NOT allow env access
        token = _safe_mode_context.set(True)
        try:
            result_safe = _try_powerfx_eval("=Env.TEST_VAR")
            assert result_safe == "=Env.TEST_VAR"

            # Then, set safe_mode=False - should allow env access
            token2 = _safe_mode_context.set(False)
            try:
                result_unsafe = _try_powerfx_eval("=Env.TEST_VAR")
                assert result_unsafe == "test_value"
            finally:
                _safe_mode_context.reset(token2)

            # After reset, should block again
            result_safe_again = _try_powerfx_eval("=Env.TEST_VAR")
            assert result_safe_again == "=Env.TEST_VAR"
        finally:
            _safe_mode_context.reset(token)


class TestAgentManifest:
    """Tests for AgentManifest class."""

    def test_agent_manifest_creation(self):
        manifest = AgentManifest(name="my-agent-manifest", description="A test manifest")
        assert manifest.name == "my-agent-manifest"
        assert manifest.description == "A test manifest"

    def test_agent_manifest_from_dict(self):
        data = {
            "name": "my-agent-manifest",
            "description": "A test manifest",
        }
        manifest = AgentManifest.from_dict(data)
        assert manifest.name == "my-agent-manifest"

    def test_agent_manifest_with_resources(self):
        data = {
            "name": "my-agent-manifest",
            "resources": [
                {"name": "model1", "kind": "model", "id": "gpt-4"},
                {
                    "name": "tool1",
                    "kind": "tool",
                    "id": "search-tool",
                },
            ],
        }
        manifest = AgentManifest.from_dict(data)
        assert manifest.name == "my-agent-manifest"
        assert len(manifest.resources) == 2
        # Resources are converted via Resource.from_dict based on their 'kind'
        assert isinstance(manifest.resources[0], ModelResource)
        assert isinstance(manifest.resources[1], ToolResource)

    def test_agent_manifest_complete(self):
        """Test a complete agent manifest with all fields."""
        data = {
            "name": "complete-manifest",
            "description": "A complete test manifest",
            "template": {
                "name": "assistant",
                "kind": "Prompt",
                "description": "A helpful assistant",
            },
            "resources": [
                {"name": "model1", "kind": "model", "id": "gpt-4"},
            ],
        }
        manifest = AgentManifest.from_dict(data)
        assert manifest.name == "complete-manifest"
        assert isinstance(manifest.template, AgentDefinition)
        assert len(manifest.resources) == 1
        assert isinstance(manifest.resources[0], ModelResource)

    def test_agent_manifest_with_dict_resources(self):
        """Test AgentManifest with dict format for resources (MAML YAML dict syntax)."""
        data = {
            "name": "manifest-with-dict-resources",
            "description": "Test manifest with dict resources",
            "resources": {
                "gptModelDeployment": {"kind": "model", "id": "gpt-4o"},
                "webSearchInstance": {"kind": "tool", "id": "web-search"},
                "analyticsTool": {"kind": "tool", "id": "analytics"},
            },
        }
        manifest = AgentManifest.from_dict(data)
        assert manifest.name == "manifest-with-dict-resources"
        assert len(manifest.resources) == 3

        # Check that all resources were converted correctly
        resource_names = {r.name for r in manifest.resources}
        assert resource_names == {"gptModelDeployment", "webSearchInstance", "analyticsTool"}

        # Check specific resource
        gpt_resource = next(r for r in manifest.resources if r.name == "gptModelDeployment")
        assert isinstance(gpt_resource, ModelResource)
        assert gpt_resource.id == "gpt-4o"

        web_resource = next(r for r in manifest.resources if r.name == "webSearchInstance")
        assert isinstance(web_resource, ToolResource)
        assert web_resource.id == "web-search"
