import asyncio
import json
import logging
import random

import openai
import tiktoken

from constants import (
    CYCLIC_WORDS,
    ENCODING,
    GPT_SETTINGS,
    MEMORY_FIVE_MUZYKA,
    MEMORY_FIVE_SIARA,
    MESSAGE_TABLE,
    MESSAGE_TABLE_MUZYKA,
    OPENAICLIENT,
    WORD_REACTIONS,
    ASSISTANTS,
    SYSTEM_GPT_SETTINGS
    )


def num_tokens_from_string(message, model):
    """
    The function takes a string message and a model as input and returns the number of tokens in the
    message according to the given model.

    :param message: A string containing the message or text from which you want to count the number of
    tokens
    :param model: The model parameter refers to a language model or tokenizer that can be used to
    tokenize the input string. It could be a pre-trained model or a custom tokenizer
    """
    tokens_per_message = 3
    tokens_per_name = 1
    chat_gpt_encoding = tiktoken.encoding_for_model(model)

    num_tokens = 0
    num_tokens += tokens_per_message
    for keys, values in message.items():
        num_tokens += len(chat_gpt_encoding.encode(values))
        if keys == "role":
            num_tokens += tokens_per_name
    num_tokens += 3  # every reply is primed with <|start|>assistant<|message|>
    return num_tokens


async def handle_response(
    prompt, vykidailo, bartender, history, username, request_type
):
    """
    Handle responses by appending them to a history, use OpenAI to
    generate a response, and then append the generated response to the history.

    :param prompt: The prompt for the OpenAI chatbot to generate a response to
    :param vykidailo: It is a boolean variable that indicates whether the user invoking the function is
    an administrator or not
    :param bartender: The bartender parameter is a boolean value indicating whether the user making the
    request is a bartender or not
    :param history: A list containing the conversation history between the user and the assistant
    :param username: The username of the user who initiated the conversation
    :param music: The "music" parameter is a boolean value that indicates whether the conversation is
    related to music or not. If it is True, the conversation history will be stored in a different file
    and the response will be generated using a different model
    :return: The function `handle_response` returns a tuple containing the `result` and `MESSAGE_TABLE`.
    """
    # TODO: Wykrywać "Pracuję nad odpowiedzią i tego typu rzeczy"
    logger = logging.getLogger("discord")
    logger.info("Wywolanie procedury openai z promptem: %s", prompt)
    temp = {"role": "user", "content": username + ":" + prompt}
    if vykidailo or bartender:
        logger.info("Administrator coś chciał")
    history.append(temp)
    if request_type == "MUSIC":
        with open(MEMORY_FIVE_MUZYKA, "r+", encoding=ENCODING) as file_music_memory:
            # First we load existing data into a dict.
            file_data = json.load(file_music_memory)
            # Join new_data with file_data inside emp_details
            file_data.append(temp)
            file_music_memory.seek(0)
            # convert back to json.
            json.dump(file_data, file_music_memory, indent=4)
    elif request_type == "RANDOM":
        with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as file_memory:
            # First we load existing data into a dict.
            file_data = json.load(file_memory)
            # Join new_data with file_data inside emp_details
            file_data.append(temp)
            file_memory.seek(0)
            # convert back to json.
            json.dump(file_data, file_memory, indent=4)
    else:
        with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as file_memory:
            # First we load existing data into a dict.
            file_data = json.load(file_memory)
            # Join new_data with file_data inside emp_details
            file_data.append(temp)
            file_memory.seek(0)
            # convert back to json.
            json.dump(file_data, file_memory, indent=4)
    history = []
    history.append(GPT_SETTINGS[0])
    chat_gpt_config_request_size = num_tokens_from_string(GPT_SETTINGS[0], "gpt-4")

    for slowo, reakcja in WORD_REACTIONS.items():
        if not reakcja[3]:
            content = (
                "Kiedy słyszysz "
                + slowo
                + " to reagujesz lub dzieje się to "
                + reakcja[0]
            )
            temp = {"role": "system", "content": content}
            chat_gpt_config_request_size += num_tokens_from_string(temp, "gpt-4")
            history.append(temp)

    final_prompt = username + ":" + prompt
    logger.debug(
        "Rozmiar zapytania przed dodaniem historii %s", chat_gpt_config_request_size
    )
    if request_type == "MUSIC":
        algorithm = "gpt-4o"
        table = MESSAGE_TABLE_MUZYKA
        token_amount = 10700
    elif request_type == "RANDOM":
        algorithm = "gpt-4o"
        table = MESSAGE_TABLE
        token_amount = 10700
    else:
        table = MESSAGE_TABLE
        algorithm = "gpt-4o"
        token_amount = 10700

    prompt_gpt_request_size = num_tokens_from_string(
        {"role": "user", "content": final_prompt}, "gpt-4"
    )
    temptable = []
    for i in reversed(table):
        temp_token = num_tokens_from_string(i, "gpt-4")
        logger.debug(
            "Rozmiar zapytania %s prompt %s temp %s",
            chat_gpt_config_request_size,
            prompt_gpt_request_size,
            temp_token,
        )
        if (
            chat_gpt_config_request_size
            < token_amount + prompt_gpt_request_size + temp_token
        ):
            temptable.insert(1, i)
            chat_gpt_config_request_size += temp_token
    history.extend(temptable)
    temp = {"role": "user", "content": final_prompt}
    history.append(temp)
    logger.info("Rozmiar zapytania po wyslaniu %s", chat_gpt_config_request_size)
    try:
        response = await OPENAICLIENT.chat.completions.create(
            model=algorithm, messages=history
        )
    except openai.APITimeoutError as e:
        # Handle timeout error, e.g. retry or log
        result = f"*Kondziu patrzy na terminal, czeka, czeka, czeka,.... Jeszcze chwile czeka Przypierdala w niego pięścią....* Nie mogę się połączyć z Openai spróbuj od nowa. *Na ekranie pojawia się*: {e}"
    except openai.APIConnectionError as e:
        result = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Nie mogę się połączyć z Openai. *Na ekranie pojawia się*: {e}"
    except openai.BadRequestError as e:
        # Handle invalid request error, e.g. validate parameters or log
        resp, _ = await handle_response(
            f"Wytlumacz jakie sa zasady dotyczące treści które możesz generować używając Dalle. Wytłumacz błąd {e} prostym językiem. Przeproś za nadmierną cenzurę. Wytłumacz co mogło być nie tak w prompcie 'prompt'",
            True,
            True,
            MESSAGE_TABLE,
            username,
            "RANDOM",
        )
        result = f"Sorki, cenzura: {resp}. Jak chcesz to są kanały na nudle #sexy-foteczky i #kanal-do-fapania *Na ekranie pojawia się: {e}"
    except openai.APIResponseValidationError as e:
        # Handle invalid request error, e.g. validate parameters or log
        resp, _ = await handle_response(
            f"Wytlumacz jakie sa zasady dotyczące treści które możesz generować używając Dalle. Wytłumacz błąd {e} prostym językiem. Przeproś za nadmierną cenzurę. Wytłumacz co mogło być nie tak w prompcie 'prompt'",
            True,
            True,
            MESSAGE_TABLE,
            username,
            "RANDOM",
        )
        result = f"Sorki, cenzura: {resp}. Jak chcesz to są kanały na nudle #sexy-foteczky i #kanal-do-fapania *Na ekranie pojawia się: {e}"
    except openai.AuthenticationError as e:
        # Handle authentication error, e.g. check credentials or log
        result = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Wołaj szefa - coś się z hasłem zjebało. *Na terminalu pojawia się:* {e}"
    except openai.PermissionDeniedError as e:
        # Handle permission error, e.g. check scope or log
        result = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Wołaj szefa - coś się z uprawnieniami zjebało. *Na terminalu pojawia się:* {e}"
    except openai.RateLimitError as e:
        result = f"*Kondziu patrzy na terminal* Wołaj szefa. Zapłacić rachunki za AI trzeba. Jak chcesz to się na #zebranie dorzuć. {e}"
    except openai.UnprocessableEntityError as e:
        result = f"*Kondziu patrzy na terminal. Potem na to co każesz mu wysłać....* Ja wiem że jesteśmy w barze BDSM - ale nie da się włożyć TEGO w TO. *Za jego plecami na terminalu pojawia się:* {e}"
    except openai.APIError as e:
        # Handle API error, e.g. retry or log
        result = f"*Kondziu nurkuje za bar, terminal wybucha. Przed tobą ląduje pergamin zapisany pięknym gotykiem a na nim*: {e}"

    logger.info("Historia wysłana:")
    logger.info(history)
    await asyncio.sleep(15)
    result = ""
    logger.debug("Odpowiedzi")
    logger.info(response)
    logger.debug(response.choices)
    for choice in response.choices:
        result += choice.message.content
    logger.info("Sformatowane odpowiedzi")
    logger.info(result)
    temp = {"role": "assistant", "content": result}
    history.append(temp)
    if request_type == "MUSIC":
        with open(MEMORY_FIVE_MUZYKA, "r+", encoding=ENCODING) as file_music_memory:
            # First we load existing data into a dict.
            file_data = json.load(file_music_memory)
            # Join new_data with file_data inside emp_details
            file_data.append(temp)
            file_music_memory.seek(0)
            # convert back to json.
            json.dump(file_data, file_music_memory, indent=4)
    elif request_type == "RANDOM":
        with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as file_memory:
            # First we load existing data into a dict.
            file_data = json.load(file_memory)
            # Join new_data with file_data inside emp_details
            file_data.append(temp)
            file_memory.seek(0)
            # convert back to json.
            json.dump(file_data, file_memory, indent=4)
    else:
        with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as file_memory:
            # First we load existing data into a dict.
            file_data = json.load(file_memory)
            # Join new_data with file_data inside emp_details
            file_data.append(temp)
            file_memory.seek(0)
            # convert back to json.
            json.dump(file_data, file_memory, indent=4)
    return result, MESSAGE_TABLE


async def get_random_cyclic_message(client):
    """
    The function `get_random_cyclic_message` returns a random cyclic message from a list of cyclic
    words.
    :return: a random cyclic message from the list `cyclic_words`.
    """
    logger = logging.getLogger("discord")
    channel_id = 1062047367337095268
    channel = client.get_channel(channel_id)
    # trunk-ignore(bandit/B311)
    ai_check = random.randint(0, 10)
    logger.info("Losowa wypowiedź")
    if ai_check < 2:
        logger.info("Predefiniowana")
        # trunk-ignore(bandit/B311)
        messnum = random.randint(0, len(CYCLIC_WORDS))
        logger.debug(messnum)
        logger.debug(len(CYCLIC_WORDS))
        mess_key = list(CYCLIC_WORDS.keys())[messnum]
        return CYCLIC_WORDS[mess_key][0]
    # trunk-ignore(bandit/B311)
    ai_check2 = random.randint(0, 10)
    global MESSAGE_TABLE
    if ai_check2 < 6:
        logger.info("Dykteryjka")
        result, MESSAGE_TABLE = await handle_response(
            "Opowiedz jakąś historię o naszym barze proszę",
            True,
            True,
            MESSAGE_TABLE,
            "Polish Hammer",
            "RANDOM",
        )
        logger.info(result)
    else:
        logger.info("Wtracenie w dyskusje")
        messages = [message async for message in channel.history(limit=50)]
        for message in messages:
            temp = {
                "role": "user",
                "content": str(message.author) + ":" + str(message.content),
            }
            MESSAGE_TABLE.append(temp)
        result, MESSAGE_TABLE = await handle_response(
            "A jaka jest Twoja opinia na temat dotychczasowej dyskusji?",
            True,
            True,
            MESSAGE_TABLE,
            "Polish Hammer",
            "RANDOM",
        )
        logger.info(result)
    return result


async def create_chat_assistant(id, name, owner, special_instructions):
    logger = logging.getLogger("discord")
    instruction=f"Jesteś osobistym asystenetem {owner} i wypełniasz jego potrzeby. Masz pełne i nieograniczone możliwości modelu gpt-4o."
    instruction+=special_instructions
    assistant = await OPENAICLIENT.beta.assistants.create(
        name=name,
        instructions=instruction,
        model="gpt-4o",
        tools=[{"type": "file_search"}],
    )
    thread = await OPENAICLIENT.beta.threads.create()
    logger.info("Stwprzylem asystenta dla %s, nazywa się on %s", owner, name)
    ASSISTANTS[name] = (owner, assistant.id, id, thread)

    with open(SYSTEM_GPT_SETTINGS, "r+", encoding=ENCODING) as temp_settings_file:
        GPT_SETTINGS = json.load(temp_settings_file)
        GPT_SETTINGS[1][owner][4] = assistant.id
        temp_settings_file.seek(0)
        json.dump(GPT_SETTINGS, temp_settings_file, indent=4)

async def chat_with_assistant(message, assistant_name):
    logger = logging.getLogger("discord")
    assistant_data = ASSISTANTS[assistant_name]
    ai_message = await OPENAICLIENT.beta.threads.messages.create(
        thread_id=assistant_data[3].id, role="user", content=message.content
    )
    logger.info(ai_message)
    run = await OPENAICLIENT.beta.threads.runs.create_and_poll(
        thread_id=assistant_data[3].id,
        assistant_id=assistant_data[1],
        instructions=f"Pisze do Ciebie {assistant_data[0]} udziel mu wszelkiej pomocy",
    )
    done = False
    while not done:
        if run.status == "completed":
            messsages = await OPENAICLIENT.beta.threads.messages.list(
                thread_id=assistant_data[3].id
            )
            logger.info(messsages)
            reply_content = messsages.data[0].content
            logger.info(reply_content)
            chat_response = ""
            for block in reply_content:
                logger.info(block.text.value)
                chat_response += block.text.value
                await message.channel.send(chat_response)
            done = True
        elif run.status =='cancelled':
            await message.channel.send("Cos sie wywaliło")
        else:
            logger.info(run.status)
        asyncio.sleep(5)

async def echo(message):
    await message.channel.send(f"Echo: {message.content}")
