"""Async client — why AsyncClient and when to use it.

AsyncClient has the same API as SyncClient — just add async with,
await, and async for. The real difference is concurrency:

  SyncClient:  every HTTP request blocks the thread.
               Operations are always sequential.

  AsyncClient: the event loop handles I/O in the background.
               Multiple operations run concurrently on one thread.

This matters when you need to:
  - Generate several images at the same time
  - Stream responses from multiple characters simultaneously
  - Build bots or servers that handle many users at once

In this example, we:
  - Generate 3 images concurrently with asyncio.gather()
  - Stream 2 character chats at the same time with TaskGroup

See auth.py for authentication examples (works identically with AsyncClient).

Requires:
    SEAART_EMAIL and SEAART_PASSWORD environment variables.
"""

from __future__ import annotations

import asyncio
import os

from seaart import AsyncClient
from seaart.helpers import extract_character_link, extract_model_link
from seaart.types import AppId

CHARACTER_URL_A = (
    "https://www.seaart.ai/ru/character/chat/d5f5v2te878c73cmjhhg?from=null&s_id=123"
)
CHARACTER_URL_B = (
    "https://www.seaart.ai/ru/character/chat/d5f5v2te878c73cmjhhg?from=null&s_id=456"
)

MODEL_URL = "https://www.seaart.ai/create/image?id=cu8d8ble878c738hejd0&model_ver_no=f1ebdcc036b767a672db420eb367688d"


# ---------------------------------------------------------------------------
# Concurrent image generation
# ---------------------------------------------------------------------------


async def concurrent_image_generation() -> None:
    """Generate several images at the same time.

    With SyncClient, three text_to_img calls run one after another —
    total time = sum of all three generation times.

    Here we submit all tasks first (each await is just the short HTTP POST,
    not the generation itself), then wait for all of them concurrently.
    Total time ≈ time of the slowest single task.

    SyncClient has wait_many() for concurrent polling, but the submissions
    themselves are still blocking one-by-one. With AsyncClient both
    submission and waiting are fully concurrent.
    """
    model = extract_model_link(MODEL_URL)
    assert model is not None

    async with AsyncClient() as client:
        await client.auth.login(
            email=os.environ["SEAART_EMAIL"],
            password=os.environ["SEAART_PASSWORD"],
        )

        # Submit all three tasks — each await returns immediately
        # after the server accepts the request.
        t1 = await client.images.text_to_img(
            "a serene mountain lake at dawn, oil painting", model.model_no
        )
        t2 = await client.images.text_to_img(
            "cyberpunk city street in heavy rain, neon reflections", model.model_no
        )
        t3 = await client.images.text_to_img(
            "a fox sitting in an autumn forest, watercolor", model.model_no
        )

        print(f"Tasks submitted: {t1.value.id}, {t2.value.id}, {t3.value.id}")

        # Poll all three concurrently — they don't block each other.
        results = await asyncio.gather(
            t1.wait(timeout=180),
            t2.wait(timeout=180),
            t3.wait(timeout=180),
        )

        print("All done:")
        for item in results:
            print(f"  {item.image_url}")


# ---------------------------------------------------------------------------
# Concurrent character streaming
# ---------------------------------------------------------------------------


async def concurrent_character_streams() -> None:
    """Stream responses from two characters at the same time.

    This is impossible with SyncClient without threads — a synchronous
    SSE stream blocks the thread for its entire duration. You literally
    cannot read from two streams at once.

    AsyncClient suspends each coroutine while waiting for the next chunk,
    so both streams make progress concurrently on a single thread.
    """
    link_a = extract_character_link(CHARACTER_URL_A)
    link_b = extract_character_link(CHARACTER_URL_B)
    assert link_a is not None and link_b is not None

    async with AsyncClient(app_id=AppId.APP) as client:
        await client.auth.login(
            email=os.environ["SEAART_EMAIL"],
            password=os.environ["SEAART_PASSWORD"],
        )

        async def stream_character(character_id: str, question: str) -> None:
            """Open a session, stream the reply, then clean up."""
            chat = await client.characters.chats.create(character_id)
            session_id = chat.value.session_id
            await client.characters.chats.start(session_id)

            try:
                async for item in client.characters.chats.stream(
                    question, session_id
                ):
                    if item.event == "chunk":
                        # Prefix so you can tell the characters apart.
                        tag = character_id[:8]
                        print(f"[{tag}] {item.content}", end="", flush=True)
                    elif item.event == "end":
                        print()
            finally:
                await client.characters.chats.delete(session_id)

        # Both coroutines run concurrently on the same event loop.
        async with asyncio.TaskGroup() as tg:
            tg.create_task(
                stream_character(link_a.character_id, "Tell me about yourself.")
            )
            tg.create_task(
                stream_character(link_b.character_id, "What's your favorite story?")
            )


if __name__ == "__main__":
    asyncio.run(concurrent_image_generation())
