"""Tests for Kling resource."""
from __future__ import annotations

import pytest
import respx
from httpx import Response

from runblob import (
    RunBlob,
    KlingStatus,
    KlingVideoModel,
    KlingO3VideoModel,
    KlingGenerateResponse,
    KlingVideoStatusResponse,
    KlingPhotoGenerateResponse,
    KlingPhotoStatusResponse,
    AuthenticationError,
    NotFoundError,
    GenerationTimeoutError,
)


VIDEO_GEN_URL = "/v1/kling/generate"
VIDEO_STATUS_URL = "/v1/kling/generations/gen-123"
O1_VIDEO_GEN_URL = "/v1/kling/o1-video/generate"
O1_VIDEO_STATUS_URL = "/v1/kling/o1-video/generations/gen-o1"
O3_VIDEO_GEN_URL = "/v1/kling/o3-video/generate"
O3_VIDEO_STATUS_URL = "/v1/kling/o3-video/generations/gen-o3"
O1_PHOTO_GEN_URL = "/v1/kling/o1-photo/generate"
O1_PHOTO_STATUS_URL = "/v1/kling/o1-photo/generations/photo-o1"
O3_PHOTO_GEN_URL = "/v1/kling/o3-photo/generate"
O3_PHOTO_STATUS_URL = "/v1/kling/o3-photo/generations/photo-o3"


class TestVideoGenerate:
    async def test_basic_generate(self, client: RunBlob, mock_api):
        mock_api.post(VIDEO_GEN_URL).mock(
            return_value=Response(201, json={
                "generation_id": "gen-123",
                "status": "pending",
                "price": "2.5000",
            })
        )

        result = await client.kling.generate(prompt="A cat walking on the street")

        assert isinstance(result, KlingGenerateResponse)
        assert result.generation_id == "gen-123"
        assert result.status == KlingStatus.PENDING
        assert result.price == "2.5000"

    async def test_generate_with_model(self, client: RunBlob, mock_api):
        mock_api.post(VIDEO_GEN_URL).mock(
            return_value=Response(201, json={
                "generation_id": "gen-456",
                "status": "pending",
                "price": "5.0000",
            })
        )

        result = await client.kling.generate(
            prompt="A sunset timelapse",
            model="kling_2.6",
            duration="10",
            aspect_ratio="9:16",
            cfg_scale=0.8,
        )

        assert result.generation_id == "gen-456"

    async def test_generate_with_enum(self, client: RunBlob, mock_api):
        mock_api.post(VIDEO_GEN_URL).mock(
            return_value=Response(201, json={
                "generation_id": "gen-enum",
                "status": "pending",
                "price": "1.0000",
            })
        )

        result = await client.kling.generate(
            prompt="test",
            model=KlingVideoModel.KLING_1_6_PRO,
        )

        assert result.generation_id == "gen-enum"

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

        with pytest.raises(AuthenticationError):
            await client.kling.generate(prompt="test")


class TestVideoGetStatus:
    async def test_completed(self, client: RunBlob, mock_api):
        mock_api.get(VIDEO_STATUS_URL).mock(
            return_value=Response(200, json={
                "generation_id": "gen-123",
                "status": "completed",
                "prompt": "A cat",
                "video_url": "https://storage.runblob.io/video.mp4",
                "model": "kling_2.5_turbo",
            })
        )

        result = await client.kling.get_status("gen-123")

        assert result.is_completed
        assert result.video_url == "https://storage.runblob.io/video.mp4"

    async def test_processing(self, client: RunBlob, mock_api):
        mock_api.get(VIDEO_STATUS_URL).mock(
            return_value=Response(200, json={
                "generation_id": "gen-123",
                "status": "processing",
                "prompt": "A cat",
            })
        )

        result = await client.kling.get_status("gen-123")

        assert not result.is_terminal
        assert result.video_url is None

    async def test_failed(self, client: RunBlob, mock_api):
        mock_api.get(VIDEO_STATUS_URL).mock(
            return_value=Response(200, json={
                "generation_id": "gen-123",
                "status": "failed",
                "prompt": "A cat",
                "description": "Content moderation failed",
            })
        )

        result = await client.kling.get_status("gen-123")

        assert result.is_failed
        assert result.description == "Content moderation failed"

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

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


class TestVideoWaitForResult:
    async def test_immediate_completion(self, client: RunBlob, mock_api):
        mock_api.get(VIDEO_STATUS_URL).mock(
            return_value=Response(200, json={
                "generation_id": "gen-123",
                "status": "completed",
                "prompt": "test",
                "video_url": "https://storage.runblob.io/out.mp4",
            })
        )

        result = await client.kling.wait_for_result("gen-123", timeout=5.0)
        assert result.is_completed

    async def test_polls_until_done(self, client: RunBlob, mock_api):
        route = mock_api.get(VIDEO_STATUS_URL)
        route.side_effect = [
            Response(200, json={"generation_id": "gen-123", "status": "processing", "prompt": "t"}),
            Response(200, json={"generation_id": "gen-123", "status": "completed", "prompt": "t", "video_url": "https://x.com/v.mp4"}),
        ]

        result = await client.kling.wait_for_result(
            "gen-123", timeout=30.0, poll_interval=0.01, max_poll_interval=0.02,
        )

        assert result.is_completed
        assert route.call_count == 2

    async def test_timeout(self, client: RunBlob, mock_api):
        mock_api.get(VIDEO_STATUS_URL).mock(
            return_value=Response(200, json={"generation_id": "gen-123", "status": "processing", "prompt": "t"})
        )

        with pytest.raises(GenerationTimeoutError):
            await client.kling.wait_for_result("gen-123", timeout=0.05, poll_interval=0.01)


class TestO1Video:
    async def test_generate(self, client: RunBlob, mock_api):
        mock_api.post(O1_VIDEO_GEN_URL).mock(
            return_value=Response(201, json={
                "generation_id": "gen-o1",
                "status": "pending",
                "price": "3.0000",
            })
        )

        result = await client.kling.generate_o1_video(
            prompt="A dog running in a field",
            duration="10",
        )

        assert result.generation_id == "gen-o1"

    async def test_get_status(self, client: RunBlob, mock_api):
        mock_api.get(O1_VIDEO_STATUS_URL).mock(
            return_value=Response(200, json={
                "generation_id": "gen-o1",
                "status": "completed",
                "prompt": "test",
                "video_url": "https://storage.runblob.io/o1.mp4",
            })
        )

        result = await client.kling.get_o1_video_status("gen-o1")
        assert result.is_completed


class TestO3Video:
    async def test_generate(self, client: RunBlob, mock_api):
        mock_api.post(O3_VIDEO_GEN_URL).mock(
            return_value=Response(201, json={
                "generation_id": "gen-o3",
                "status": "pending",
                "price": "4.0000",
            })
        )

        result = await client.kling.generate_o3_video(
            prompt="Cinematic sunset over mountains",
            model="kling_o3_pro",
            duration="15",
        )

        assert result.generation_id == "gen-o3"

    async def test_get_status(self, client: RunBlob, mock_api):
        mock_api.get(O3_VIDEO_STATUS_URL).mock(
            return_value=Response(200, json={
                "generation_id": "gen-o3",
                "status": "completed",
                "prompt": "test",
                "video_url": "https://storage.runblob.io/o3.mp4",
            })
        )

        result = await client.kling.get_o3_video_status("gen-o3")
        assert result.is_completed


class TestO1Photo:
    async def test_generate(self, client: RunBlob, mock_api):
        mock_api.post(O1_PHOTO_GEN_URL).mock(
            return_value=Response(201, json={
                "generation_id": "photo-o1",
                "status": "pending",
                "price": "0.5000",
            })
        )

        result = await client.kling.generate_o1_photo(
            prompt="A portrait of a woman",
            img_resolution="2k",
            aspect_ratio="3:4",
        )

        assert isinstance(result, KlingPhotoGenerateResponse)
        assert result.generation_id == "photo-o1"

    async def test_get_status_completed(self, client: RunBlob, mock_api):
        mock_api.get(O1_PHOTO_STATUS_URL).mock(
            return_value=Response(200, json={
                "generation_id": "photo-o1",
                "status": "completed",
                "prompt": "test",
                "image_url": "https://storage.runblob.io/photo.png",
            })
        )

        result = await client.kling.get_o1_photo_status("photo-o1")
        assert result.is_completed
        assert result.image_url == "https://storage.runblob.io/photo.png"


class TestO3Photo:
    async def test_generate(self, client: RunBlob, mock_api):
        mock_api.post(O3_PHOTO_GEN_URL).mock(
            return_value=Response(201, json={
                "generation_id": "photo-o3",
                "status": "pending",
                "price": "1.0000",
            })
        )

        result = await client.kling.generate_o3_photo(
            prompt="A landscape painting",
            img_resolution="4k",
            aspect_ratio="16:9",
        )

        assert isinstance(result, KlingPhotoGenerateResponse)
        assert result.generation_id == "photo-o3"

    async def test_get_status_completed(self, client: RunBlob, mock_api):
        mock_api.get(O3_PHOTO_STATUS_URL).mock(
            return_value=Response(200, json={
                "generation_id": "photo-o3",
                "status": "completed",
                "prompt": "test",
                "image_url": "https://storage.runblob.io/o3photo.png",
            })
        )

        result = await client.kling.get_o3_photo_status("photo-o3")
        assert result.is_completed

    async def test_wait_for_result(self, client: RunBlob, mock_api):
        route = mock_api.get(O3_PHOTO_STATUS_URL)
        route.side_effect = [
            Response(200, json={"generation_id": "photo-o3", "status": "processing", "prompt": "t"}),
            Response(200, json={"generation_id": "photo-o3", "status": "completed", "prompt": "t", "image_url": "https://x.com/p.png"}),
        ]

        result = await client.kling.wait_for_o3_photo(
            "photo-o3", timeout=30.0,
        )

        assert result.is_completed
        assert route.call_count == 2


class TestBatch:
    async def test_batch_generate(self, client: RunBlob, mock_api):
        mock_api.post(VIDEO_GEN_URL).mock(
            return_value=Response(201, json={
                "generation_id": "batch-1",
                "status": "pending",
                "price": "2.5000",
            })
        )

        results = await client.kling.generate_batch(
            ["Prompt 1", "Prompt 2", "Prompt 3"],
            model="kling_2.5_turbo",
            concurrency=2,
        )

        assert len(results) == 3


class TestHasKlingResource:
    async def test_client_has_kling(self, client: RunBlob):
        assert hasattr(client, "kling")
