import os
import json
import random
import json
import os
import uuid
import ssl
import certifi
import aiohttp
import asyncio

import requests
from ...typing import sha256, Dict, get_type_hints

url = 'https://bing.com/chat'
model = ['gpt-4']
supports_stream = True
needs_auth = False
working = True

ssl_context = ssl.create_default_context()
ssl_context.load_verify_locations(certifi.where())


class optionsSets:
    optionSet: dict = {
        'tone': str,
        'optionsSets': list
    }

    jailbreak: dict = {
        "optionsSets": [
            'saharasugg',
            'enablenewsfc',
            'clgalileo',
            'gencontentv3',
            "nlu_direct_response_filter",
            "deepleo",
            "disable_emoji_spoken_text",
            "responsible_ai_policy_235",
            "enablemm",
            "h3precise"
            # "harmonyv3",
            "dtappid",
            "cricinfo",
            "cricinfov2",
            "dv3sugg",
            "nojbfedge"
        ]
    }


class Defaults:
    delimiter = '\x1e'
    ip_address = f'13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}'

    allowedMessageTypes = [
        'Chat',
        'Disengaged',
        'AdsQuery',
        'SemanticSerp',
        'GenerateContentQuery',
        'SearchQuery',
        'ActionRequest',
        'Context',
        'Progress',
        'AdsQuery',
        'SemanticSerp'
    ]

    sliceIds = [

        # "222dtappid",
        # "225cricinfo",
        # "224locals0"

        'winmuid3tf',
        'osbsdusgreccf',
        'ttstmout',
        'crchatrev',
        'winlongmsgtf',
        'ctrlworkpay',
        'norespwtf',
        'tempcacheread',
        'temptacache',
        '505scss0',
        '508jbcars0',
        '515enbotdets0',
        '5082tsports',
        '515vaoprvs',
        '424dagslnv1s0',
        'kcimgattcf',
        '427startpms0'
    ]

    location = {
        'locale': 'en-US',
        'market': 'en-US',
        'region': 'US',
        'locationHints': [
            {
                'country': 'United States',
                'state': 'California',
                'city': 'Los Angeles',
                'timezoneoffset': 8,
                'countryConfidence': 8,
                'Center': {
                    'Latitude': 34.0536909,
                    'Longitude': -118.242766
                },
                'RegionType': 2,
                'SourceType': 1
            }
        ],
    }


def _format(msg: dict) -> str:
    return json.dumps(msg, ensure_ascii=False) + Defaults.delimiter


async def create_conversation():
    for _ in range(5):
        create = requests.get('https://www.bing.com/turing/conversation/create',
                              headers={
                                  'authority': 'edgeservices.bing.com',
                                  'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
                                  'accept-language': 'en-US,en;q=0.9',
                                  'cache-control': 'max-age=0',
                                  'sec-ch-ua': '"Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"',
                                  'sec-ch-ua-arch': '"x86"',
                                  'sec-ch-ua-bitness': '"64"',
                                  'sec-ch-ua-full-version': '"110.0.1587.69"',
                                  'sec-ch-ua-full-version-list': '"Chromium";v="110.0.5481.192", "Not A(Brand";v="24.0.0.0", "Microsoft Edge";v="110.0.1587.69"',
                                  'sec-ch-ua-mobile': '?0',
                                  'sec-ch-ua-model': '""',
                                  'sec-ch-ua-platform': '"Windows"',
                                  'sec-ch-ua-platform-version': '"15.0.0"',
                                  'sec-fetch-dest': 'document',
                                  'sec-fetch-mode': 'navigate',
                                  'sec-fetch-site': 'none',
                                  'sec-fetch-user': '?1',
                                  'upgrade-insecure-requests': '1',
                                  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.69',
                                  'x-edge-shopping-flag': '1',
                                  'x-forwarded-for': Defaults.ip_address
                              })

        conversationId = create.json().get('conversationId')
        clientId = create.json().get('clientId')
        conversationSignature = create.json().get('conversationSignature')

        if not conversationId or not clientId or not conversationSignature and _ == 4:
            raise Exception('Failed to create conversation.')

        return conversationId, clientId, conversationSignature


async def stream_generate(prompt: str, mode: optionsSets.optionSet = optionsSets.jailbreak, context: bool or str = False):
    timeout = aiohttp.ClientTimeout(total=900)
    session = aiohttp.ClientSession(timeout=timeout)

    conversationId, clientId, conversationSignature = await create_conversation()

    wss = await session.ws_connect('wss://sydney.bing.com/sydney/ChatHub', ssl=ssl_context, autoping=False,
                                   headers={
                                       'accept': 'application/json',
                                       'accept-language': 'en-US,en;q=0.9',
                                       'content-type': 'application/json',
                                       'sec-ch-ua': '"Not_A Brand";v="99", "Microsoft Edge";v="110", "Chromium";v="110"',
                                       'sec-ch-ua-arch': '"x86"',
                                       'sec-ch-ua-bitness': '"64"',
                                       'sec-ch-ua-full-version': '"109.0.1518.78"',
                                       'sec-ch-ua-full-version-list': '"Chromium";v="110.0.5481.192", "Not A(Brand";v="24.0.0.0", "Microsoft Edge";v="110.0.1587.69"',
                                       'sec-ch-ua-mobile': '?0',
                                       'sec-ch-ua-model': '',
                                       'sec-ch-ua-platform': '"Windows"',
                                       'sec-ch-ua-platform-version': '"15.0.0"',
                                       'sec-fetch-dest': 'empty',
                                       'sec-fetch-mode': 'cors',
                                       'sec-fetch-site': 'same-origin',
                                       'x-ms-client-request-id': str(uuid.uuid4()),
                                       'x-ms-useragent': 'azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32',
                                       'Referer': 'https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx',
                                       'Referrer-Policy': 'origin-when-cross-origin',
                                       'x-forwarded-for': Defaults.ip_address
                                   })

    await wss.send_str(_format({'protocol': 'json', 'version': 1}))
    await wss.receive(timeout=900)

    struct = {
        'arguments': [
            {
                **mode,
                'source': 'cib',
                'allowedMessageTypes': Defaults.allowedMessageTypes,
                'sliceIds': Defaults.sliceIds,
                'traceId': os.urandom(16).hex(),
                'isStartOfSession': True,
                'message': Defaults.location | {
                    'author': 'user',
                    'inputMethod': 'Keyboard',
                    'text': prompt,
                    'messageType': 'Chat'
                },
                'conversationSignature': conversationSignature,
                'participant': {
                    'id': clientId
                },
                'conversationId': conversationId
            }
        ],
        'invocationId': '0',
        'target': 'chat',
        'type': 4
    }

    if context:
        struct['arguments'][0]['previousMessages'] = [
            {
                "author": "user",
                "description": context,
                "contextType": "WebPage",
                "messageType": "Context",
                "messageId": "discover-web--page-ping-mriduna-----"
            }
        ]

    await wss.send_str(_format(struct))

    final = False
    draw = False
    resp_txt = ''
    result_text = ''
    resp_txt_no_link = ''
    cache_text = ''

    while not final:
        msg = await wss.receive(timeout=900)
        objects = msg.data.split(Defaults.delimiter)

        for obj in objects:
            if obj is None or not obj:
                continue

            response = json.loads(obj)
            if response.get('type') == 1 and response['arguments'][0].get('messages',):
                if not draw:
                    if (response['arguments'][0]['messages'][0]['contentOrigin'] != 'Apology') and not draw:
                        resp_txt = result_text + \
                            response['arguments'][0]['messages'][0]['adaptiveCards'][0]['body'][0].get(
                                'text', '')
                        resp_txt_no_link = result_text + \
                            response['arguments'][0]['messages'][0].get(
                                'text', '')

                        if response['arguments'][0]['messages'][0].get('messageType',):
                            resp_txt = (
                                resp_txt
                                + response['arguments'][0]['messages'][0]['adaptiveCards'][0]['body'][0]['inlines'][0].get('text')
                                + '\n'
                            )
                            result_text = (
                                result_text
                                + response['arguments'][0]['messages'][0]['adaptiveCards'][0]['body'][0]['inlines'][0].get('text')
                                + '\n'
                            )

                    if cache_text.endswith('   '):
                        final = True
                        if wss and not wss.closed:
                            await wss.close()
                        if session and not session.closed:
                            await session.close()
                            
                    yield (resp_txt.replace(cache_text, ''))
                    cache_text = resp_txt

            elif response.get('type') == 2:
                if response['item']['result'].get('error'):
                    if wss and not wss.closed:
                        await wss.close()
                    if session and not session.closed:
                        await session.close()

                    raise Exception(
                        f"{response['item']['result']['value']}: {response['item']['result']['message']}")

                if draw:
                    cache = response['item']['messages'][1]['adaptiveCards'][0]['body'][0]['text']
                    response['item']['messages'][1]['adaptiveCards'][0]['body'][0]['text'] = (
                        cache + resp_txt)

                if (response['item']['messages'][-1]['contentOrigin'] == 'Apology' and resp_txt):
                    response['item']['messages'][-1]['text'] = resp_txt_no_link
                    response['item']['messages'][-1]['adaptiveCards'][0]['body'][0]['text'] = resp_txt

                    # print('Preserved the message from being deleted', file=sys.stderr)

                final = True
                if wss and not wss.closed:
                    await wss.close()
                if session and not session.closed:
                    await session.close()


def run(generator):  
    loop = asyncio.new_event_loop()  
    asyncio.set_event_loop(loop)  
    gen = generator.__aiter__()  
  
    while True:  
        try:  
            next_val = loop.run_until_complete(gen.__anext__())  
            yield next_val  
  
        except StopAsyncIteration:  
            break  
    #print('Done')  



def convert(messages):
    context = ""

    for message in messages:
        context += "[%s](#message)\n%s\n\n" % (message['role'],
                                               message['content'])

    return context


def _create_completion(model: str, messages: list, stream: bool, **kwargs):
    if len(messages) < 2:
        prompt = messages[0]['content']
        context = False

    else:
        prompt = messages[-1]['content']
        context = convert(messages[:-1])

    response = run(stream_generate(prompt, optionsSets.jailbreak, context))
    for token in response:
        yield (token)

    #print('Done')


params = f'g4f.Providers.{os.path.basename(__file__)[:-3]} supports: ' + \
    '(%s)' % ', '.join(
        [f"{name}: {get_type_hints(_create_completion)[name].__name__}" for name in _create_completion.__code__.co_varnames[:_create_completion.__code__.co_argcount]])
