from __future__ import annotations

import random
from typing import AsyncIterator

from ..base_provider import AsyncAuthedProvider, ProviderModelMixin
from ...providers.helper import get_last_user_message
from ...requests import StreamSession, sse_stream, raise_for_status
from ...providers.response import AuthResult, TitleGeneration, JsonConversation, FinishReason
from ...typing import AsyncResult, Messages
from ...errors import MissingAuthError

class Kimi(AsyncAuthedProvider, ProviderModelMixin):
    url = "https://www.kimi.com"
    working = False
    active_by_default = True
    default_model = "kimi-k2"
    models = [default_model]
    model_aliases = {"moonshotai/Kimi-K2-Instruct": default_model}

    @classmethod
    async def on_auth_async(cls, proxy: str = None, **kwargs) -> AsyncIterator:
        device_id = str(random.randint(1000000000000000, 9999999999999999))
        async with StreamSession(proxy=proxy, impersonate="chrome") as session:
            async with session.post(
                "https://www.kimi.com/api/device/register",
                json={},
                headers={
                    "x-msh-device-id": device_id,
                    "x-msh-platform": "web",
                    "x-traffic-id": device_id
                }
            ) as response:
                await raise_for_status(response)
            data = await response.json()
            if not data.get("access_token"):
                raise Exception("No access token received")
        yield AuthResult(
            api_key=data.get("access_token"),
            device_id=device_id,
        )

    @classmethod
    async def create_authed(
        cls,
        model: str,
        messages: Messages,
        auth_result: AuthResult,
        proxy: str = None,
        conversation: JsonConversation = None,
        web_search: bool = False,
        **kwargs
    ) -> AsyncResult:
        async with StreamSession(
            proxy=proxy,
            impersonate="chrome",
            headers={
                "Authorization": f"Bearer {auth_result.api_key}",
            }
        ) as session:
            if conversation is None:
                async with session.post("https://www.kimi.com/api/chat", json={
                    "name":"未命名会话",
                    "born_from":"home",
                    "kimiplus_id":"kimi",
                    "is_example":False,
                    "source":"web",
                    "tags":[]
                }) as response:
                    try:
                        await raise_for_status(response)
                    except Exception as e:
                        if "匿名聊天使用次数超过" in str(e):
                            raise MissingAuthError("Anonymous chat usage limit exceeded")
                        raise e
                    chat_data = await response.json()
                conversation = JsonConversation(chat_id=chat_data.get("id"))
                yield conversation
            data = {
                "kimiplus_id": "kimi",
                "extend": {"sidebar": True},
                "model": "k2",
                "use_search": web_search,
                "messages": [
                    {
                        "role": "user",
                        "content": get_last_user_message(messages)
                    }
                ],
                "refs": [],
                "history": [],
                "scene_labels": [],
                "use_semantic_memory": False,
                "use_deep_research": False
            }
            async with session.post(
                f"https://www.kimi.com/api/chat/{conversation.chat_id}/completion/stream",
                json=data
            ) as response:
                await raise_for_status(response)
                async for line in sse_stream(response):
                    if line.get("event") == "cmpl":
                        yield line.get("text")
                    elif line.get("event") == "rename":
                        yield TitleGeneration(line.get("text"))
                    elif line.get("event") == "all_done":
                        yield FinishReason("stop")
                        break