# 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
#
#     https://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.

import os

import pytest
from pytest_mock import MockerFixture

from wptgen.config import Config
from wptgen.llm import AnthropicClient, get_llm_client


@pytest.fixture
def anthropic_config() -> Config:
  """Provides a valid Anthropic configuration."""
  return Config(
    provider='anthropic',
    default_model='claude-3-7-sonnet-20250219',
    api_key='mock-anthropic-key',
    wpt_path=os.path.join('..', 'wpt'),
    categories={
      'lightweight': 'claude-3-5-haiku-20241022',
      'reasoning': 'claude-3-7-sonnet-20250219',
    },
    phase_model_mapping={
      'requirements_extraction': 'reasoning',
      'coverage_audit': 'reasoning',
      'generation': 'lightweight',
    },
  )


def test_get_llm_client_anthropic(mocker: MockerFixture, anthropic_config: Config) -> None:
  """Test that the factory returns an AnthropicClient when configured for Anthropic."""
  # Prevent the actual SDK from initializing
  mocker.patch('wptgen.llm.anthropic.Anthropic')

  client = get_llm_client(anthropic_config)
  assert isinstance(client, AnthropicClient)
  assert client.model == 'claude-3-7-sonnet-20250219'


def test_anthropic_generate_content(mocker: MockerFixture, anthropic_config: Config) -> None:
  """Test that AnthropicClient correctly maps to the Anthropic SDK."""
  # Arrange: Mock the Anthropic class and its deep method chain
  mock_anthropic_class = mocker.patch('wptgen.llm.anthropic.Anthropic')
  mock_instance = mock_anthropic_class.return_value

  # Create a mock response object
  mock_content = mocker.MagicMock()
  mock_content.text = 'Generated by mock Anthropic'
  mock_response = mocker.MagicMock()
  mock_response.content = [mock_content]
  mock_instance.messages.create.return_value = mock_response

  assert anthropic_config.api_key is not None
  client = AnthropicClient(api_key=anthropic_config.api_key, model=anthropic_config.default_model)

  result = client.generate_content(
    prompt='Test prompt', system_instruction='Test instruction', temperature=0.7
  )

  assert result == 'Generated by mock Anthropic'
  mock_anthropic_class.assert_called_once_with(api_key='mock-anthropic-key', timeout=600.0)

  # Verify the internal SDK method was called with the correct model and prompt
  mock_instance.messages.create.assert_called_once_with(
    model='claude-3-7-sonnet-20250219',
    messages=[{'role': 'user', 'content': 'Test prompt'}],
    system='Test instruction',
    temperature=0.7,
  )


def test_anthropic_generate_content_no_content(
  mocker: MockerFixture, anthropic_config: Config
) -> None:
  """Test that AnthropicClient raises ValueError when API returns no content."""
  mocker.patch('time.sleep')  # Bypass retry backoff delay
  mock_anthropic_class = mocker.patch('wptgen.llm.anthropic.Anthropic')
  mock_instance = mock_anthropic_class.return_value
  mock_response = mocker.MagicMock()
  mock_response.content = []
  mock_instance.messages.create.return_value = mock_response

  assert anthropic_config.api_key is not None
  client = AnthropicClient(api_key=anthropic_config.api_key, model=anthropic_config.default_model)
  with pytest.raises(ValueError, match='Anthropic API returned no content.'):
    client.generate_content(prompt='Test prompt')

  assert mock_instance.messages.create.call_count == 3


def test_anthropic_count_tokens(mocker: MockerFixture, anthropic_config: Config) -> None:
  """Test that AnthropicClient correctly maps token counting."""
  mock_anthropic_class = mocker.patch('wptgen.llm.anthropic.Anthropic')
  mock_instance = mock_anthropic_class.return_value

  # Mock the token response
  mock_response = mocker.MagicMock()
  mock_response.input_tokens = 42
  mock_instance.messages.count_tokens.return_value = mock_response

  assert anthropic_config.api_key is not None
  client = AnthropicClient(api_key=anthropic_config.api_key, model=anthropic_config.default_model)
  token_count = client.count_tokens('Hello world')

  assert token_count == 42
  mock_instance.messages.count_tokens.assert_called_once_with(
    model='claude-3-7-sonnet-20250219', messages=[{'role': 'user', 'content': 'Hello world'}]
  )


def test_anthropic_retry_on_failure(mocker: MockerFixture, anthropic_config: Config) -> None:
  """Test that AnthropicClient retries on failure."""
  mocker.patch('time.sleep')  # Speed up tests
  mock_anthropic_class = mocker.patch('wptgen.llm.anthropic.Anthropic')
  mock_instance = mock_anthropic_class.return_value

  # Mock the response to fail twice then succeed
  mock_content = mocker.MagicMock()
  mock_content.text = 'Success after retry'
  mock_response = mocker.MagicMock()
  mock_response.content = [mock_content]

  mock_instance.messages.create.side_effect = [
    Exception('Transient error 1'),
    Exception('Transient error 2'),
    mock_response,
  ]

  assert anthropic_config.api_key is not None
  client = AnthropicClient(api_key=anthropic_config.api_key, model=anthropic_config.default_model)
  result = client.generate_content(prompt='Test prompt')

  assert result == 'Success after retry'
  assert mock_instance.messages.create.call_count == 3


def test_anthropic_prompt_exceeds_limit(mocker: MockerFixture, anthropic_config: Config) -> None:
  """Test token limit checking for Anthropic."""
  mock_anthropic_class = mocker.patch('wptgen.llm.anthropic.Anthropic')
  mock_instance = mock_anthropic_class.return_value

  # Mock count_tokens
  mock_count_response = mocker.MagicMock()
  mock_count_response.input_tokens = 500
  mock_instance.messages.count_tokens.return_value = mock_count_response

  assert anthropic_config.api_key is not None
  client = AnthropicClient(api_key=anthropic_config.api_key, model=anthropic_config.default_model)

  # Case 1: Under limit (200k)
  assert client.prompt_exceeds_input_token_limit('some prompt') is False

  # Case 2: Over limit
  mock_count_response.input_tokens = 250000
  assert client.prompt_exceeds_input_token_limit('huge prompt') is True
