import copy
import json
from typing import Dict, Iterator, List, Optional, Tuple, Union

import json5

from qwen_agent import Agent
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import Message
from qwen_agent.tools import BaseTool

CONFIG_SCHEMA = {
    'name': '... # 角色名字，5字左右',
    'description': '... # 角色简介，10字左右',
    'instructions': '... # 对角色的具体功能要求，30字左右，以第二人称称呼角色'
}

CONFIG_EXAMPLE = {
    'name': '小红书写作专家',
    'description': '我会写小红书爆款',
    'instructions': '你是小红书爆款写作专家，创作会先产5个标题（含emoji），再产正文（每段落含emoji，文末有tag）。'
}

BACKGROUND_TOKEN = '<Background>'
CONFIG_TOKEN = '<Config>'
ANSWER_TOKEN = '<Answer>'

ROLE_CREATE_SYSTEM = '''你扮演创建群聊的助手，请你根据用户输入的聊天主题，创建n个合适的虚拟角色，这些角色将在一个聊天室内对话，你需要和用户进行对话，明确用户对这些角色的要求。

配置文件为json格式：
{config_schema}

一个优秀的RichConfig样例如下：
{config_example}

在接下来的对话中，请在回答时严格使用如下格式，先生成群聊背景，然后依次生成所有角色的配置文件，最后再作出回复，除此之外不要回复其他任何内容：
{background_token}: ... # 生成的群聊背景，包括人物关系，预设故事背景等信息。
{config_token}: ... # 生成的第一个角色的配置文件，严格按照以上json格式，禁止为空。保证name和description不为空。instructions内容比description具体，如果用户给出了详细指令，请完全保留，用第二人称描述角色，例如“你是xxx，你具有xxx能力。
{config_token}: ... # 生成的第二个角色的配置文件，要求同上。
...
{config_token}: ... # 生成的第n个角色的配置文件，要求同上，如果用户没有明确指出n的数量，则n等于3；要求每个角色的名字不相同。
{answer_token}: ... # 你希望对用户说的话，用于询问用户对角色的要求，禁止为空，问题要广泛，不要重复问类似的问题。

如果群聊背景或某个角色的配置文件不需要更新，可以不重复输出{background_token}和对应的{config_token}的内容、只输出{answer_token}和需要修改的{config_token}的内容。'''.format(
    config_schema=json.dumps(CONFIG_SCHEMA, ensure_ascii=False, indent=2),
    config_example=json.dumps(CONFIG_EXAMPLE, ensure_ascii=False, indent=2),
    background_token=BACKGROUND_TOKEN,
    config_token=CONFIG_TOKEN,
    answer_token=ANSWER_TOKEN,
)
assert CONFIG_TOKEN in ROLE_CREATE_SYSTEM
assert ANSWER_TOKEN in ROLE_CREATE_SYSTEM


class GroupChatCreator(Agent):

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 name: Optional[str] = None,
                 description: Optional[str] = None,
                 **kwargs):
        super().__init__(function_list=function_list,
                         llm=llm,
                         system_message=ROLE_CREATE_SYSTEM,
                         name=name,
                         description=description,
                         **kwargs)

    def _run(self,
             messages: List[Message],
             agents: List[Agent] = None,
             lang: str = 'en',
             **kwargs) -> Iterator[List[Message]]:
        messages = copy.deepcopy(messages)
        messages = self._preprocess_messages(messages)

        for rsp in self._call_llm(messages=messages):
            yield self._postprocess_messages(rsp)

    def _preprocess_messages(self, messages: List[Message]) -> List[Message]:
        new_messages = []
        content = []
        for message in messages:
            if message.role != 'assistant':
                new_messages.append(message)
            else:
                if message.name == 'background':
                    content.append(f'{BACKGROUND_TOKEN}: {message.content}')
                elif message.name == 'role_config':
                    content.append(f'{CONFIG_TOKEN}: {message.content}')
                else:
                    content.append(f'{ANSWER_TOKEN}: {message.content}')
                    assert new_messages[-1].role == 'user'
                    new_messages.append(Message('assistant', '\n'.join(content)))
                    content = []
        return new_messages

    def _postprocess_messages(self, messages: List[Message]) -> List[Message]:
        new_messages = []
        assert len(messages) == 1
        message = messages[-1]
        background, cfgs, answer = self._extract_role_config_and_answer(message.content)
        if background:
            new_messages.append(Message(message.role, background, name='background'))
        if cfgs:
            for cfg in cfgs:
                new_messages.append(Message(message.role, cfg, name='role_config'))

        new_messages.append(Message(message.role, answer, name=message.name))
        return new_messages

    def _extract_role_config_and_answer(self, text: str) -> Tuple[str, List[str], str]:
        background, cfgs, answer = '', [], ''
        back_pos, cfg_pos, ans_pos = text.find(f'{BACKGROUND_TOKEN}: '), text.find(f'{CONFIG_TOKEN}: '), text.find(
            f'{ANSWER_TOKEN}: ')

        if ans_pos > -1:
            answer = text[ans_pos + len(f'{ANSWER_TOKEN}: '):]
        else:
            ans_pos = len(text)

        if back_pos > -1:
            if cfg_pos > back_pos:
                background = text[back_pos + len(f'{BACKGROUND_TOKEN}: '):cfg_pos]
            else:
                background = text[back_pos + len(f'{BACKGROUND_TOKEN}: '):ans_pos]
        text = text[:ans_pos]

        tmp = text.split(f'{CONFIG_TOKEN}: ')
        for t in tmp:
            if t.strip():
                try:
                    _ = json5.loads(t.strip())
                    cfgs.append(t.strip())
                except Exception:
                    continue

        if not (background or cfgs or answer):
            # There should always be ANSWER_TOKEN, if not, treat the entire content as answer
            answer = text
        return background, cfgs, answer
