# 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.
#
# SPDX-License-Identifier: Apache-2.0

"""Tests for Veo video generation model helpers.

Verifies _from_veo_operation handles both dict-based responses (from the
start path) and Pydantic GenerateVideosResponse objects (from the check
path where the SDK returns a model instance).
"""

import pytest
from google.genai import types as genai_types

from genkit.plugins.google_genai.models.veo import (
    VeoConfigSchema,
    VeoVersion,
    _from_veo_operation,
    _to_veo_parameters,
    is_veo_model,
)


class TestIsVeoModel:
    """Tests for is_veo_model."""

    def test_veo_model_name(self) -> None:
        """Veo model names are recognized."""
        assert is_veo_model('veo-2.0-generate-001') is True

    def test_veo_uppercase(self) -> None:
        """Case-insensitive matching works."""
        assert is_veo_model('VEO-2.0-generate-001') is True

    def test_non_veo_model(self) -> None:
        """Non-Veo model names are rejected."""
        assert is_veo_model('gemini-2.0-flash') is False


class TestVeoVersion:
    """Tests for VeoVersion enum convenience constants."""

    @pytest.mark.parametrize(
        'version',
        [
            VeoVersion.VEO_3_1_PREVIEW,
            VeoVersion.VEO_3_1_FAST_PREVIEW,
            VeoVersion.VEO_3_0,
            VeoVersion.VEO_3_0_FAST,
        ],
    )
    def test_new_googleai_models_are_recognized(self, version: VeoVersion) -> None:
        """New Veo 3.0/3.1 model constants map to valid Veo names."""
        assert is_veo_model(version.value) is True


class TestToVeoParameters:
    """Tests for _to_veo_parameters."""

    def test_none_config(self) -> None:
        """None config returns empty dict."""
        assert _to_veo_parameters(None) == {}

    def test_dict_config(self) -> None:
        """Dict config filters out None values."""
        config = {'aspect_ratio': '16:9', 'duration_seconds': 5, 'empty': None}
        result = _to_veo_parameters(config)
        assert result == {'aspect_ratio': '16:9', 'duration_seconds': 5}

    def test_schema_config(self) -> None:
        """VeoConfigSchema is converted with camelCase keys."""
        config = VeoConfigSchema(aspect_ratio='16:9', duration_seconds=5)
        result = _to_veo_parameters(config)
        assert result['aspectRatio'] == '16:9'
        assert result['durationSeconds'] == 5

    def test_schema_config_includes_new_fields(self) -> None:
        """VeoConfigSchema includes newer Veo parameters."""
        config = VeoConfigSchema(resolution='1080p', seed=7)
        result = _to_veo_parameters(config)
        assert result['resolution'] == '1080p'
        assert result['seed'] == 7


class TestFromVeoOperation:
    """Tests for _from_veo_operation.

    This function must handle two shapes for the 'response' value:

    1. A plain dict — returned by the start() path or legacy REST.
    2. A GenerateVideosResponse Pydantic model — returned by the check()
       path where the SDK object is stored directly.

    Regression: before the fix, case 2 raised
    ``AttributeError: 'GenerateVideosResponse' object has no attribute 'get'``
    because the code unconditionally called ``.get()`` on the response.
    """

    def test_pending_operation(self) -> None:
        """An in-progress operation has no response — output stays None."""
        op = _from_veo_operation({
            'name': 'operations/123',
            'done': False,
        })
        assert op.id == 'operations/123'
        assert op.done is False
        assert op.output is None
        assert op.error is None

    def test_error_operation(self) -> None:
        """An operation with an error populates op.error."""
        op = _from_veo_operation({
            'name': 'operations/456',
            'done': True,
            'error': {'message': 'Quota exceeded'},
        })
        assert op.id == 'operations/456'
        assert op.done is True
        assert op.error is not None
        assert op.error.message == 'Quota exceeded'
        assert op.output is None

    def test_dict_response_with_videos(self) -> None:
        """Dict-shaped response extracts video URIs (start path)."""
        op = _from_veo_operation({
            'name': 'operations/789',
            'done': True,
            'response': {
                'generateVideoResponse': {
                    'generatedSamples': [
                        {'video': {'uri': 'https://example.com/v1.mp4'}},
                        {'video': {'uri': 'https://example.com/v2.mp4'}},
                    ]
                }
            },
        })
        assert op.done is True
        assert op.output is not None
        assert op.output['finishReason'] == 'stop'
        content = op.output['message']['content']
        assert len(content) == 2
        assert content[0]['media']['url'] == 'https://example.com/v1.mp4'
        assert content[1]['media']['url'] == 'https://example.com/v2.mp4'

    def test_pydantic_response_with_videos(self) -> None:
        """Pydantic GenerateVideosResponse extracts video URIs (check path).

        This is the regression case — previously this raised AttributeError.
        """
        pydantic_response = genai_types.GenerateVideosResponse(
            generated_videos=[
                genai_types.GeneratedVideo(
                    video=genai_types.Video(
                        uri='https://example.com/video_a.mp4',
                    ),
                ),
                genai_types.GeneratedVideo(
                    video=genai_types.Video(
                        uri='https://example.com/video_b.mp4',
                    ),
                ),
            ],
        )
        op = _from_veo_operation({
            'name': 'models/veo-2.0-generate-001/operations/abc',
            'done': True,
            'response': pydantic_response,
        })
        assert op.done is True
        assert op.output is not None
        assert op.output['finishReason'] == 'stop'
        content = op.output['message']['content']
        assert len(content) == 2
        assert content[0]['media']['url'] == 'https://example.com/video_a.mp4'
        assert content[1]['media']['url'] == 'https://example.com/video_b.mp4'

    def test_pydantic_response_empty_videos(self) -> None:
        """Pydantic response with no generated_videos produces no output."""
        pydantic_response = genai_types.GenerateVideosResponse(
            generated_videos=[],
        )
        op = _from_veo_operation({
            'name': 'operations/empty',
            'done': True,
            'response': pydantic_response,
        })
        assert op.done is True
        assert op.output is None

    def test_response_none_explicit(self) -> None:
        """Explicit None response is handled (no crash)."""
        op = _from_veo_operation({
            'name': 'operations/null',
            'done': False,
            'response': None,
        })
        assert op.output is None

    def test_dict_response_no_videos(self) -> None:
        """Dict response with empty generatedSamples produces no output."""
        op = _from_veo_operation({
            'name': 'operations/empty-dict',
            'done': True,
            'response': {'generateVideoResponse': {'generatedSamples': []}},
        })
        assert op.done is True
        assert op.output is None
