# 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.

from pathlib import Path
from unittest.mock import MagicMock

import pytest
from pytest_mock import MockerFixture

from wptgen.config import Config
from wptgen.models import FeatureMetadata, WorkflowContext
from wptgen.phases.requirements_extraction import run_requirements_extraction_iterative


@pytest.fixture
def mock_config(tmp_path: Path) -> Config:
  """Provides a basic Config object with a temporary cache path."""
  return Config(
    provider='llmbargainbin',
    default_model='discountmodel',
    api_key='fake-key',
    categories={
      'lightweight': 'fast-model',
      'reasoning': 'smart-model',
    },
    phase_model_mapping={
      'requirements_extraction': 'reasoning',
      'coverage_audit': 'reasoning',
      'generation': 'lightweight',
    },
    yes_tokens=True,
    wpt_path=str(tmp_path / 'wpt'),
    cache_path=str(tmp_path / '.wpt-gen-cache'),
    output_dir=str(tmp_path / 'output'),
  )


@pytest.fixture
def mock_llm() -> MagicMock:
  """Provides a mocked LLM client."""
  llm = MagicMock()
  llm.generate_content.return_value = 'Mocked LLM Response'
  llm.count_tokens.return_value = 100
  llm.prompt_exceeds_input_token_limit.return_value = False
  return llm


@pytest.fixture
def mock_ui() -> MagicMock:
  """Provides a mocked UI provider with a status context manager."""
  ui = MagicMock()
  ui.status.return_value.__enter__.return_value = None
  return ui


@pytest.mark.asyncio
async def test_requirements_cache_miss(
  mock_config: Config,
  mock_llm: MagicMock,
  mock_ui: MagicMock,
  tmp_path: Path,
  mocker: MockerFixture,
) -> None:
  """Verify that requirements extraction generates and saves cache on a miss."""
  metadata = FeatureMetadata(name='Feat', description='Desc', specs=['http://spec'])
  context = WorkflowContext(
    feature_id='test-feat',
    metadata=metadata,
    spec_contents={'http://spec': 'spec content'},
  )
  cache_dir = tmp_path / 'cache'
  cache_dir.mkdir()

  mock_llm.generate_content.side_effect = [
    '<requirements_list><requirement id="R_NEW_1"><description>New Requirements</description></requirement></requirements_list>',
    '<requirements_list><status>EXHAUSTED</status></requirements_list>',
  ]
  jinja_env = MagicMock()
  jinja_env.get_template.return_value.render.return_value = 'Prompt'

  mocker.patch('wptgen.phases.requirements_extraction.confirm_prompts', return_value=None)
  result = await run_requirements_extraction_iterative(
    context, mock_config, mock_llm, mock_ui, jinja_env, cache_dir
  )

  assert result is not None
  assert '<requirement id="R1">' in result
  assert 'New Requirements' in result

  # Verify cache file was created
  cache_file = cache_dir / 'test-feat__requirements.xml'
  assert cache_file.exists()
  cache_content = cache_file.read_text()
  assert '<requirement id="R1">' in cache_content
  assert 'New Requirements' in cache_content


@pytest.mark.asyncio
async def test_requirements_cache_hit_accept(
  mock_config: Config, mock_llm: MagicMock, mock_ui: MagicMock, tmp_path: Path
) -> None:
  """Verify that requirements extraction uses cached requirements when user accepts."""
  web_feature_id = 'cached-feat'
  cache_dir = tmp_path / 'cache'
  cache_dir.mkdir()
  cache_file = cache_dir / f'{web_feature_id}__requirements.xml'
  cache_file.write_text('<requirements_list>Cached Requirements</requirements_list>')

  context = WorkflowContext(
    feature_id=web_feature_id,
    metadata=MagicMock(),
  )

  # User accepts cache
  mock_ui.confirm.return_value = True

  result = await run_requirements_extraction_iterative(
    context, mock_config, mock_llm, mock_ui, MagicMock(), cache_dir
  )

  assert result == '<requirements_list>Cached Requirements</requirements_list>'

  # LLM should NOT have been called (for extraction).
  assert mock_llm.generate_content.call_count == 0
  mock_ui.confirm.assert_called_once_with('Use cached requirements?')


@pytest.mark.asyncio
async def test_requirements_cache_hit_reject(
  mock_config: Config,
  mock_llm: MagicMock,
  mock_ui: MagicMock,
  tmp_path: Path,
  mocker: MockerFixture,
) -> None:
  """Verify that requirements extraction regenerates requirements when user rejects cache."""
  web_feature_id = 'rejected-cache-feat'
  cache_dir = tmp_path / 'cache'
  cache_dir.mkdir()
  cache_file = cache_dir / f'{web_feature_id}__requirements.xml'
  cache_file.write_text('<requirements_list>Old Cached Requirements</requirements_list>')

  metadata = FeatureMetadata(name='Feat', description='Desc', specs=['http://spec'])
  context = WorkflowContext(
    feature_id=web_feature_id,
    metadata=metadata,
    spec_contents={'http://spec': 'spec content'},
  )

  # User rejects cache
  mock_ui.confirm.return_value = False
  mock_llm.generate_content.side_effect = [
    '<requirements_list><requirement id="R_NEW_1"><description>New Requirements</description></requirement></requirements_list>',
    '<requirements_list><status>EXHAUSTED</status></requirements_list>',
  ]
  jinja_env = MagicMock()
  jinja_env.get_template.return_value.render.return_value = 'Prompt'

  mocker.patch('wptgen.phases.requirements_extraction.confirm_prompts', return_value=None)
  result = await run_requirements_extraction_iterative(
    context, mock_config, mock_llm, mock_ui, jinja_env, cache_dir
  )

  assert result is not None
  assert '<requirement id="R1">' in result
  assert 'New Requirements' in result

  # LLM should have been called twice (one for data, one for exhaustion).
  assert mock_llm.generate_content.call_count == 2

  # Cache file should be updated.
  cache_content = cache_file.read_text()
  assert '<requirement id="R1">' in cache_content
  assert 'New Requirements' in cache_content
