import logging
import mimetypes
import os
import json
from typing import Dict, List, Optional, Any

from mattermostdriver import Driver
from mnemosynecore.vault.univ_conn import un_conn

_RU_TO_LAT = {
    "а": "a",
    "б": "b",
    "в": "v",
    "г": "g",
    "д": "d",
    "е": "e",
    "ё": "e",
    "ж": "zh",
    "з": "z",
    "и": "i",
    "й": "i",
    "к": "k",
    "л": "l",
    "м": "m",
    "н": "n",
    "о": "o",
    "п": "p",
    "р": "r",
    "с": "s",
    "т": "t",
    "у": "u",
    "ф": "f",
    "х": "h",
    "ц": "ts",
    "ч": "ch",
    "ш": "sh",
    "щ": "sch",
    "ъ": "",
    "ы": "y",
    "ь": "",
    "э": "e",
    "ю": "yu",
    "я": "ya",
}

_DIGIT_TO_EMOJI = {
    "0": "zero",
    "1": "one",
    "2": "two",
    "3": "three",
    "4": "four",
    "5": "five",
    "6": "six",
    "7": "seven",
    "8": "eight",
    "9": "nine",
}


def _expand_text_for_letter_emojis(
    text: str,
    *,
    translit_ru: bool,
    char_overrides: Optional[Dict[str, str]] = None,
) -> str:
    if char_overrides and not isinstance(char_overrides, dict):
        raise TypeError("char_overrides должен быть dict или None")

    pieces: List[str] = []
    for ch in text:
        if char_overrides and ch in char_overrides:
            pieces.append(str(char_overrides[ch]))
            continue
        if translit_ru:
            lat = _RU_TO_LAT.get(ch.lower())
            if lat is not None:
                pieces.append(lat)
                continue
        pieces.append(ch)
    return "".join(pieces)


def _validate_unknown_policy(unknown_policy: str) -> None:
    if unknown_policy not in ("keep", "skip", "raise"):
        raise ValueError("unknown_policy должен быть one of: keep, skip, raise")


def _emoji_name_for_char(
    ch: str,
    *,
    letter_suffix: str,
    space_emoji: str,
    digits_as_words: bool,
) -> Optional[str]:
    if ch.isspace():
        return space_emoji

    low = ch.lower()
    if "a" <= low <= "z":
        return f"{low}{letter_suffix}"
    if low.isdigit():
        if digits_as_words:
            return _DIGIT_TO_EMOJI.get(low)
        return f"{low}{letter_suffix}"
    return None


def _build_letter_emoji_tokens(
    text: str,
    *,
    letter_suffix: str,
    space_emoji: str,
    translit_ru: bool,
    digits_as_words: bool,
    unknown_policy: str,
    char_overrides: Optional[Dict[str, str]] = None,
) -> List[str]:
    _validate_unknown_policy(unknown_policy)
    normalized = _expand_text_for_letter_emojis(
        text,
        translit_ru=translit_ru,
        char_overrides=char_overrides,
    )

    tokens: List[str] = []
    for ch in normalized:
        emoji_name = _emoji_name_for_char(
            ch,
            letter_suffix=letter_suffix,
            space_emoji=space_emoji,
            digits_as_words=digits_as_words,
        )
        if emoji_name:
            tokens.append(f":{emoji_name}:")
            continue

        if unknown_policy == "skip":
            continue
        if unknown_policy == "raise":
            raise ValueError(
                f"Символ '{ch}' не поддерживается для letter-emoji. "
                "Используйте unknown_policy='keep' или добавьте char_overrides."
            )
        tokens.append(ch)
    return tokens


def _build_letter_emoji_names(
    text: str,
    *,
    letter_suffix: str,
    space_emoji: str,
    translit_ru: bool,
    digits_as_words: bool,
    unknown_policy: str,
    include_spaces: bool,
    char_overrides: Optional[Dict[str, str]] = None,
) -> List[str]:
    _validate_unknown_policy(unknown_policy)
    normalized = _expand_text_for_letter_emojis(
        text,
        translit_ru=translit_ru,
        char_overrides=char_overrides,
    )
    names: List[str] = []
    for ch in normalized:
        emoji_name = _emoji_name_for_char(
            ch,
            letter_suffix=letter_suffix,
            space_emoji=space_emoji,
            digits_as_words=digits_as_words,
        )
        if emoji_name:
            if emoji_name == space_emoji and not include_spaces:
                continue
            names.append(emoji_name)
            continue

        if unknown_policy == "raise":
            raise ValueError(
                f"Символ '{ch}' не поддерживается для реакций letter-emoji. "
                "Используйте unknown_policy='skip' или добавьте char_overrides."
            )
    return names


def _resolve_mattermost_user_id(driver: Driver) -> str:
    client = getattr(driver, "client", None)
    user_id = getattr(client, "userid", None)
    if user_id:
        return str(user_id)

    users = getattr(driver, "users", None)
    if users and hasattr(users, "get_user"):
        me = users.get_user("me")
        if isinstance(me, dict) and me.get("id"):
            return str(me["id"])

    raise ValueError("Не удалось определить user_id для отправки реакций")


def get_mattermost_driver(bot_id: str) -> Driver:
    if bot_id.startswith('{'):
        try:
            cfg = json.loads(bot_id)
        except json.JSONDecodeError:
            raise ValueError("Неверный формат JSON для конфигурации Mattermost")

        driver = Driver({
            "url": cfg["host"],
            "token": cfg["password"],
            "scheme": cfg.get("schema", "https"),
            "port": int(cfg.get("port", 443)),
            "basepath": cfg.get("basepath", "/api/v4"),
        })
        driver.login()
        return driver

    return un_conn(bot_id, "mattermost")


def _resolve_test_bot_id(
    bot_id: str,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
) -> str:
    from mnemosynecore.vault.client import get_secret_test

    raw = bot_id.strip()
    if raw.startswith("{"):
        try:
            json.loads(raw)
        except json.JSONDecodeError as exc:
            raise ValueError("Неверный формат JSON для конфигурации Mattermost") from exc
        return raw

    if creds_path is None:
        cfg = get_secret_test(bot_id, dir_path=dir_path)
    else:
        cfg = get_secret_test(
            bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        )
    return json.dumps(cfg)


def get_mattermost_driver_test(
    bot_id: str,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
) -> Driver:
    return get_mattermost_driver(
        _resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        )
    )


def send_message(
    *,
    channel_id: str,
    bot_id: str,
    text: str,
    root_id: Optional[str] = None,
    silent: bool = False,
) -> None:
    post_message(
        channel_id=channel_id,
        bot_id=bot_id,
        text=text,
        root_id=root_id,
        silent=silent,
    )


def post_message(
    *,
    channel_id: str,
    bot_id: str,
    text: str,
    root_id: Optional[str] = None,
    props: Optional[Dict[str, object]] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    driver = get_mattermost_driver(bot_id)
    options = {"channel_id": channel_id, "message": text.strip()}
    if root_id:
        options["root_id"] = root_id
    if props:
        options["props"] = props

    try:
        post = driver.posts.create_post(options=options)
        post_id = post.get("id") if isinstance(post, dict) else None
        if not silent:
            if root_id:
                logging.info(
                    "Сообщение отправлено в тред Mattermost: channel_id=%s, root_id=%s",
                    channel_id,
                    root_id,
                )
            else:
                logging.info("Сообщение отправлено в Mattermost: %s", channel_id)
        return {
            "channel_id": channel_id,
            "post_id": post_id,
            "root_id": root_id,
        }
    except Exception as exc:
        logging.exception(
            "Ошибка отправки сообщения в Mattermost (channel_id=%s)", channel_id
        )
        raise


def send_thread_message(
    *,
    channel_id: str,
    bot_id: str,
    root_id: str,
    text: str,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    if not root_id:
        raise ValueError("root_id обязателен для отправки сообщения в тред")
    return post_message(
        channel_id=channel_id,
        bot_id=bot_id,
        text=text,
        root_id=root_id,
        silent=silent,
    )


def send_thread_messages(
    *,
    channel_id: str,
    bot_id: str,
    root_id: str,
    messages: List[str],
    silent: bool = False,
    continue_on_error: bool = False,
) -> Dict[str, List[Dict[str, Optional[str]]]]:
    if not root_id:
        raise ValueError("root_id обязателен для отправки сообщений в тред")
    sent: List[Dict[str, Optional[str]]] = []
    failed: List[Dict[str, Optional[str]]] = []
    for message in messages:
        try:
            result = send_thread_message(
                channel_id=channel_id,
                bot_id=bot_id,
                root_id=root_id,
                text=message,
                silent=silent,
            )
            sent.append(result)
        except Exception as exc:
            if continue_on_error:
                failed.append(
                    {
                        "channel_id": channel_id,
                        "root_id": root_id,
                        "post_id": None,
                        "error": str(exc),
                    }
                )
                continue
            raise
    return {"sent": sent, "failed": failed}


def send_file_bytes(
    *,
    channel_id: str,
    bot_id: str,
    file_name: str,
    file_bytes: bytes,
    text: str = "",
    mime_type: Optional[str] = None,
    root_id: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    if not file_name:
        raise ValueError("file_name обязателен")
    if not isinstance(file_bytes, (bytes, bytearray)):
        raise TypeError("file_bytes должен быть bytes или bytearray")

    payload_bytes = bytes(file_bytes)
    mm_mime_type = mime_type or "application/octet-stream"

    driver = get_mattermost_driver(bot_id)
    upload = driver.files.upload_file(
        channel_id=channel_id,
        files={"files": (file_name, payload_bytes, mm_mime_type)},
    )

    file_infos = upload.get("file_infos") if isinstance(upload, dict) else None
    if not file_infos:
        raise ValueError("Mattermost не вернул file_infos после загрузки файла")

    first_file = file_infos[0]
    file_id = first_file.get("id") if isinstance(first_file, dict) else str(first_file)
    message = text.strip() if text else " "

    post = driver.posts.create_post(
        options={
            "channel_id": channel_id,
            "message": message,
            "file_ids": [file_id],
            **({"root_id": root_id} if root_id else {}),
        }
    )
    post_id = post.get("id") if isinstance(post, dict) else None

    if not silent:
        logging.info(
            "Файл отправлен в Mattermost: channel_id=%s, file_name=%s",
            channel_id,
            file_name,
        )
    return {
        "channel_id": channel_id,
        "file_name": file_name,
        "file_id": file_id,
        "post_id": post_id,
        "root_id": root_id,
    }


def send_file(
    *,
    channel_id: str,
    bot_id: str,
    file_path: str,
    text: str = "",
    file_name: Optional[str] = None,
    mime_type: Optional[str] = None,
    root_id: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    if not os.path.isfile(file_path):
        raise FileNotFoundError(file_path)

    final_file_name = file_name or os.path.basename(file_path)
    final_mime_type = mime_type or mimetypes.guess_type(final_file_name)[0] or "application/octet-stream"

    with open(file_path, "rb") as f:
        payload = f.read()

    return send_file_bytes(
        channel_id=channel_id,
        bot_id=bot_id,
        file_name=final_file_name,
        file_bytes=payload,
        text=text,
        mime_type=final_mime_type,
        root_id=root_id,
        silent=silent,
    )


def send_files(
    *,
    channel_id: str,
    bot_id: str,
    file_paths: List[str],
    text: str = "",
    root_id: Optional[str] = None,
    silent: bool = False,
) -> List[Dict[str, Optional[str]]]:
    if not file_paths:
        return []
    results = []
    for file_path in file_paths:
        result = send_file(
            channel_id=channel_id,
            bot_id=bot_id,
            file_path=file_path,
            text=text,
            root_id=root_id,
            silent=silent,
        )
        results.append(result)
    return results


def send_dataframe_as_csv(
    *,
    channel_id: str,
    bot_id: str,
    df,
    file_name: str = "dataframe.csv",
    text: str = "",
    include_index: bool = False,
    encoding: str = "utf-8",
    root_id: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    import pandas as pd

    if not isinstance(df, pd.DataFrame):
        raise TypeError("df должен быть pandas.DataFrame")

    csv_bytes = df.to_csv(index=include_index).encode(encoding)
    return send_file_bytes(
        channel_id=channel_id,
        bot_id=bot_id,
        file_name=file_name,
        file_bytes=csv_bytes,
        text=text,
        mime_type="text/csv",
        root_id=root_id,
        silent=silent,
    )


def send_dataframe_preview(
    *,
    channel_id: str,
    bot_id: str,
    df,
    title: str = "",
    max_rows: int = 10,
    max_cols: int = 10,
    root_id: Optional[str] = None,
    silent: bool = False,
) -> None:
    import pandas as pd

    if not isinstance(df, pd.DataFrame):
        raise TypeError("df должен быть pandas.DataFrame")
    if max_rows < 1 or max_cols < 1:
        raise ValueError("max_rows и max_cols должны быть >= 1")

    preview = df.head(max_rows).iloc[:, :max_cols]
    preview_text = preview.to_string(index=False)
    header = f"{title.strip()}\n" if title else ""
    text = (
        f"{header}"
        f"rows={len(df)}, cols={len(df.columns)}, preview_rows={len(preview)}, preview_cols={len(preview.columns)}\n"
        f"```\n{preview_text}\n```"
    )
    send_message(
        channel_id=channel_id,
        bot_id=bot_id,
        text=text,
        root_id=root_id,
        silent=silent,
    )


def text_to_letter_emojis(
    text: str,
    *,
    letter_suffix: str = "_letter",
    space_emoji: str = "space",
    translit_ru: bool = True,
    digits_as_words: bool = True,
    unknown_policy: str = "keep",
    char_overrides: Optional[Dict[str, str]] = None,
) -> str:
    if not isinstance(text, str):
        raise TypeError("text должен быть строкой")
    tokens = _build_letter_emoji_tokens(
        text=text,
        letter_suffix=letter_suffix,
        space_emoji=space_emoji,
        translit_ru=translit_ru,
        digits_as_words=digits_as_words,
        unknown_policy=unknown_policy,
        char_overrides=char_overrides,
    )
    return " ".join(tokens)


def send_message_as_letter_emojis(
    *,
    channel_id: str,
    bot_id: str,
    text: str,
    root_id: Optional[str] = None,
    letter_suffix: str = "_letter",
    space_emoji: str = "space",
    translit_ru: bool = True,
    digits_as_words: bool = True,
    unknown_policy: str = "keep",
    char_overrides: Optional[Dict[str, str]] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    emoji_text = text_to_letter_emojis(
        text=text,
        letter_suffix=letter_suffix,
        space_emoji=space_emoji,
        translit_ru=translit_ru,
        digits_as_words=digits_as_words,
        unknown_policy=unknown_policy,
        char_overrides=char_overrides,
    )
    return post_message(
        channel_id=channel_id,
        bot_id=bot_id,
        text=emoji_text,
        root_id=root_id,
        silent=silent,
    )


def add_letter_emoji_reactions(
    *,
    post_id: str,
    bot_id: str,
    text: str,
    letter_suffix: str = "_letter",
    space_emoji: str = "space",
    translit_ru: bool = True,
    digits_as_words: bool = True,
    include_spaces: bool = False,
    unknown_policy: str = "skip",
    char_overrides: Optional[Dict[str, str]] = None,
    skip_duplicates: bool = True,
    continue_on_error: bool = True,
    silent: bool = False,
) -> Dict[str, Any]:
    if not post_id:
        raise ValueError("post_id обязателен")
    if not isinstance(text, str) or not text.strip():
        raise ValueError("text обязателен и не должен быть пустым")

    emoji_names = _build_letter_emoji_names(
        text=text,
        letter_suffix=letter_suffix,
        space_emoji=space_emoji,
        translit_ru=translit_ru,
        digits_as_words=digits_as_words,
        unknown_policy=unknown_policy,
        include_spaces=include_spaces,
        char_overrides=char_overrides,
    )
    if not emoji_names:
        raise ValueError("Не удалось построить ни одной emoji-реакции из text")

    driver = get_mattermost_driver(bot_id)
    user_id = _resolve_mattermost_user_id(driver)

    added: List[str] = []
    skipped: List[Dict[str, str]] = []
    failed: List[Dict[str, str]] = []
    seen: set[str] = set()

    for emoji_name in emoji_names:
        if skip_duplicates and emoji_name in seen:
            skipped.append({"emoji_name": emoji_name, "reason": "duplicate_in_word"})
            continue
        seen.add(emoji_name)

        try:
            driver.reactions.create_reaction(
                options={
                    "user_id": user_id,
                    "post_id": post_id,
                    "emoji_name": emoji_name,
                }
            )
            added.append(emoji_name)
        except Exception as exc:
            if continue_on_error:
                failed.append({"emoji_name": emoji_name, "error": str(exc)})
                continue
            raise

    if not silent:
        logging.info(
            "Добавлены letter-emoji реакции: post_id=%s, added=%s, failed=%s",
            post_id,
            len(added),
            len(failed),
        )

    return {
        "post_id": post_id,
        "user_id": user_id,
        "added": added,
        "skipped": skipped,
        "failed": failed,
    }


def send_message_test(
    *,
    channel_id: str,
    bot_id: str,
    text: str,
    dir_path: str | None = None,
    creds_path: str | None = None,
    root_id: Optional[str] = None,
    silent: bool = False,
) -> None:
    send_message(
        channel_id=channel_id,
        bot_id=_resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        text=text,
        root_id=root_id,
        silent=silent,
    )


def post_message_test(
    *,
    channel_id: str,
    bot_id: str,
    text: str,
    dir_path: str | None = None,
    creds_path: str | None = None,
    root_id: Optional[str] = None,
    props: Optional[Dict[str, object]] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return post_message(
        channel_id=channel_id,
        bot_id=_resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        text=text,
        root_id=root_id,
        props=props,
        silent=silent,
    )


def send_file_bytes_test(
    *,
    channel_id: str,
    bot_id: str,
    file_name: str,
    file_bytes: bytes,
    text: str = "",
    mime_type: Optional[str] = None,
    root_id: Optional[str] = None,
    dir_path: str | None = None,
    creds_path: str | None = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_file_bytes(
        channel_id=channel_id,
        bot_id=_resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        file_name=file_name,
        file_bytes=file_bytes,
        text=text,
        mime_type=mime_type,
        root_id=root_id,
        silent=silent,
    )


def send_file_test(
    *,
    channel_id: str,
    bot_id: str,
    file_path: str,
    text: str = "",
    file_name: Optional[str] = None,
    mime_type: Optional[str] = None,
    root_id: Optional[str] = None,
    dir_path: str | None = None,
    creds_path: str | None = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_file(
        channel_id=channel_id,
        bot_id=_resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        file_path=file_path,
        text=text,
        file_name=file_name,
        mime_type=mime_type,
        root_id=root_id,
        silent=silent,
    )


def send_files_test(
    *,
    channel_id: str,
    bot_id: str,
    file_paths: List[str],
    text: str = "",
    root_id: Optional[str] = None,
    dir_path: str | None = None,
    creds_path: str | None = None,
    silent: bool = False,
) -> List[Dict[str, Optional[str]]]:
    return send_files(
        channel_id=channel_id,
        bot_id=_resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        file_paths=file_paths,
        text=text,
        root_id=root_id,
        silent=silent,
    )


def send_dataframe_as_csv_test(
    *,
    channel_id: str,
    bot_id: str,
    df,
    file_name: str = "dataframe.csv",
    text: str = "",
    include_index: bool = False,
    encoding: str = "utf-8",
    root_id: Optional[str] = None,
    dir_path: str | None = None,
    creds_path: str | None = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_dataframe_as_csv(
        channel_id=channel_id,
        bot_id=_resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        df=df,
        file_name=file_name,
        text=text,
        include_index=include_index,
        encoding=encoding,
        root_id=root_id,
        silent=silent,
    )


def send_dataframe_preview_test(
    *,
    channel_id: str,
    bot_id: str,
    df,
    title: str = "",
    max_rows: int = 10,
    max_cols: int = 10,
    root_id: Optional[str] = None,
    dir_path: str | None = None,
    creds_path: str | None = None,
    silent: bool = False,
) -> None:
    send_dataframe_preview(
        channel_id=channel_id,
        bot_id=_resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        df=df,
        title=title,
        max_rows=max_rows,
        max_cols=max_cols,
        root_id=root_id,
        silent=silent,
    )


def send_thread_message_test(
    *,
    channel_id: str,
    bot_id: str,
    root_id: str,
    text: str,
    dir_path: str | None = None,
    creds_path: str | None = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_thread_message(
        channel_id=channel_id,
        bot_id=_resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        root_id=root_id,
        text=text,
        silent=silent,
    )


def send_thread_messages_test(
    *,
    channel_id: str,
    bot_id: str,
    root_id: str,
    messages: List[str],
    dir_path: str | None = None,
    creds_path: str | None = None,
    silent: bool = False,
    continue_on_error: bool = False,
) -> Dict[str, List[Dict[str, Optional[str]]]]:
    return send_thread_messages(
        channel_id=channel_id,
        bot_id=_resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        root_id=root_id,
        messages=messages,
        silent=silent,
        continue_on_error=continue_on_error,
    )


def send_message_as_letter_emojis_test(
    *,
    channel_id: str,
    bot_id: str,
    text: str,
    root_id: Optional[str] = None,
    letter_suffix: str = "_letter",
    space_emoji: str = "space",
    translit_ru: bool = True,
    digits_as_words: bool = True,
    unknown_policy: str = "keep",
    char_overrides: Optional[Dict[str, str]] = None,
    dir_path: str | None = None,
    creds_path: str | None = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_message_as_letter_emojis(
        channel_id=channel_id,
        bot_id=_resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        text=text,
        root_id=root_id,
        letter_suffix=letter_suffix,
        space_emoji=space_emoji,
        translit_ru=translit_ru,
        digits_as_words=digits_as_words,
        unknown_policy=unknown_policy,
        char_overrides=char_overrides,
        silent=silent,
    )


def add_letter_emoji_reactions_test(
    *,
    post_id: str,
    bot_id: str,
    text: str,
    letter_suffix: str = "_letter",
    space_emoji: str = "space",
    translit_ru: bool = True,
    digits_as_words: bool = True,
    include_spaces: bool = False,
    unknown_policy: str = "skip",
    char_overrides: Optional[Dict[str, str]] = None,
    skip_duplicates: bool = True,
    continue_on_error: bool = True,
    dir_path: str | None = None,
    creds_path: str | None = None,
    silent: bool = False,
) -> Dict[str, Any]:
    return add_letter_emoji_reactions(
        post_id=post_id,
        bot_id=_resolve_test_bot_id(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        text=text,
        letter_suffix=letter_suffix,
        space_emoji=space_emoji,
        translit_ru=translit_ru,
        digits_as_words=digits_as_words,
        include_spaces=include_spaces,
        unknown_policy=unknown_policy,
        char_overrides=char_overrides,
        skip_duplicates=skip_duplicates,
        continue_on_error=continue_on_error,
        silent=silent,
    )


# Короткие алиасы с простыми именами.
def send_text(
    *,
    channel_id: str,
    bot_id: str,
    text: str,
    root_id: Optional[str] = None,
    silent: bool = False,
) -> None:
    send_message(
        channel_id=channel_id,
        bot_id=bot_id,
        text=text,
        root_id=root_id,
        silent=silent,
    )


def send_text_test(
    *,
    channel_id: str,
    bot_id: str,
    text: str,
    dir_path: str | None = None,
    creds_path: str | None = None,
    root_id: Optional[str] = None,
    silent: bool = False,
) -> None:
    send_message_test(
        channel_id=channel_id,
        bot_id=bot_id,
        text=text,
        dir_path=dir_path,
        creds_path=creds_path,
        root_id=root_id,
        silent=silent,
    )


def reply_text(
    *,
    channel_id: str,
    bot_id: str,
    root_id: str,
    text: str,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_thread_message(
        channel_id=channel_id,
        bot_id=bot_id,
        root_id=root_id,
        text=text,
        silent=silent,
    )


def reply_text_test(
    *,
    channel_id: str,
    bot_id: str,
    root_id: str,
    text: str,
    dir_path: str | None = None,
    creds_path: str | None = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_thread_message_test(
        channel_id=channel_id,
        bot_id=bot_id,
        root_id=root_id,
        text=text,
        dir_path=dir_path,
        creds_path=creds_path,
        silent=silent,
    )
