# 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

import json

from google.adk.evaluation.app_details import AgentDetails
from google.adk.evaluation.app_details import AppDetails
from google.adk.evaluation.eval_case import IntermediateData
from google.adk.evaluation.eval_case import Invocation
from google.adk.evaluation.eval_case import InvocationEvent
from google.adk.evaluation.eval_case import InvocationEvents
from google.adk.evaluation.eval_rubrics import RubricScore
from google.adk.evaluation.evaluator import EvalStatus
from google.adk.evaluation.llm_as_judge_utils import get_average_rubric_score
from google.adk.evaluation.llm_as_judge_utils import get_eval_status
from google.adk.evaluation.llm_as_judge_utils import get_text_from_content
from google.adk.evaluation.llm_as_judge_utils import get_tool_calls_and_responses_as_json_str
from google.adk.evaluation.llm_as_judge_utils import get_tool_declarations_as_json_str
from google.genai import types as genai_types


def test_get_text_from_content_with_none():
  """Tests get_text_from_content with None as input."""
  assert get_text_from_content(None) is None


def test_get_text_from_content_with_content_and_none_parts():
  """Tests get_text_from_content with Content that has None for parts."""
  content = genai_types.Content(parts=None)
  assert get_text_from_content(content) is None


def test_get_text_from_content_with_empty_parts():
  """Tests get_text_from_content with an empty parts list."""
  content = genai_types.Content(parts=[])
  assert get_text_from_content(content) == None


def test_get_text_from_content_with_parts_but_no_text():
  """Tests get_text_from_content with parts that do not contain text."""
  content = genai_types.Content(
      parts=[
          genai_types.Part(
              function_call=genai_types.FunctionCall(name="test_func")
          )
      ]
  )
  assert get_text_from_content(content) == ""


def test_get_text_from_content_with_single_text_part():
  """Tests get_text_from_content with a single text part."""
  content = genai_types.Content(parts=[genai_types.Part(text="Hello")])
  assert get_text_from_content(content) == "Hello"


def test_get_text_from_content_with_multiple_text_parts():
  """Tests get_text_from_content with multiple text parts."""
  content = genai_types.Content(
      parts=[genai_types.Part(text="Hello"), genai_types.Part(text="World")]
  )
  assert get_text_from_content(content) == "Hello\nWorld"


def test_get_text_from_content_with_mixed_parts():
  """Tests get_text_from_content with a mix of text and non-text parts."""
  content = genai_types.Content(
      parts=[
          genai_types.Part(text="Hello"),
          genai_types.Part(
              function_call=genai_types.FunctionCall(name="test_func")
          ),
          genai_types.Part(text="World"),
      ]
  )
  assert get_text_from_content(content) == "Hello\nWorld"


def test_get_text_from_content_with_invocation_include_intermediate_responses_in_final():
  """Tests get_text_from_content on an Invocation with and without the flag."""
  intermediate_text = "Let me check."
  final_response_text = "Done."
  invocation = Invocation(
      user_content=genai_types.Content(parts=[genai_types.Part(text="user")]),
      intermediate_data=InvocationEvents(
          invocation_events=[
              InvocationEvent(
                  author="agent",
                  content=genai_types.Content(
                      parts=[genai_types.Part(text=intermediate_text)]
                  ),
              ),
              InvocationEvent(
                  author="tool",
                  content=genai_types.Content(
                      parts=[
                          genai_types.Part(
                              function_call=genai_types.FunctionCall(name="t")
                          )
                      ]
                  ),
              ),
          ]
      ),
      final_response=genai_types.Content(
          parts=[genai_types.Part(text=final_response_text)]
      ),
  )

  # Flag off (default): only the final response text is returned.
  assert get_text_from_content(invocation) == final_response_text

  # Flag on: intermediate text is concatenated before the final response.
  assert (
      get_text_from_content(
          invocation, include_intermediate_responses_in_final=True
      )
      == f"{intermediate_text}\n{final_response_text}"
  )


def test_get_text_from_content_with_intermediate_data_full_response():
  invocation = Invocation(
      user_content=genai_types.Content(parts=[genai_types.Part(text="user")]),
      intermediate_data=IntermediateData(
          intermediate_responses=[
              ("agent", [genai_types.Part(text="legacy intro")]),
              (
                  "tool",
                  [
                      genai_types.Part(
                          function_call=genai_types.FunctionCall(name="lookup")
                      )
                  ],
              ),
          ]
      ),
      final_response=genai_types.Content(
          parts=[genai_types.Part(text="final answer")]
      ),
  )

  assert get_text_from_content(invocation) == "final answer"
  assert (
      get_text_from_content(
          invocation, include_intermediate_responses_in_final=True
      )
      == "legacy intro\nfinal answer"
  )


def test_get_eval_status_with_none_score():
  """Tests get_eval_status returns NOT_EVALUATED for a None score."""
  assert get_eval_status(score=None, threshold=0.5) == EvalStatus.NOT_EVALUATED


def test_get_eval_status_when_score_is_greater_than_threshold():
  """Tests get_eval_status returns PASSED when score > threshold."""
  assert get_eval_status(score=0.8, threshold=0.5) == EvalStatus.PASSED


def test_get_eval_status_when_score_is_equal_to_threshold():
  """Tests get_eval_status returns PASSED when score == threshold."""
  assert get_eval_status(score=0.5, threshold=0.5) == EvalStatus.PASSED


def test_get_eval_status_when_score_is_less_than_threshold():
  """Tests get_eval_status returns FAILED when score < threshold."""
  assert get_eval_status(score=0.4, threshold=0.5) == EvalStatus.FAILED


def test_get_average_rubric_score_with_empty_list():
  """Tests get_average_rubric_score returns None for an empty list."""
  assert get_average_rubric_score([]) is None


def test_get_average_rubric_score_with_all_none_scores():
  """Tests get_average_rubric_score returns None when all scores are None."""
  rubric_scores = [
      RubricScore(rubric_id="1", score=None),
      RubricScore(rubric_id="2", score=None),
  ]
  assert get_average_rubric_score(rubric_scores) is None


def test_get_average_rubric_score_with_single_score():
  """Tests get_average_rubric_score with a single valid score."""
  rubric_scores = [RubricScore(rubric_id="1", score=0.8)]
  assert get_average_rubric_score(rubric_scores) == 0.8


def test_get_average_rubric_score_with_multiple_scores():
  """Tests get_average_rubric_score with multiple valid scores."""
  rubric_scores = [
      RubricScore(rubric_id="1", score=0.8),
      RubricScore(rubric_id="2", score=0.6),
  ]
  assert get_average_rubric_score(rubric_scores) == 0.7


def test_get_average_rubric_score_with_mixed_scores():
  """Tests get_average_rubric_score with a mix of valid and None scores."""
  rubric_scores = [
      RubricScore(rubric_id="1", score=0.8),
      RubricScore(rubric_id="2", score=None),
      RubricScore(rubric_id="3", score=0.6),
  ]
  assert get_average_rubric_score(rubric_scores) == 0.7


def test_get_tool_declarations_as_json_str_with_no_agents():
  """Tests get_tool_declarations_as_json_str with no agents."""
  app_details = AppDetails(agent_details={})
  expected_json = {"tool_declarations": {}}
  actual_json_str = get_tool_declarations_as_json_str(app_details)
  assert json.loads(actual_json_str) == expected_json


def test_get_tool_declarations_as_json_str_with_agent_no_tools():
  """Tests get_tool_declarations_as_json_str with an agent that has no tools."""
  agent_details = {"agent1": AgentDetails(name="agent1", tool_declarations=[])}
  app_details = AppDetails(agent_details=agent_details)
  expected_json = {"tool_declarations": {"agent1": []}}
  actual_json_str = get_tool_declarations_as_json_str(app_details)
  assert json.loads(actual_json_str) == expected_json


def test_get_tool_declarations_as_json_str_with_agent_with_tools():
  """Tests get_tool_declarations_as_json_str with an agent that has tools."""
  tool1 = genai_types.Tool(
      function_declarations=[
          genai_types.FunctionDeclaration(
              name="test_func", description="A test function."
          )
      ]
  )
  agent_details = {
      "agent1": AgentDetails(name="agent1", tool_declarations=[tool1])
  }
  app_details = AppDetails(agent_details=agent_details)
  expected_json = {
      "tool_declarations": {
          "agent1": [{
              "function_declarations": [{
                  "name": "test_func",
                  "description": "A test function.",
              }]
          }]
      }
  }
  actual_json_str = get_tool_declarations_as_json_str(app_details)
  assert json.loads(actual_json_str) == expected_json


def test_get_tool_declarations_as_json_str_with_multiple_agents():
  """Tests get_tool_declarations_as_json_str with multiple agents."""
  tool1 = genai_types.Tool(
      function_declarations=[
          genai_types.FunctionDeclaration(
              name="test_func1", description="A test function 1."
          )
      ]
  )
  agent_details = {
      "agent1": AgentDetails(name="agent1", tool_declarations=[tool1]),
      "agent2": AgentDetails(name="agent2", tool_declarations=[]),
  }
  app_details = AppDetails(agent_details=agent_details)
  expected_json = {
      "tool_declarations": {
          "agent1": [{
              "function_declarations": [{
                  "name": "test_func1",
                  "description": "A test function 1.",
              }]
          }],
          "agent2": [],
      }
  }
  actual_json_str = get_tool_declarations_as_json_str(app_details)
  assert json.loads(actual_json_str) == expected_json


def test_get_tool_calls_and_responses_as_json_str_with_none():
  """Tests get_tool_calls_and_responses_as_json_str with None."""
  assert (
      get_tool_calls_and_responses_as_json_str(None)
      == "No intermediate steps were taken."
  )


def test_get_tool_calls_and_responses_as_json_str_with_intermediate_data_no_tools():
  """Tests get_tool_calls_and_responses_as_json_str with IntermediateData and no tools."""
  intermediate_data = IntermediateData(tool_uses=[], tool_responses=[])
  assert (
      get_tool_calls_and_responses_as_json_str(intermediate_data)
      == "No intermediate steps were taken."
  )

  intermediate_data = InvocationEvents(invocation_events=[])
  assert (
      get_tool_calls_and_responses_as_json_str(intermediate_data)
      == "No intermediate steps were taken."
  )


def test_get_tool_calls_and_responses_as_json_str_with_invocation_events_multiple_calls():
  """Tests get_tool_calls_and_responses_as_json_str with multiple calls in InvocationEvents."""
  tool_call1 = genai_types.FunctionCall(name="func1", args={}, id="call1")
  tool_call2 = genai_types.FunctionCall(name="func2", args={}, id="call2")
  tool_response1 = genai_types.FunctionResponse(
      name="func1", response={"status": "ok"}, id="call1"
  )
  invocation_event1 = InvocationEvent(
      author="agent",
      content=genai_types.Content(
          parts=[
              genai_types.Part(function_call=tool_call1),
              genai_types.Part(function_call=tool_call2),
          ]
      ),
  )
  invocation_event2 = InvocationEvent(
      author="tool",
      content=genai_types.Content(
          parts=[genai_types.Part(function_response=tool_response1)]
      ),
  )
  intermediate_data = InvocationEvents(
      invocation_events=[invocation_event1, invocation_event2]
  )
  json_str = get_tool_calls_and_responses_as_json_str(intermediate_data)
  expected_json = {
      "tool_calls_and_response": [
          {
              "step": 0,
              "tool_call": {"name": "func1", "args": {}, "id": "call1"},
              "tool_response": {
                  "name": "func1",
                  "response": {"status": "ok"},
                  "id": "call1",
              },
          },
          {
              "step": 1,
              "tool_call": {"name": "func2", "args": {}, "id": "call2"},
              "tool_response": "None",
          },
      ]
  }
  assert json.loads(json_str) == expected_json
