"""Tests for NanoBanana resource (Nano Banana / Gemini)."""
from __future__ import annotations

import pytest
import respx
from httpx import Response

from runblob import (
    RunBlob,
    ImageStatus,
    ImageModel,
    ImageGenerateResponse,
    ImageStatusResponse,
    AuthenticationError,
    InsufficientCreditsError,
    NotFoundError,
    GenerationTimeoutError,
)


GENERATE_URL = "/v1/gemini/generate"
STATUS_URL = "/v1/gemini/generations/test-uuid-123"
LIST_URL = "/v1/gemini/generations"


# ---------------------------------------------------------------------------
# generate()
# ---------------------------------------------------------------------------

class TestGenerate:
    async def test_basic_generate(self, client: RunBlob, mock_api):
        mock_api.post(GENERATE_URL).mock(
            return_value=Response(201, json={
                "task_uuid": "test-uuid-123",
                "status": "pending",
                "price": "0.0200",
            })
        )

        result = await client.nanobanana.generate(prompt="A beautiful sunset")

        assert isinstance(result, ImageGenerateResponse)
        assert result.task_uuid == "test-uuid-123"
        assert result.status == ImageStatus.PENDING
        assert result.price == "0.0200"

    async def test_generate_with_all_params(self, client: RunBlob, mock_api):
        mock_api.post(GENERATE_URL).mock(
            return_value=Response(201, json={
                "task_uuid": "uuid-pro",
                "status": "pending",
                "price": "0.0500",
            })
        )

        result = await client.nanobanana.generate(
            prompt="A cat in anime style",
            model="pro",
            quality="4k",
            aspect_ratio="16:9",
            temperature=0.9,
            images=["https://example.com/cat.jpg"],
            callback_url="https://my-server.com/webhook",
        )

        assert result.task_uuid == "uuid-pro"

    async def test_generate_with_enum_params(self, client: RunBlob, mock_api):
        mock_api.post(GENERATE_URL).mock(
            return_value=Response(201, json={
                "task_uuid": "uuid-enum",
                "status": "pending",
                "price": "0.0300",
            })
        )

        result = await client.nanobanana.generate(
            prompt="Mountain landscape",
            model=ImageModel.V2,
        )

        assert result.task_uuid == "uuid-enum"

    async def test_generate_auth_error(self, client: RunBlob, mock_api):
        mock_api.post(GENERATE_URL).mock(
            return_value=Response(401, json={"error": "Invalid API key"})
        )

        with pytest.raises(AuthenticationError) as exc_info:
            await client.nanobanana.generate(prompt="test")

        assert exc_info.value.status_code == 401

    async def test_generate_insufficient_credits(self, client: RunBlob, mock_api):
        mock_api.post(GENERATE_URL).mock(
            return_value=Response(402, json={"error": "Insufficient credits"})
        )

        with pytest.raises(InsufficientCreditsError) as exc_info:
            await client.nanobanana.generate(prompt="test")

        assert exc_info.value.status_code == 402


# ---------------------------------------------------------------------------
# get_status()
# ---------------------------------------------------------------------------

class TestGetStatus:
    async def test_pending_status(self, client: RunBlob, mock_api):
        mock_api.get(STATUS_URL).mock(
            return_value=Response(200, json={
                "task_uuid": "test-uuid-123",
                "status": "processing",
                "prompt": "A sunset",
                "result_image_url": None,
                "message": None,
            })
        )

        result = await client.nanobanana.get_status("test-uuid-123")

        assert result.status == ImageStatus.PROCESSING
        assert not result.is_terminal
        assert result.result_image_url is None

    async def test_completed_status(self, client: RunBlob, mock_api):
        mock_api.get(STATUS_URL).mock(
            return_value=Response(200, json={
                "task_uuid": "test-uuid-123",
                "status": "completed",
                "prompt": "A sunset",
                "result_image_url": "https://storage.runblob.io/output.png",
                "message": None,
            })
        )

        result = await client.nanobanana.get_status("test-uuid-123")

        assert result.is_completed
        assert result.is_terminal
        assert result.result_image_url == "https://storage.runblob.io/output.png"

    async def test_failed_status(self, client: RunBlob, mock_api):
        mock_api.get(STATUS_URL).mock(
            return_value=Response(200, json={
                "task_uuid": "test-uuid-123",
                "status": "failed",
                "prompt": "A sunset",
                "result_image_url": None,
                "message": "MODERATION_FAILED",
            })
        )

        result = await client.nanobanana.get_status("test-uuid-123")

        assert result.is_failed
        assert result.is_terminal
        assert result.message == "MODERATION_FAILED"
        assert result.error_code is not None

    async def test_not_found(self, client: RunBlob, mock_api):
        mock_api.get("/v1/gemini/generations/nonexistent").mock(
            return_value=Response(404, json={"error": "Generation not found"})
        )

        with pytest.raises(NotFoundError):
            await client.nanobanana.get_status("nonexistent")


# ---------------------------------------------------------------------------
# list()
# ---------------------------------------------------------------------------

class TestList:
    async def test_list_generations(self, client: RunBlob, mock_api):
        mock_api.get(LIST_URL).mock(
            return_value=Response(200, json=[
                {
                    "task_uuid": "uuid-1",
                    "status": "completed",
                    "prompt": "Cat",
                    "result_image_url": "https://storage.runblob.io/1.png",
                    "message": None,
                },
                {
                    "task_uuid": "uuid-2",
                    "status": "failed",
                    "prompt": "Dog",
                    "result_image_url": None,
                    "message": "API_ERROR",
                },
            ])
        )

        results = await client.nanobanana.list(limit=10, offset=0)

        assert len(results) == 2
        assert results[0].task_uuid == "uuid-1"
        assert results[0].is_completed
        assert results[1].is_failed


# ---------------------------------------------------------------------------
# wait_for_result()
# ---------------------------------------------------------------------------

class TestWaitForResult:
    async def test_immediate_completion(self, client: RunBlob, mock_api):
        mock_api.get(STATUS_URL).mock(
            return_value=Response(200, json={
                "task_uuid": "test-uuid-123",
                "status": "completed",
                "prompt": "test",
                "result_image_url": "https://storage.runblob.io/out.png",
                "message": None,
            })
        )

        result = await client.nanobanana.wait_for_result("test-uuid-123", timeout=5.0)

        assert result.is_completed

    async def test_polls_until_done(self, client: RunBlob, mock_api):
        # First two calls → processing, third → completed
        route = mock_api.get(STATUS_URL)
        route.side_effect = [
            Response(200, json={
                "task_uuid": "test-uuid-123",
                "status": "processing",
                "prompt": "test",
                "result_image_url": None,
                "message": None,
            }),
            Response(200, json={
                "task_uuid": "test-uuid-123",
                "status": "processing",
                "prompt": "test",
                "result_image_url": None,
                "message": None,
            }),
            Response(200, json={
                "task_uuid": "test-uuid-123",
                "status": "completed",
                "prompt": "test",
                "result_image_url": "https://storage.runblob.io/done.png",
                "message": None,
            }),
        ]

        result = await client.nanobanana.wait_for_result(
            "test-uuid-123",
            timeout=30.0,
            poll_interval=0.01,
            max_poll_interval=0.02,
        )

        assert result.is_completed
        assert route.call_count == 3

    async def test_timeout_raises(self, client: RunBlob, mock_api):
        mock_api.get(STATUS_URL).mock(
            return_value=Response(200, json={
                "task_uuid": "test-uuid-123",
                "status": "processing",
                "prompt": "test",
                "result_image_url": None,
                "message": None,
            })
        )

        with pytest.raises(GenerationTimeoutError) as exc_info:
            await client.nanobanana.wait_for_result(
                "test-uuid-123",
                timeout=0.05,
                poll_interval=0.01,
            )

        assert exc_info.value.task_uuid == "test-uuid-123"

    async def test_failed_result(self, client: RunBlob, mock_api):
        mock_api.get(STATUS_URL).mock(
            return_value=Response(200, json={
                "task_uuid": "test-uuid-123",
                "status": "failed",
                "prompt": "test",
                "result_image_url": None,
                "message": "MODERATION_FAILED",
            })
        )

        result = await client.nanobanana.wait_for_result("test-uuid-123")

        assert result.is_failed
        assert result.message == "MODERATION_FAILED"
