import asyncio
import json
import logging
import random

import openai
import tiktoken
import time
from other_functions import discord_friendly_send
from constants import (
    AI_CONFIGS,
    ASSISTANTS,
    CLAUDECLIENT,
    CYCLIC_WORDS,
    DEFAULT_AI_CONFIG,
    ENCODING,
    GPT_SETTINGS,
    MEMORY_FIVE_MUZYKA,
    MEMORY_FIVE_SIARA,
    MESSAGE_TABLE,
    MESSAGE_TABLE_MUZYKA,
    OPENAICLIENT,
    SYSTEM_GPT_SETTINGS,
    WORD_REACTIONS,
    CHEAP_MODEL,
    LATEST_MODEL
)

try:
    import anthropic
except ImportError:  # pragma: no cover - optional at runtime
    anthropic = None

# this do per user
VECTOR_STORE_ID = -1


# *=========================================== AI provider abstraction
# The AI cog talks to exactly one backend at a time, chosen by _ACTIVE_CONFIG.
# Legacy defaults ("gpt"/OpenAI) keep the historical behaviour byte-for-byte;
# selecting a "claude" config routes the same handle_response pipeline through
# the Anthropic Messages API instead. Backend-specific exceptions are funnelled
# into a single AIError so handle_response can keep its one set of in-character
# error replies regardless of provider.
_ACTIVE_CONFIG_NAME = DEFAULT_AI_CONFIG

# Legacy default algorithm strings that mean "let the bot pick" rather than
# "force this exact model" - so a caller that still passes the old gpt-4o
# default auto-selects the active provider's model instead of 400-ing on Claude.
_AUTO_ALGOS = {"", "auto", "gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"}


class AIError(Exception):
    """Provider-neutral wrapper so handle_response reacts to one exception type.

    ``category`` is one of: timeout, connection, bad_request,
    response_validation, auth, permission, rate_limit, unprocessable, api.
    ``original`` is the underlying SDK exception (interpolated into replies).
    """

    def __init__(self, category: str, original: Exception):
        super().__init__(str(original))
        self.category = category
        self.original = original


def _active_config() -> dict:
    return (
        AI_CONFIGS.get(_ACTIVE_CONFIG_NAME)
        or AI_CONFIGS.get("gpt")
        or next(iter(AI_CONFIGS.values()))
    )


def list_ai_configs():
    """Selectable config names (templates prefixed with '_' are hidden)."""
    return [name for name in AI_CONFIGS if not name.startswith("_")]


def get_active_ai_config() -> str:
    return _ACTIVE_CONFIG_NAME


def set_active_ai_config(name: str) -> dict:
    """Switch the active AI backend and persist the choice. Raises on error."""
    global _ACTIVE_CONFIG_NAME
    if name not in AI_CONFIGS:
        raise KeyError(name)
    cfg = AI_CONFIGS[name]
    provider = cfg.get("provider")
    if provider == "anthropic" and CLAUDECLIENT is None:
        raise RuntimeError("klient Anthropic nie jest skonfigurowany (brak ANTHROPIC_API_KEY)")
    if provider == "openai" and OPENAICLIENT is None:
        raise RuntimeError("klient OpenAI nie jest skonfigurowany (brak OPENAI_API_KEY)")
    _ACTIVE_CONFIG_NAME = name
    _persist_active_ai_config(name)
    return cfg


def _persist_active_ai_config(name: str) -> None:
    """Best-effort write of the active-config choice into system_gpt_settings.json.

    Keeps the historical two-element structure intact: updates index 2 if it
    already exists, appends it when the file has exactly the original two
    elements, and otherwise leaves the file untouched (the in-memory switch
    still applies).
    """
    logger = logging.getLogger("discord")
    try:
        with open(SYSTEM_GPT_SETTINGS, "r", encoding=ENCODING) as handle:
            data = json.load(handle)
    except (OSError, json.JSONDecodeError) as exc:
        logger.warning("Nie mogę odczytać %s do zapisu configu AI: %s", SYSTEM_GPT_SETTINGS, exc)
        return
    if not isinstance(data, list) or len(data) < 2:
        logger.warning("Nietypowa struktura %s - pomijam zapis configu AI", SYSTEM_GPT_SETTINGS)
        return
    if len(data) > 2 and isinstance(data[2], dict):
        data[2]["active"] = name
        data[2].setdefault("configs", AI_CONFIGS)
    else:
        data = data[:2] + [{"active": name, "configs": AI_CONFIGS}]
    try:
        with open(SYSTEM_GPT_SETTINGS, "w", encoding=ENCODING) as handle:
            json.dump(data, handle, indent=4, ensure_ascii=False)
    except OSError as exc:
        logger.warning("Nie mogę zapisać configu AI do %s: %s", SYSTEM_GPT_SETTINGS, exc)


def _map_openai_error(exc: Exception) -> AIError:
    mapping = [
        (openai.APITimeoutError, "timeout"),
        (openai.APIConnectionError, "connection"),
        (openai.BadRequestError, "bad_request"),
        (openai.APIResponseValidationError, "response_validation"),
        (openai.AuthenticationError, "auth"),
        (openai.PermissionDeniedError, "permission"),
        (openai.RateLimitError, "rate_limit"),
        (openai.UnprocessableEntityError, "unprocessable"),
        (openai.APIError, "api"),
    ]
    for cls, category in mapping:
        if isinstance(exc, cls):
            return AIError(category, exc)
    return AIError("api", exc)


def _map_anthropic_error(exc: Exception) -> AIError:
    mapping = [
        ("APITimeoutError", "timeout"),
        ("APIConnectionError", "connection"),
        ("BadRequestError", "bad_request"),
        ("APIResponseValidationError", "response_validation"),
        ("AuthenticationError", "auth"),
        ("PermissionDeniedError", "permission"),
        ("RateLimitError", "rate_limit"),
        ("UnprocessableEntityError", "unprocessable"),
        ("APIError", "api"),
    ]
    for name, category in mapping:
        cls = getattr(anthropic, name, None)
        if cls and isinstance(exc, cls):
            return AIError(category, exc)
    return AIError("api", exc)


def _to_anthropic_messages(messages):
    """Split OpenAI-style messages into (system_prompt, alternating convo).

    Claude takes the system prompt as a separate parameter (not a role in the
    messages list) and requires the conversation to open with a user turn, so
    system messages are concatenated out and any leading assistant turns are
    dropped.
    """
    system_parts = []
    convo = []
    for msg in messages:
        role = msg.get("role")
        content = msg.get("content", "")
        if role == "system":
            system_parts.append(content)
        else:
            convo.append(
                {"role": "assistant" if role == "assistant" else "user", "content": content}
            )
    while convo and convo[0]["role"] != "user":
        convo.pop(0)
    if not convo:
        convo = [{"role": "user", "content": " "}]
    return "\n\n".join(part for part in system_parts if part), convo


async def _anthropic_call(messages, model, cfg):
    """Claude counterpart of openai_call. Returns a plain string."""
    if CLAUDECLIENT is None:
        raise AIError("auth", RuntimeError("klient Anthropic nie jest skonfigurowany"))
    system_prompt, convo = _to_anthropic_messages(messages)
    kwargs = {
        "model": model,
        "max_tokens": int(cfg.get("max_tokens", 2048)),
        "messages": convo,
    }
    if system_prompt:
        kwargs["system"] = system_prompt
    # NOTE: temperature is deliberately omitted - Opus 4.8 / Sonnet 5 reject
    # sampling params with a 400.
    try:
        resp = await CLAUDECLIENT.messages.create(**kwargs)
    except Exception as exc:  # pylint: disable=broad-except
        raise _map_anthropic_error(exc)
    text = "".join(
        block.text for block in resp.content if getattr(block, "type", None) == "text"
    )
    return text.strip()


async def provider_generate(messages, model, temperature=0.2):
    """Dispatch a chat completion to the active backend, normalising errors."""
    cfg = _active_config()
    try:
        if cfg.get("provider") == "anthropic":
            return await _anthropic_call(messages, model, cfg)
        return await openai_call(messages, model, temperature)
    except AIError:
        raise
    except Exception as exc:  # pylint: disable=broad-except
        # Only the OpenAI path reaches here un-normalised (_anthropic_call
        # already wraps its own errors).
        raise _map_openai_error(exc)


def select_model(req_type: str, algo: str) -> str:
    cfg = _active_config()
    latest = cfg.get("latest_model", LATEST_MODEL)
    cheap = cfg.get("cheap_model", CHEAP_MODEL)
    algo_str = (algo or "").strip()
    # An explicit, non-legacy model id is honoured verbatim; anything in
    # _AUTO_ALGOS (incl. the old gpt-4o default) means "auto pick for the
    # active provider", so flipping the switch actually changes the model.
    if algo_str and algo_str.lower() not in _AUTO_ALGOS:
        return algo_str
    if req_type == "MUSIC":
        return cheap
    return latest


async def openai_call(messages, model, temperature=0.2):
    """
    Responses API dla 4o/4.1*, fallback Chat Completions dla gpt-3.5-turbo.
    Zwraca czysty string odpowiedzi.
    """
    logger = logging.getLogger("discord")

    if model.startswith("gpt-3.5"):
        logger.info("3.5")
        # legacy path – bez zmian w Twoim kodzie wyżej/niżej
        resp = await OPENAICLIENT.chat.completions.create(
            model=model,
            messages=messages,
            temperature=temperature,
        )
        result = ""
        for choice in resp.choices:
            result += choice.message.content
        return result.strip()

    else:
        logger.info("4.0+")
        # Responses API (zalecane dla 4o/4.1*)
        resp = await OPENAICLIENT.responses.create(
            model=model,
            temperature=temperature,
            input=messages,
        )
        # SDK zapewnia output_text dla zwykłych odpowiedzi
        return (getattr(resp.output_text, "output_text", None) or "").strip() or str(
            resp.output_text
        )


def create_vector_store():
    # Create a vector store caled "Financial Statements"
    return OPENAICLIENT.beta.vector_stores.create_and_poll(name="Hammer Stash")
    # expires_after={
    # "anchor": "last_active_at",
    # "days": 7}
    # )


def upload_files_to_vector_store(assistant):

    # Ready the files for upload to OpenAI
    file_paths = ["edgar/goog-10k.pdf", "edgar/brka-10k.txt"]
    file_streams = [open(path, "rb") for path in file_paths]

    # file = client.beta.vector_stores.files.create_and_poll(
    # vector_store_id="vs_abc123",
    # file_id="file-abc123"
    # )
    # batch = client.beta.vector_stores.file_batches.create_and_poll(
    # vector_store_id="vs_abc123",
    # file_ids=['file_1', 'file_2', 'file_3', 'file_4', 'file_5']
    # )

    # Use the upload and poll SDK helper to upload the files, add them to the vector store,
    # and poll the status of the file batch for completion.
    file_batch = OPENAICLIENT.beta.vector_stores.file_batches.upload_and_poll(
        vector_store_id=VECTOR_STORE_ID, files=file_streams
    )

    # You can print the status and the file counts of the batch to see the result of this operation.
    print(file_batch.status)
    print(file_batch.file_counts)
    assistant = OPENAICLIENT.beta.assistants.update(
        assistant_id=assistant.id,
        tool_resources={"file_search": {"vector_store_ids": [VECTOR_STORE_ID]}},
    )


def delete_files_from_vector_store(assistant, file_id):
    result = OPENAICLIENT.beta.vector_stores.file_batches.delete(
        vector_store_id=VECTOR_STORE_ID, files=file_id
    )

    # You can print the status and the file counts of the batch to see the result of this operation.
    print(result)
    assistant = OPENAICLIENT.beta.assistants.update(
        assistant_id=assistant.id,
        tool_resources={"file_search": {"vector_store_ids": [VECTOR_STORE_ID]}},
    )


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,
    algorithm="gpt-4o",
    none_request="",
    internal_retry: bool = False
):
    """
    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
    :param request_type: The type of request being made, which can be "MUSIC", "RANDOM", "NONE" or "GENERAL"
        GENERAL is for regular conversations, MUSIC is for music-related requests,
        RANDOM is for random requests, and NONE is to not store the request in memory
    :param algorithm: The algorithm to be used for generating the response, default is "gpt-4o"
    :return: The function `handle_response` returns a tuple containing the `result` and `MESSAGE_TABLE`.
    """
    logger = logging.getLogger("discord")
    logger.info("Wywolanie procedury openai z promptem: %s", prompt)
    if vykidailo or bartender:
        logger.info("Administrator coś chciał")
    model_to_use = select_model(request_type, algorithm)
    logger.info("Wybrany model: %s", model_to_use)
    if request_type == "MUSIC" and model_to_use == "gpt-4o-mini":
        try:
            # nic — normalnie pójdzie Responses API
            pass
        except Exception:
            model_to_use = "gpt-3.5-turbo"
    # --- 2) Budowa historii (token budget + reguły systemowe) ---
    # NOTE: ignorujemy przekazany 'history' jako listę (tak było wcześniej),
    # ale zwracamy aktualną tablicę do nadpisania w miejscach wołania (back-compat).
    base_system = GPT_SETTINGS[0]  # zakładamy {"role":"system","content":...}
    history_msgs = []

    if request_type != "NONE":
        history_msgs.append(base_system)
        chat_gpt_config_request_size = num_tokens_from_string(base_system, "gpt-4")

        # Dynamiczne mikro-reguły (WORD_REACTIONS), jak w Twoim kodzie
        for slowo, reakcja in WORD_REACTIONS.items():
            if not reakcja[3]:
                content = f"Kiedy słyszysz {slowo} to reagujesz lub dzieje się to {reakcja[0]}"
                sys_msg = {"role": "system", "content": content}
                chat_gpt_config_request_size += num_tokens_from_string(sys_msg, "gpt-4")
                history_msgs.append(sys_msg)

        # wybór właściwej tablicy pamięci i budżetu
        if request_type == "MUSIC":
            table = MESSAGE_TABLE_MUZYKA
            token_amount = 10700
        elif request_type in ("RANDOM", "GENERAL"):
            table = MESSAGE_TABLE
            token_amount = 10700
        else:
            table = []
            token_amount = 10000

        # doklejanie historii od końca aż do limitu (zachowana kolejność czasowa)
        final_prompt = f"{username}:{prompt}"
        prompt_gpt_request_size = num_tokens_from_string({"role": "user", "content": final_prompt}, "gpt-4")

        acc = []
        for msg in reversed(table):
            t = num_tokens_from_string(msg, "gpt-4")
            if chat_gpt_config_request_size + prompt_gpt_request_size + t <= token_amount:
                acc.append(msg)
                chat_gpt_config_request_size += t
            else:
                break
        # przywróć chronologicznie
        history_msgs.extend(reversed(acc))

        # aktualny prompt
        history_msgs.append({"role": "user", "content": final_prompt})
        logger.info("Rozmiar zapytania (tok): %s", prompt_gpt_request_size)  # tokeny już policzone wyżej

    else:
        # --- tryb NONE: nie dotykamy pamięci i pozwalamy przekazać własny 'none_request' ---
        if isinstance(none_request, list):
            history_msgs = none_request
        elif isinstance(none_request, str) and none_request.strip():
            history_msgs = [{"role": "user", "content": none_request}]
        else:
            history_msgs = [{"role": "user", "content": f"{username}:{prompt}"}]

        logger.info("Rozmiar zapytania (tok): %s", "n/a")  # tokeny już policzone wyżej

    try:
        # ...przygotowanie messages/system prompt/itp. jak masz...
        # retry/backoff + deadline (zachowuje Twoją semantykę logowania)
        timeout_sec = 120
        deadline = time.time() + timeout_sec
        response = await asyncio.wait_for(
            provider_generate(messages=history_msgs, model=model_to_use),
            timeout=max(0.1, deadline - time.time()),
        )

    except AIError as e:
        # One handler for both backends; e.category is provider-neutral and
        # e.original is the underlying SDK exception (kept for the {..} tails).
        err = e.original
        if e.category == "timeout":
            response = 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ę*: {err}"
        elif e.category == "connection":
            response = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Nie mogę się połączyć z Openai. *Na ekranie pojawia się*: {err}"
        elif e.category in ("bad_request", "response_validation"):
            # Handle invalid request error, e.g. validate parameters or log
            if internal_retry:
                resp = "Nie umiem tego teraz ładnie wytłumaczyć — OpenAI mnie zastrzeliło."
            else:
                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 {err} prostym językiem. Przeproś za nadmierną cenzurę. Wytłumacz co mogło być nie tak w prompcie 'prompt'",
                    True,
                    True,
                    MESSAGE_TABLE,
                    username,
                    "RANDOM",
                    internal_retry=True,
                )
            response = f"Sorki, cenzura: {resp}. Jak chcesz to są kanały na nudle #sexy-foteczky i #kanal-do-fapania *Na ekranie pojawia się: {err}"
        elif e.category == "auth":
            # Handle authentication error, e.g. check credentials or log
            response = 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ę:* {err}"
        elif e.category == "permission":
            # Handle permission error, e.g. check scope or log
            response = 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ę:* {err}"
        elif e.category == "rate_limit":
            response = f"*Kondziu patrzy na terminal* Wołaj szefa. Zapłacić rachunki za AI trzeba. Jak chcesz to się na #zebranie dorzuć. {err}"
        elif e.category == "unprocessable":
            response = 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ę:* {err}"
        else:  # "api" and anything unmapped
            # Handle API error, e.g. retry or log
            response = f"*Kondziu nurkuje za bar, terminal wybucha. Przed tobą ląduje pergamin zapisany pięknym gotykiem a na nim*: {err}"

    logger.info("Historia wysłana:")
    temp_assistant = {"role": "assistant", "content": response}
    logger.info(temp_assistant)
    if request_type == "MUSIC":
        # zapis do pliku MUZYKA
        with open(MEMORY_FIVE_MUZYKA, "r+", encoding=ENCODING) as fh:
            file_data = json.load(fh)
            file_data.append({"role": "user", "content": f"{username}:{prompt}"})
            file_data.append(temp_assistant)
            fh.seek(0)
            json.dump(file_data, fh, indent=4)
        return response, MESSAGE_TABLE_MUZYKA

    elif request_type in ("RANDOM", "GENERAL"):
        with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as fh:
            file_data = json.load(fh)
            file_data.append({"role": "user", "content": f"{username}:{prompt}"})
            file_data.append(temp_assistant)
            fh.seek(0)
            json.dump(file_data, fh, indent=4)
        return response, MESSAGE_TABLE

    else:  # NONE
        return response, []


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(owner_id, 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_id][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 discord_friendly_send(message.channel, chat_response)
                # await message.channel.send(chat_response)
            done = True
        elif run.status == "cancelled":
            await discord_friendly_send(message.channel, "Cos sie wywaliło")
        else:
            logger.info(run.status)
        asyncio.sleep(5)


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