import asyncio
import hashlib
import json
import os
import re
import time
import uuid
from concurrent.futures import ThreadPoolExecutor

try:
    import cloudscraper
    from cloudscraper import CloudScraper
except ImportError:
    CloudScraper = None

from .helper import get_last_user_message
from .yupp.models import YuppModelManager
from ..cookies import get_cookies
from ..debug import log
from ..errors import RateLimitError, ProviderException, MissingAuthError, MissingRequirementsError
from ..image import is_accepted_format, to_bytes
from ..providers.base_provider import AsyncGeneratorProvider, ProviderModelMixin
from ..providers.response import Reasoning, PlainTextResponse, PreviewResponse, JsonConversation, ImageResponse, \
    ProviderInfo, FinishReason, JsonResponse, VariantResponse
from ..tools.auth import AuthManager
from ..tools.media import merge_media
from ..typing import AsyncResult, Messages, Optional, Dict, Any, List

YUPP_ACCOUNTS: List[Dict[str, Any]] = []
account_rotation_lock = asyncio.Lock()
ImagesCache: Dict[str, dict] = {}
_accounts_loaded = False
_executor = ThreadPoolExecutor(max_workers=10)


def create_scraper():
    scraper = cloudscraper.create_scraper(
        browser={
            'browser': 'chrome',
            'platform': 'windows',
            'desktop': True,
            'mobile': False
        },
        delay=10,
        interpreter='nodejs'
    )
    scraper.headers.update({
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0",
        "Accept": "text/x-component, */*",
        "Accept-Encoding": "gzip, deflate, br, zstd",
        "Accept-Language": "en-US,en;q=0.9",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-origin",
        "Sec-Ch-Ua": '"Microsoft Edge";v="137", "Chromium";v="137", "Not/A)Brand";v="24"',
        "Sec-Ch-Ua-Mobile": "?0",
        "Sec-Ch-Ua-Platform": '"Windows"'
    })
    return scraper


def load_yupp_accounts(tokens_str: str):
    global YUPP_ACCOUNTS, _accounts_loaded
    if _accounts_loaded:
        return
    if not tokens_str:
        return
    tokens = [token.strip() for token in tokens_str.split(',') if token.strip()]
    YUPP_ACCOUNTS = [
        {
            "token": token,
            "is_valid": True,
            "error_count": 0,
            "last_used": 0.0
        }
        for token in tokens
    ]
    _accounts_loaded = True


async def get_best_yupp_account() -> Optional[Dict[str, Any]]:
    max_error_count = int(os.getenv("MAX_ERROR_COUNT", "3"))
    error_cooldown = int(os.getenv("ERROR_COOLDOWN", "300"))

    async with account_rotation_lock:
        now = time.time()
        valid_accounts = [
            acc
            for acc in YUPP_ACCOUNTS
            if acc["is_valid"]
               and (
                       acc["error_count"] < max_error_count
                       or now - acc["last_used"] > error_cooldown
               )
        ]

        if not valid_accounts:
            return None

        for acc in valid_accounts:
            if (
                    acc["error_count"] >= max_error_count
                    and now - acc["last_used"] > error_cooldown
            ):
                acc["error_count"] = 0

        valid_accounts.sort(key=lambda x: (x["last_used"], x["error_count"]))
        account = valid_accounts[0]
        account["last_used"] = now
        return account


def sync_claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], reward_id: str):
    try:
        log_debug(f"Claiming reward {reward_id}...")
        url = "https://yupp.ai/api/trpc/reward.claim?batch=1"
        payload = {"0": {"json": {"rewardId": reward_id}}}
        scraper.cookies.set("__Secure-yupp.session-token", account['token'])
        response = scraper.post(url, json=payload)
        response.raise_for_status()
        data = response.json()
        balance = data[0]["result"]["data"]["json"]["currentCreditBalance"]
        log_debug(f"Reward claimed successfully. New balance: {balance}")
        return balance
    except Exception as e:
        log_debug(f"Failed to claim reward {reward_id}. Error: {e}")
        return None


async def claim_yupp_reward(scraper: CloudScraper, account: Dict[str, Any], reward_id: str):
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(_executor, sync_claim_yupp_reward, scraper, account, reward_id)


def sync_make_chat_private(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
    try:
        log_debug(f"Setting chat {chat_id} to PRIVATE...")
        url = "https://yupp.ai/api/trpc/chat.updateSharingSettings?batch=1"
        payload = {
            "0": {
                "json": {
                    "chatId": chat_id,
                    "status": "PRIVATE"
                }
            }
        }
        scraper.cookies.set("__Secure-yupp.session-token", account['token'])
        response = scraper.post(url, json=payload)
        response.raise_for_status()
        data = response.json()
        if (
                isinstance(data, list) and len(data) > 0
                and "json" in data[0].get("result", {}).get("data", {})
        ):
            log_debug(f"Chat {chat_id} is now PRIVATE")
            return True
        log_debug(f"Unexpected response while setting chat private: {data}")
        return False
    except Exception as e:
        log_debug(f"Failed to make chat {chat_id} private: {e}")
        return False


async def make_chat_private(scraper: CloudScraper, account: Dict[str, Any], chat_id: str) -> bool:
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(_executor, sync_make_chat_private, scraper, account, chat_id)


def log_debug(message: str):
    if os.getenv("DEBUG_MODE", "false").lower() == "true":
        print(f"[DEBUG] {message}")
    else:
        log(f"[Yupp] {message}")


def format_messages_for_yupp(messages: Messages) -> str:
    if not messages:
        return ""

    if len(messages) == 1 and isinstance(messages[0].get("content"), str):
        return messages[0].get("content", "").strip()

    formatted = []

    system_messages = [msg for msg in messages if msg.get("role") in ["developer", "system"]]
    if system_messages:
        for sys_msg in system_messages:
            content = sys_msg.get("content", "")
            formatted.append(content)

    user_assistant_msgs = [msg for msg in messages if msg.get("role") in ["user", "assistant"]]
    for msg in user_assistant_msgs:
        role = "Human" if msg.get("role") == "user" else "Assistant"
        content = msg.get("content", "")
        for part in content if isinstance(content, list) else [{"text": content}]:
            if part.get("text", "").strip():
                formatted.append(f"\n\n{role}: {part.get('text', '')}")

    if not formatted or not formatted[-1].strip().startswith("Assistant:"):
        formatted.append("\n\nAssistant:")

    result = "".join(formatted)
    if result.startswith("\n\n"):
        result = result[2:]

    return result


class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
    url = "https://yupp.ai"
    login_url = "https://discord.gg/qXA4Wf4Fsm"
    working = CloudScraper is not None
    active_by_default = True
    supports_stream = True
    image_cache = True

    @classmethod
    def get_models(cls, api_key: str = None, **kwargs) -> List[str]:
        if not cls.models:
            if not api_key:
                api_key = AuthManager.load_api_key(cls)
            if not api_key:
                api_key = get_cookies("yupp.ai", False).get("__Secure-yupp.session-token")
            if not api_key:
                raise MissingAuthError("No Yupp accounts configured. Set YUPP_API_KEY environment variable.")
            manager = YuppModelManager(api_key=api_key, session=create_scraper())
            models = manager.client.fetch_models()
            if models:
                cls.models_tags = {model.get("name"): manager.processor.generate_tags(model) for model in models}
                cls.models = [model.get("name") for model in models]
                cls.image_models = [model.get("name") for model in models if model.get("isImageGeneration")]
                cls.vision_models = [model.get("name") for model in models if
                                     "image/*" in model.get("supportedAttachmentMimeTypes", [])]
        return cls.models

    @classmethod
    def sync_prepare_files(cls, media, scraper: CloudScraper, account: Dict[str, Any]) -> list:
        files = []
        if not media:
            return files
        for file, name in media:
            data = to_bytes(file)
            hasher = hashlib.md5()
            hasher.update(data)
            image_hash = hasher.hexdigest()
            cached_file = ImagesCache.get(image_hash)
            if cls.image_cache and cached_file:
                log_debug("Using cached image")
                files.append(cached_file)
                continue

            scraper.cookies.set("__Secure-yupp.session-token", account['token'])
            presigned_resp = scraper.post(
                "https://yupp.ai/api/trpc/chat.createPresignedURLForUpload?batch=1",
                json={
                    "0": {"json": {"fileName": name, "fileSize": len(data), "contentType": is_accepted_format(data)}}
                },
                headers={"Content-Type": "application/json"}
            )
            presigned_resp.raise_for_status()
            upload_info = presigned_resp.json()[0]["result"]["data"]["json"]
            upload_url = upload_info["signedUrl"]

            scraper.put(
                upload_url,
                data=data,
                headers={
                    "Content-Type": is_accepted_format(data),
                    "Content-Length": str(len(data))
                }
            )

            attachment_resp = scraper.post(
                "https://yupp.ai/api/trpc/chat.createAttachmentForUploadedFile?batch=1",
                json={"0": {"json": {"fileName": name, "contentType": is_accepted_format(data),
                                     "fileId": upload_info["fileId"]}}},
                cookies={"__Secure-yupp.session-token": account["token"]}
            )
            attachment_resp.raise_for_status()
            attachment = attachment_resp.json()[0]["result"]["data"]["json"]
            file_info = {
                "fileName": attachment["file_name"],
                "contentType": attachment["content_type"],
                "attachmentId": attachment["attachment_id"],
                "chatMessageId": ""
            }
            ImagesCache[image_hash] = file_info
            files.append(file_info)
        return files

    @classmethod
    async def prepare_files(cls, media, scraper: CloudScraper, account: Dict[str, Any]) -> list:
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(_executor, cls.sync_prepare_files, media, scraper, account)

    @classmethod
    def sync_get_signed_image(cls, scraper: CloudScraper, image_id: str) -> str:
        url = "https://yupp.ai/api/trpc/chat.getSignedImage"
        resp = scraper.get(
            url,
            params={
                "batch": "1",
                "input": json.dumps(
                    {"0": {"json": {"imageId": image_id}}}
                )
            }
        )
        resp.raise_for_status()
        data = resp.json()
        return data[0]["result"]["data"]["json"]["signed_url"]

    @classmethod
    async def get_signed_image(cls, scraper: CloudScraper, image_id: str) -> str:
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(_executor, cls.sync_get_signed_image, scraper, image_id)

    @classmethod
    def sync_stream_request(cls, scraper: CloudScraper, url: str, payload: list, headers: dict,
                            timeout: int):
        response = scraper.post(url, json=payload, headers=headers, stream=True, timeout=timeout)
        response.raise_for_status()
        return response

    @classmethod
    async def create_async_generator(
            cls,
            model: str,
            messages: Messages,
            proxy: str = None,
            **kwargs,
    ) -> AsyncResult:
        if  CloudScraper is None:
            raise MissingRequirementsError("cloudscraper library is required for Yupp provider | install it via 'pip install cloudscraper'")
        api_key = kwargs.get("api_key")
        if not api_key:
            api_key = get_cookies("yupp.ai", False).get("__Secure-yupp.session-token")
        if api_key:
            load_yupp_accounts(api_key)
            log_debug(f"Yupp provider initialized with {len(YUPP_ACCOUNTS)} accounts")
        else:
            raise MissingAuthError("No Yupp accounts configured. Set YUPP_API_KEY environment variable.")

        conversation = kwargs.get("conversation")
        url_uuid = conversation.url_uuid if conversation else None
        is_new_conversation = url_uuid is None

        prompt = kwargs.get("prompt")
        if prompt is None:
            if is_new_conversation:
                prompt = format_messages_for_yupp(messages)
            else:
                prompt = get_last_user_message(messages, prompt)

        log_debug(
            f"Use url_uuid: {url_uuid}, Formatted prompt length: {len(prompt)}, Is new conversation: {is_new_conversation}")

        max_attempts = len(YUPP_ACCOUNTS)
        for attempt in range(max_attempts):
            account = await get_best_yupp_account()
            if not account:
                raise ProviderException("No valid Yupp accounts available")

            try:
                scraper = create_scraper()
                if proxy:
                    scraper.proxies = {
                        "http": proxy,
                        "https": proxy
                    }

                turn_id = str(uuid.uuid4())

                media = kwargs.get("media")
                if media:
                    media_ = list(merge_media(media, messages))
                    files = await cls.prepare_files(media_, scraper=scraper, account=account)
                else:
                    files = []

                mode = "image" if model in cls.image_models else "text"

                if is_new_conversation:
                    url_uuid = str(uuid.uuid4())
                    payload = [
                        url_uuid,
                        turn_id,
                        prompt,
                        "$undefined",
                        "$undefined",
                        files,
                        "$undefined",
                        [{"modelName": model, "promptModifierId": "$undefined"}] if model else "none",
                        mode,
                        True,
                        "$undefined",
                    ]
                    url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
                    yield JsonConversation(url_uuid=url_uuid)
                    next_action = kwargs.get("next_action", "7f7de0a21bc8dc3cee8ba8b6de632ff16f769649dd")
                else:
                    payload = [
                        url_uuid,
                        turn_id,
                        prompt,
                        False,
                        [],
                        [{"modelName": model, "promptModifierId": "$undefined"}] if model else [],
                        mode,
                        files
                    ]
                    url = f"https://yupp.ai/chat/{url_uuid}?stream=true"
                    next_action = kwargs.get("next_action", "7f9ec99a63cbb61f69ef18c0927689629bda07f1bf")

                headers = {
                    "accept": "text/x-component",
                    "content-type": "text/plain;charset=UTF-8",
                    "next-action": next_action,
                    "cookie": f"__Secure-yupp.session-token={account['token']}",
                }

                log_debug(f"Sending request to: {url}")
                log_debug(f"Payload structure: {type(payload)}, length: {len(str(payload))}")

                _timeout = kwargs.get("timeout")
                if isinstance(_timeout, (int, float)):
                    timeout = int(_timeout)
                else:
                    timeout = 5 * 60

                loop = asyncio.get_event_loop()
                response = await loop.run_in_executor(
                    _executor,
                    cls.sync_stream_request,
                    scraper,
                    url,
                    payload,
                    headers,
                    timeout
                )

                asyncio.create_task(make_chat_private(scraper, account, url_uuid))

                async for chunk in cls._process_stream_response(response, account, scraper, prompt, model):
                    yield chunk
                return

            except RateLimitError:
                log_debug(f"Account ...{account['token'][-4:]} hit rate limit, rotating")
                async with account_rotation_lock:
                    account["error_count"] += 1
                continue

            except ProviderException as e:
                log_debug(f"Account ...{account['token'][-4:]} failed: {str(e)}")
                async with account_rotation_lock:
                    if "auth" in str(e).lower() or "401" in str(e) or "403" in str(e):
                        account["is_valid"] = False
                    else:
                        account["error_count"] += 1
                continue

            except Exception as e:
                log_debug(f"Unexpected error with account ...{account['token'][-4:]}: {str(e)}")
                error_str = str(e).lower()
                if "500" in error_str or "internal server error" in error_str:
                    account["is_valid"] = False
                    continue
                async with account_rotation_lock:
                    account["error_count"] += 1
                raise ProviderException(f"Yupp request failed: {str(e)}") from e

        raise ProviderException("All Yupp accounts failed after rotation attempts")

    @classmethod
    async def _process_stream_response(
            cls,
            response,
            account: Dict[str, Any],
            scraper: CloudScraper,
            prompt: str,
            model_id: str
    ) -> AsyncResult:
        line_pattern = re.compile(b"^([0-9a-fA-F]+):(.*)")
        target_stream_id = None
        reward_info = None
        is_thinking = False
        thinking_content = ""
        normal_content = ""
        quick_content = ""
        variant_text = ""
        stream = {
            "target": [],
            "variant": [],
            "quick": [],
            "thinking": [],
            "extra": []
        }
        select_stream = [None, None]
        capturing_ref_id: Optional[str] = None
        capturing_lines: List[bytes] = []
        think_blocks: Dict[str, str] = {}
        image_blocks: Dict[str, str] = {}

        def extract_ref_id(ref):
            return ref[2:] if ref and isinstance(ref, str) and ref.startswith("$@") else None

        def extract_ref_name(ref: str) -> Optional[str]:
            if not isinstance(ref, str):
                return None
            if ref.startswith("$@"):
                return ref[2:]
            if ref.startswith("$") and len(ref) > 1:
                return ref[1:]
            return None

        def is_valid_content(content: str) -> bool:
            if not content or content in [None, "", "$undefined"]:
                return False
            return True

        async def process_content_chunk(content: str, chunk_id: str, line_count: int, *, for_target: bool = False):
            nonlocal normal_content

            if not is_valid_content(content):
                return

            if '<yapp class="image-gen">' in content:
                img_block = content.split('<yapp class="image-gen">').pop().split('</yapp>')[0]
                image_id = json.loads(img_block).get("image_id")
                signed_url = await cls.get_signed_image(scraper, image_id)
                img = ImageResponse(signed_url, prompt)
                yield img
                return

            if is_thinking:
                yield Reasoning(content)
            else:
                if for_target:
                    normal_content += content
                yield content

        def finalize_capture_block(ref_id: str, lines: List[bytes]):
            text = b"".join(lines).decode("utf-8", errors="ignore")

            think_start = text.find("<think>")
            think_end = text.find("</think>")
            if think_start != -1 and think_end != -1 and think_end > think_start:
                inner = text[think_start + len("<think>"):think_end].strip()
                if inner:
                    think_blocks[ref_id] = inner

            yapp_start = text.find('<yapp class="image-gen">')
            if yapp_start != -1:
                yapp_end = text.find("</yapp>", yapp_start)
                if yapp_end != -1:
                    yapp_block = text[yapp_start:yapp_end + len("</yapp>")]
                    image_blocks[ref_id] = yapp_block

        try:
            line_count = 0
            quick_response_id = None
            variant_stream_id = None
            is_started: bool = False
            variant_image: Optional[ImageResponse] = None
            reward_id = "a"
            routing_id = "e"
            turn_id = None
            persisted_turn_id = None
            left_message_id = None
            right_message_id = None
            nudge_new_chat_id = None
            nudge_new_chat = False

            loop = asyncio.get_event_loop()

            def iter_lines():
                for line in response.iter_lines():
                    if line:
                        yield line

            lines_iterator = iter_lines()

            while True:
                try:
                    line = await loop.run_in_executor(_executor, lambda: next(lines_iterator, None))
                    if line is None:
                        break
                except StopIteration:
                    break

                line_count += 1

                if isinstance(line, str):
                    line = line.encode()

                if capturing_ref_id is not None:
                    capturing_lines.append(line)

                    if b"</yapp>" in line:
                        idx = line.find(b"</yapp>")
                        suffix = line[idx + len(b"</yapp>"):]
                        finalize_capture_block(capturing_ref_id, capturing_lines)
                        capturing_ref_id = None
                        capturing_lines = []

                        if suffix.strip():
                            line = suffix
                        else:
                            continue
                    else:
                        continue

                match = line_pattern.match(line)
                if not match:
                    if b"<think>" in line:
                        m = line_pattern.match(line)
                        if m:
                            capturing_ref_id = m.group(1).decode()
                            capturing_lines = [line]
                            continue
                    continue

                chunk_id, chunk_data = match.groups()
                chunk_id = chunk_id.decode()

                if nudge_new_chat_id and chunk_id == nudge_new_chat_id:
                    nudge_new_chat = chunk_data.decode()
                    continue

                try:
                    data = json.loads(chunk_data) if chunk_data != b"{}" else {}
                except json.JSONDecodeError:
                    continue

                if chunk_id == reward_id and isinstance(data, dict) and "unclaimedRewardInfo" in data:
                    reward_info = data
                    log_debug(f"Found reward info")

                elif chunk_id == "1":
                    yield PlainTextResponse(line.decode(errors="ignore"))
                    if isinstance(data, dict):
                        left_stream = data.get("leftStream", {})
                        right_stream = data.get("rightStream", {})
                        if data.get("quickResponse", {}) != "$undefined":
                            quick_response_id = extract_ref_id(
                                data.get("quickResponse", {}).get("stream", {}).get("next"))

                        if data.get("turnId", {}) != "$undefined":
                            turn_id = extract_ref_id(data.get("turnId", {}).get("next"))
                        if data.get("persistedTurn", {}) != "$undefined":
                            persisted_turn_id = extract_ref_id(data.get("persistedTurn", {}).get("next"))
                        if data.get("leftMessageId", {}) != "$undefined":
                            left_message_id = extract_ref_id(data.get("leftMessageId", {}).get("next"))
                        if data.get("rightMessageId", {}) != "$undefined":
                            right_message_id = extract_ref_id(data.get("rightMessageId", {}).get("next"))

                        reward_id = extract_ref_id(data.get("pendingRewardActionResult", "")) or reward_id
                        routing_id = extract_ref_id(data.get("routingResultPromise", "")) or routing_id
                        nudge_new_chat_id = extract_ref_id(data.get("nudgeNewChatPromise", "")) or nudge_new_chat_id
                        select_stream = [left_stream, right_stream]

                elif chunk_id == routing_id:
                    yield PlainTextResponse(line.decode(errors="ignore"))
                    if isinstance(data, dict):
                        provider_info = cls.get_dict()
                        provider_info['model'] = model_id
                        for i, selection in enumerate(data.get("modelSelections", [])):
                            if selection.get("selectionSource") == "USER_SELECTED":
                                target_stream_id = extract_ref_id(select_stream[i].get("next"))
                                provider_info["modelLabel"] = selection.get("shortLabel")
                                provider_info["modelUrl"] = selection.get("externalUrl")
                                log_debug(f"Found target stream ID: {target_stream_id}")
                            else:
                                variant_stream_id = extract_ref_id(select_stream[i].get("next"))
                                provider_info["variantLabel"] = selection.get("shortLabel")
                                provider_info["variantUrl"] = selection.get("externalUrl")
                                log_debug(f"Found variant stream ID: {variant_stream_id}")
                        yield ProviderInfo.from_dict(provider_info)

                elif target_stream_id and chunk_id == target_stream_id:
                    yield PlainTextResponse(line.decode(errors="ignore"))
                    if isinstance(data, dict):
                        target_stream_id = extract_ref_id(data.get("next"))
                        content = data.get("curr", "")
                        if content:
                            ref_name = extract_ref_name(content)
                            if ref_name and (ref_name in think_blocks or ref_name in image_blocks):
                                if ref_name in think_blocks:
                                    t_text = think_blocks[ref_name]
                                    if t_text:
                                        reasoning = Reasoning(t_text)
                                        stream["thinking"].append(reasoning)

                                if ref_name in image_blocks:
                                    img_block_text = image_blocks[ref_name]
                                    async for chunk in process_content_chunk(
                                            img_block_text,
                                            ref_name,
                                            line_count,
                                            for_target=True
                                    ):
                                        stream["target"].append(chunk)
                                        is_started = True
                                        yield chunk
                            else:
                                async for chunk in process_content_chunk(
                                        content,
                                        chunk_id,
                                        line_count,
                                        for_target=True
                                ):
                                    stream["target"].append(chunk)
                                    is_started = True
                                    yield chunk

                elif variant_stream_id and chunk_id == variant_stream_id:
                    yield PlainTextResponse("[Variant] " + line.decode(errors="ignore"))
                    if isinstance(data, dict):
                        variant_stream_id = extract_ref_id(data.get("next"))
                        content = data.get("curr", "")
                        if content:
                            async for chunk in process_content_chunk(
                                    content,
                                    chunk_id,
                                    line_count,
                                    for_target=False
                            ):
                                stream["variant"].append(chunk)
                                if isinstance(chunk, ImageResponse):
                                    yield PreviewResponse(str(chunk))
                                else:
                                    variant_text += str(chunk)
                                    if not is_started:
                                        yield PreviewResponse(variant_text)

                elif quick_response_id and chunk_id == quick_response_id:
                    yield PlainTextResponse("[Quick] " + line.decode(errors="ignore"))
                    if isinstance(data, dict):
                        content = data.get("curr", "")
                        if content:
                            async for chunk in process_content_chunk(
                                    content,
                                    chunk_id,
                                    line_count,
                                    for_target=False
                            ):
                                stream["quick"].append(chunk)
                            quick_content += content
                            yield PreviewResponse(content)

                elif chunk_id in [turn_id, persisted_turn_id]:
                    pass

                elif chunk_id == right_message_id:
                    pass

                elif chunk_id == left_message_id:
                    pass

                elif isinstance(data, dict) and "curr" in data:
                    content = data.get("curr", "")
                    if content:
                        async for chunk in process_content_chunk(
                                content,
                                chunk_id,
                                line_count,
                                for_target=False
                        ):
                            stream["extra"].append(chunk)
                            if isinstance(chunk, str) and "<streaming stopped unexpectedly" in chunk:
                                yield FinishReason(chunk)

                        yield PlainTextResponse("[Extra] " + line.decode(errors="ignore"))

            if variant_image is not None:
                yield variant_image
            elif variant_text:
                yield VariantResponse(variant_text)
            yield JsonResponse(**stream)
            log_debug(f"Finished processing {line_count} lines")

        finally:
            if reward_info and "unclaimedRewardInfo" in reward_info:
                rid = reward_info["unclaimedRewardInfo"].get("rewardId")
                if rid:
                    await claim_yupp_reward(scraper, account, rid)
