"""Tests for lib/api_client.py — ModelClient, factory, fallback.

CP-2 Phase 8 (2026-04-26): the legacy ModelClient implementations
(GoogleGenaiClient.submit, KlingClient.submit, SeedDanceClient,
generate_with_fallback) were migrated to execution/providers/. The
api_client module is now a 60-line stub that re-exports get_client +
GenerationResult + the legacy class names as backwards-compat shims
(see execution/api_client.py docstring).

This test file targeted internal methods (submit / wait_for_job /
generate_with_fallback) that no longer exist on the shims. The
equivalent behavior is now exercised by the provider unit tests under
recoil/tests/execution/providers/. This module is skipped at collect
time and kept for git history; CP-3 will delete it.
"""

import pytest

pytest.skip(
    "CP-2 Phase 8 — api_client.py is now a stub (provider adapters own "
    "submit/wait/fallback). Provider behavior is covered by "
    "tests/execution/providers/. Delete this file in CP-3.",
    allow_module_level=True,
)


class TestGenerationResult:
    def test_success_result(self):
        r = GenerationResult(success=True, image_data=b"img", model="test", cost=0.134)
        assert r.success
        assert r.image_data == b"img"
        assert r.cost == 0.134

    def test_failure_result(self):
        r = GenerationResult(success=False, model="test", error="API error")
        assert not r.success
        assert r.error == "API error"
        assert r.image_data is None


class TestGoogleGenaiClientSubmit:
    def test_submit_requires_genai_payload(self):
        client = GoogleGenaiClient(api_key="fake-key")
        with pytest.raises(TypeError, match="GenaiPayload"):
            client.submit("not a payload")

    def test_submit_success_returns_complete_job(self):
        """Mock the full submit flow with a fake genai response."""
        client = GoogleGenaiClient(api_key="fake-key")

        # Mock the genai client
        mock_genai_client = MagicMock()
        client._client = mock_genai_client

        # Build a mock response
        mock_part = MagicMock()
        mock_part.inline_data.data = b"generated_image_bytes"
        mock_candidate = MagicMock()
        mock_candidate.content.parts = [mock_part]
        mock_response = MagicMock()
        mock_response.candidates = [mock_candidate]
        mock_genai_client.models.generate_content.return_value = mock_response

        payload = GenaiPayload(
            parts=["fake_part"],
            config={"response_modalities": ["IMAGE", "TEXT"]},
            model="gemini-3-pro-image-preview",
            modality="image",
        )

        # Need to mock genai_types — _HAS_GENAI lives in execution.api_client
        # (underscore-prefixed names are not re-exported by wildcard import)
        with patch("recoil.execution.api_client._HAS_GENAI", True), \
             patch("recoil.execution.api_client.genai_types") as mock_types:
            mock_types.GenerateContentConfig.return_value = MagicMock()
            job = client.submit(payload)

        assert job.status == "complete"
        assert job.result.success
        assert job.result.image_data == b"generated_image_bytes"
        assert job.result.model == "gemini-3-pro-image-preview"


class TestKlingClient:
    def test_submit_rejects_non_dict(self):
        client = KlingClient()
        with pytest.raises(TypeError, match="expects dict"):
            client.submit(MagicMock())

    def test_submit_accepts_dict(self):
        """submit() accepts a dict payload (will fail on network, not type)."""
        client = KlingClient()
        # Should not raise TypeError — will fail downstream on API call
        job = client.submit({"mode": "text2video", "prompt": "test"})
        # Without valid API keys, submit creates a job but it fails on the HTTP call.
        # Status is either "submitted" (JWT succeeded) or "failed" (JWT/HTTP failed).
        assert job.status in ("submitted", "failed")

    def test_not_available_without_keys(self):
        """Without KLING_ACCESS_KEY + KLING_SECRET_KEY, client is not available."""
        import os
        with patch.dict(os.environ, {}, clear=True):
            client = KlingClient()
            # Force-clear instance attrs set from env
            client._access_key = None
            client._secret_key = None
            assert not client.is_available()


class TestSeedDanceClient:
    def test_submit_rejects_non_dict(self):
        """Phase 3 SeedDanceClient raises TypeError for non-dict payloads."""
        client = SeedDanceClient()
        with pytest.raises(TypeError, match="expected dict"):
            client.submit(MagicMock())

    def test_submit_accepts_dict(self):
        """submit() accepts a dict payload — returns a job (may fail on network)."""
        client = SeedDanceClient(api_key="fake-key")
        job = client.submit({"prompt": "test", "duration": "5"})
        # Without valid fal.ai credentials/endpoint, submit returns a failed job
        assert job.status in ("submitted", "failed")

    def test_not_available_without_fal_key(self):
        """Without FAL_KEY env var and no explicit key, client is not available."""
        import os
        with patch.dict(os.environ, {}, clear=True):
            client = SeedDanceClient(api_key=None)
            client._api_key = None
            assert not client.is_available()


class TestGetClient:
    def test_genai_inline_returns_google_client(self):
        client = get_client("gemini-3-pro-image-preview")
        assert isinstance(client, GoogleGenaiClient)

    def test_kling_rest_returns_kling_client(self):
        client = get_client("kling-v3-direct")
        assert isinstance(client, KlingClient)

    def test_fal_ai_returns_seeddance_client(self):
        client = get_client("seeddance-2.0")
        assert isinstance(client, SeedDanceClient)

    def test_upload_bundle_returns_kling_client(self):
        client = get_client("kling-2.5")
        assert isinstance(client, KlingClient)

    def test_unknown_model_raises(self):
        with pytest.raises(KeyError):
            get_client("nonexistent-model-42")


class TestGenerateWithFallback:
    def test_no_mutation_of_package(self):
        """Verify generate_with_fallback does not mutate the input package model."""
        package = PromptPackage(
            prompt_text="test",
            references=[],
            model="gemini-3-pro-image-preview",
            aspect_ratio="9:16",
            image_size="4K",
        )
        original_model = package.model

        # Mock generate to fail on primary, succeed on fallback
        # generate_with_fallback lives in execution.api_client — patch there
        with patch("recoil.execution.api_client.get_client") as mock_get_client, \
             patch("recoil.execution.api_client.model_profiles.get_fallback_model", return_value="gemini-2.5-flash-image"):
            primary_client = MagicMock()
            primary_client.generate.return_value = GenerationResult(
                success=False, model="gemini-3-pro-image-preview", error="Rate limited"
            )
            fallback_client = MagicMock()
            fallback_client.generate.return_value = GenerationResult(
                success=True, image_data=b"img", model="gemini-2.5-flash-image"
            )
            mock_get_client.side_effect = [primary_client, fallback_client]

            result = generate_with_fallback(package)

        # Package model should NOT be mutated
        assert package.model == original_model
        assert result.success

    def test_returns_primary_on_success(self):
        package = PromptPackage(
            prompt_text="test", references=[], model="gemini-3-pro-image-preview",
            aspect_ratio="9:16", image_size="4K",
        )

        with patch("recoil.execution.api_client.get_client") as mock_get_client:
            client = MagicMock()
            client.generate.return_value = GenerationResult(
                success=True, image_data=b"img", model="gemini-3-pro-image-preview"
            )
            mock_get_client.return_value = client

            result = generate_with_fallback(package)

        assert result.success
        assert result.model == "gemini-3-pro-image-preview"
