# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

from unittest.mock import Mock

from a2a.types import TaskArtifactUpdateEvent
from a2a.types import TaskStatusUpdateEvent
from google.adk.a2a import _compat
from google.adk.a2a.converters.from_adk_event import convert_event_to_a2a_events
from google.adk.events import event_actions
from google.adk.events.event import Event
from google.genai import types as genai_types
import pytest


class TestFromAdk:
  """Test suite for from_adk functions."""

  def setup_method(self):
    """Set up test fixtures."""
    self.mock_event = Mock(spec=Event)
    self.mock_event.id = "test-event-id"
    self.mock_event.invocation_id = "test-invocation-id"
    self.mock_event.author = "test-author"
    self.mock_event.branch = None
    self.mock_event.content = None
    self.mock_event.error_code = None
    self.mock_event.error_message = None
    self.mock_event.grounding_metadata = None
    self.mock_event.citation_metadata = None
    self.mock_event.custom_metadata = None
    self.mock_event.usage_metadata = None
    self.mock_event.actions = None
    self.mock_event.partial = True
    self.mock_event.long_running_tool_ids = None

  def test_convert_event_to_a2a_events_artifact_update(self):
    """Test conversion of event to TaskArtifactUpdateEvent."""
    # Setup event with content
    self.mock_event.content = genai_types.Content(
        parts=[genai_types.Part(text="hello")], role="model"
    )
    self.mock_event.author = "agent-1"

    agents_artifacts = {}

    # Mock part converter to return a standard text part
    mock_a2a_part = _compat.make_text_part("hello")
    mock_convert_part = Mock(return_value=[mock_a2a_part])

    result = convert_event_to_a2a_events(
        self.mock_event,
        agents_artifacts,
        task_id="task-123",
        context_id="context-456",
        part_converter=mock_convert_part,
    )

    assert len(result) == 1
    assert isinstance(result[0], TaskArtifactUpdateEvent)
    assert result[0].task_id == "task-123"
    assert result[0].context_id == "context-456"
    assert result[0].artifact.parts == [mock_a2a_part]
    assert "agent-1" in agents_artifacts  # Artifact ID should be stored

  def test_convert_event_to_a2a_events_error(self):
    """Test conversion of event with error to TaskStatusUpdateEvent."""
    self.mock_event.error_code = "ERR001"
    self.mock_event.error_message = "Something went wrong"

    agents_artifacts = {}

    result = convert_event_to_a2a_events(
        self.mock_event,
        agents_artifacts,
        task_id="task-123",
        context_id="context-456",
    )

    # Should not return any artifact events
    assert len(result) == 0

  def test_convert_event_to_a2a_events_none_event(self):
    """Test convert_event_to_a2a_events with None event."""
    with pytest.raises(ValueError, match="Event cannot be None"):
      convert_event_to_a2a_events(None, {})

  def test_convert_event_to_a2a_events_none_artifacts(self):
    """Test convert_event_to_a2a_events with None agents_artifacts."""
    with pytest.raises(ValueError, match="Agents artifacts cannot be None"):
      convert_event_to_a2a_events(self.mock_event, None)

  def test_convert_event_to_a2a_events_with_actions(self):
    """Test conversion of event with actions to TaskStatusUpdateEvent."""
    self.mock_event.actions = event_actions.EventActions()
    self.mock_event.actions.artifact_delta["image"] = 0

    agents_artifacts = {}

    result = convert_event_to_a2a_events(
        self.mock_event,
        agents_artifacts,
        task_id="task-123",
        context_id="context-456",
    )

    assert len(result) == 1
    assert isinstance(result[0], TaskStatusUpdateEvent)
    assert result[0].task_id == "task-123"
    assert result[0].context_id == "context-456"

    metadata = result[0].status.message.metadata
    assert "adk_actions" in metadata
    assert metadata["adk_actions"]["artifactDelta"] == {"image": 0}


class TestSerializeValue:
  """Tests for _serialize_value preserving JSON-native types."""

  def setup_method(self) -> None:
    from google.adk.a2a.converters.from_adk_event import _serialize_value

    self.serialize = _serialize_value

  def test_dict_preserved(self) -> None:
    value = {"key": "val", "nested": {"a": 1}}
    result = self.serialize(value)
    assert result == value
    assert isinstance(result, dict)

  def test_list_preserved(self) -> None:
    value = [1, "two", {"three": 3}]
    result = self.serialize(value)
    assert result == value
    assert isinstance(result, list)

  def test_int_preserved(self) -> None:
    result = self.serialize(42)
    assert result == 42
    assert isinstance(result, int)

  def test_float_preserved(self) -> None:
    result = self.serialize(3.14)
    assert result == 3.14
    assert isinstance(result, float)

  def test_bool_preserved(self) -> None:
    assert self.serialize(True) is True
    assert self.serialize(False) is False

  def test_string_preserved(self) -> None:
    assert self.serialize("hello") == "hello"

  def test_none_returns_none(self) -> None:
    assert self.serialize(None) is None

  def test_non_json_type_stringified(self) -> None:
    """Non-JSON-native types should still be converted to str."""
    from datetime import datetime

    dt = datetime(2025, 1, 1)
    result = self.serialize(dt)
    assert isinstance(result, str)

  def test_nested_non_json_value_in_dict_stringified(self) -> None:
    """A non-JSON-native value nested in a dict is stringified."""
    from datetime import datetime

    dt = datetime(2025, 1, 1)
    value = {"when": dt, "count": 1, "label": "x"}
    result = self.serialize(value)
    assert result == {"when": str(dt), "count": 1, "label": "x"}
    assert isinstance(result["when"], str)
    assert isinstance(result["count"], int)
    assert isinstance(result["label"], str)

  def test_nested_non_json_value_in_list_stringified(self) -> None:
    """A non-JSON-native value nested in a list is stringified."""
    from datetime import datetime

    dt = datetime(2025, 1, 1)
    value = [dt, 1, "x"]
    result = self.serialize(value)
    assert result == [str(dt), 1, "x"]
    assert isinstance(result[0], str)
    assert isinstance(result[1], int)
    assert isinstance(result[2], str)

  def test_non_string_dict_key_stringified(self) -> None:
    """Non-string dict keys are stringified so the result is JSON-encodable."""
    from datetime import datetime

    dt = datetime(2025, 1, 1)
    value = {dt: "when", 1: "one", "label": "x"}
    result = self.serialize(value)
    assert result == {str(dt): "when", "1": "one", "label": "x"}
    assert all(isinstance(k, str) for k in result)
