import inspect
import json
import logging
import os
import re
from typing import Any, Dict, List, Optional, Tuple, Union
from urllib.parse import urljoin

import requests

from mnemosynecore.vault.client import get_secret, get_secret_test

SizeValue = Union[str, Tuple[int, int], List[int]]
SupersetConn = Union[str, Dict[str, Any]]


def _parse_extra(extra_raw: Any) -> Dict[str, Any]:
    if isinstance(extra_raw, dict):
        return extra_raw
    if isinstance(extra_raw, str):
        try:
            parsed = json.loads(extra_raw) if extra_raw else {}
            if isinstance(parsed, dict):
                return parsed
        except json.JSONDecodeError:
            return {}
    return {}


def _resolve_superset_config(
    superset_conn: SupersetConn,
    dir_path: Optional[str] = None,
) -> Dict[str, Any]:
    if isinstance(superset_conn, dict):
        cfg = dict(superset_conn)
    elif isinstance(superset_conn, str):
        raw = superset_conn.strip()
        if raw.startswith("{"):
            try:
                cfg = json.loads(raw)
            except json.JSONDecodeError as exc:
                raise ValueError("Неверный формат JSON для конфигурации Superset") from exc
        else:
            from mnemosynecore.secrets import resolve_secret

            cfg = resolve_secret(superset_conn, dir_path)
    else:
        raise TypeError("superset_conn должен быть dict или str")

    extra = _parse_extra(cfg.get("extra"))
    out = dict(cfg)
    out["extra"] = extra
    out["host"] = out.get("host") or out.get("ss_url")
    out["ss_url"] = out.get("ss_url") or out.get("host")
    out["username"] = out.get("username") or out.get("login")
    out["login"] = out.get("login") or out.get("username")
    out["auth_provider"] = out.get("auth_provider") or extra.get("auth_provider") or "ldap"
    if out.get("security_cookie_auth_token") is None:
        out["security_cookie_auth_token"] = extra.get("security_cookie_auth_token")
    return out


def _resolve_superset_config_test(
    superset_conn: SupersetConn,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
) -> Dict[str, Any]:
    if isinstance(superset_conn, dict):
        return _resolve_superset_config(superset_conn, dir_path=None)
    if isinstance(superset_conn, str):
        raw = superset_conn.strip()
        if raw.startswith("{"):
            return _resolve_superset_config(raw, dir_path=None)
        return _resolve_superset_config(
            get_secret_test(raw, dir_path=dir_path, creds_path=creds_path),
            dir_path=None,
        )
    raise TypeError("superset_conn должен быть dict или str")


def _resolve_mattermost_bot_id_test(
    bot_id: str,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
) -> str:
    raw = bot_id.strip()
    if raw.startswith("{"):
        try:
            json.loads(raw)
        except json.JSONDecodeError as exc:
            raise ValueError("Неверный формат JSON для конфигурации Mattermost") from exc
        return raw

    cfg = get_secret_test(raw, dir_path=dir_path, creds_path=creds_path)
    return json.dumps(cfg)


def _normalize_size_str(size: SizeValue) -> str:
    if isinstance(size, str):
        compact = size.replace(" ", "")
        if "," not in compact:
            raise ValueError(f"Некорректный размер: {size}")
        return compact
    if isinstance(size, (tuple, list)) and len(size) == 2:
        return f"{int(size[0])},{int(size[1])}"
    raise ValueError(f"Некорректный размер: {size}")


def _normalize_size_tuple(size: SizeValue) -> Tuple[int, int]:
    compact = _normalize_size_str(size)
    left, right = compact.split(",", 1)
    return int(left), int(right)


def _call_with_supported_kwargs(func, kwargs: Dict[str, Any]):
    try:
        return func(**kwargs)
    except TypeError:
        try:
            signature = inspect.signature(func)
        except (TypeError, ValueError):
            raise

        has_var_kwargs = any(
            p.kind == inspect.Parameter.VAR_KEYWORD
            for p in signature.parameters.values()
        )
        if has_var_kwargs:
            raise

        filtered = {}
        for name, param in signature.parameters.items():
            if param.kind in (
                inspect.Parameter.POSITIONAL_OR_KEYWORD,
                inspect.Parameter.KEYWORD_ONLY,
            ) and name in kwargs:
                filtered[name] = kwargs[name]

        if not filtered or filtered == kwargs:
            raise
        return func(**filtered)


def _safe_name(value: str) -> str:
    sanitized = re.sub(r"[^A-Za-z0-9_.-]+", "_", value.strip())
    return sanitized.strip("_") or "screenshot"


def _normalize_superset_host(host: str) -> str:
    cleaned = host.strip()
    if not cleaned:
        raise ValueError("host Superset не может быть пустым")
    if cleaned.startswith("http://") or cleaned.startswith("https://"):
        return cleaned.rstrip("/")
    return f"https://{cleaned.rstrip('/')}"


def _build_superset_dashboard_url(
    *,
    cfg: Dict[str, Any],
    dashboard_id: Optional[int] = None,
    dashboard_url: Optional[str] = None,
) -> str:
    if dashboard_url:
        return dashboard_url
    if dashboard_id is None:
        raise ValueError("Нужно передать dashboard_id или dashboard_url")
    base = _normalize_superset_host(cfg["host"])
    return urljoin(base + "/", f"superset/dashboard/{dashboard_id}/")


def _build_superset_chart_url(
    *,
    cfg: Dict[str, Any],
    chart_id: Optional[int] = None,
    chart_url: Optional[str] = None,
) -> str:
    if chart_url:
        return chart_url
    if chart_id is None:
        raise ValueError("Нужно передать chart_id или chart_url")
    base = _normalize_superset_host(cfg["host"])
    return urljoin(base + "/", f"superset/explore/?slice_id={int(chart_id)}")


def _markdown_link(label: str, url: str) -> str:
    safe_label = (label or "").strip() or url
    return f"[{safe_label}]({url})"


def _get_mattermost_driver(bot_id: str):
    from mnemosynecore.mattermost import get_mattermost_driver

    return get_mattermost_driver(bot_id)


def get_superset_client(
    *,
    superset_conn: SupersetConn,
    dir_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
):
    cfg = _resolve_superset_config(superset_conn, dir_path)

    host = cfg.get("ss_url") or cfg.get("host")
    username = cfg.get("username") or cfg.get("login")
    password = cfg.get("password")
    auth_provider = cfg.get("auth_provider", "ldap")
    cookie_token = security_cookie_auth_token or cfg.get("security_cookie_auth_token")

    if not host:
        raise ValueError("Для Superset нужен host/ss_url")
    if not username:
        raise ValueError("Для Superset нужен login/username")
    if password is None:
        raise ValueError("Для Superset нужен password")

    try:
        from superset_o3_api_lib import superset_o3_api_lib

        superset_api = getattr(superset_o3_api_lib, "superset_api", None)
        if callable(superset_api):
            kwargs = {
                "username": username,
                "password": password,
                "ss_url": host,
                "auth_provider": auth_provider,
            }
            if cookie_token:
                kwargs["security_cookie_auth_token"] = cookie_token

            try:
                return superset_api(**kwargs)
            except TypeError:
                if cookie_token:
                    return superset_api(
                        username,
                        password,
                        host,
                        auth_provider,
                        security_cookie_auth_token=cookie_token,
                    )
                return superset_api(username, password, host, auth_provider)
    except ImportError:
        pass

    try:
        from superset_o3_api_lib import SupersetAPIClient
        from superset_o3_api_lib.auth import PasswordOAuthFlow
    except ImportError as exc:
        raise ImportError(
            "Для Superset-функций установите extra зависимость: mnemosynecore[superset]"
        ) from exc

    extra = cfg.get("extra") or {}
    client_secret = extra.get("client_secret")
    client_id = cfg.get("client_id") or cfg.get("schema") or extra.get("client_id")

    auth = PasswordOAuthFlow(
        superset_host=host,
        username=username,
        password=password,
        client_id=client_id,
        client_secret=client_secret,
    )
    return SupersetAPIClient(auth=auth)


def superset_request(
    *,
    endpoint: str,
    method: str = "GET",
    payload: dict | None = None,
    vault_conn_id: str,
):
    cfg = get_secret(vault_conn_id)
    return _superset_request_with_cfg(
        endpoint=endpoint,
        method=method,
        payload=payload,
        cfg=cfg,
    )


def _superset_request_with_cfg(
    *,
    endpoint: str,
    method: str = "GET",
    payload: dict | None = None,
    cfg: Dict[str, Any],
):
    base_url = cfg["host"]

    headers = {
        "Authorization": f"Bearer {cfg['password']}",
        "Content-Type": "application/json",
    }

    url = f"{base_url.rstrip('/')}/{endpoint.lstrip('/')}"
    resp = requests.request(method, url, json=payload, headers=headers)
    resp.raise_for_status()
    return resp.json()


def superset_request_test(
    *,
    endpoint: str,
    method: str = "GET",
    payload: dict | None = None,
    vault_conn_id: str,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
):
    cfg = get_secret_test(vault_conn_id, dir_path=dir_path, creds_path=creds_path)
    return _superset_request_with_cfg(
        endpoint=endpoint,
        method=method,
        payload=payload,
        cfg=cfg,
    )


def superset_dashboard_url(
    *,
    superset_conn: SupersetConn,
    dashboard_id: Optional[int] = None,
    dashboard_url: Optional[str] = None,
    dir_path: Optional[str] = None,
) -> str:
    cfg = _resolve_superset_config(superset_conn, dir_path=dir_path)
    return _build_superset_dashboard_url(
        cfg=cfg,
        dashboard_id=dashboard_id,
        dashboard_url=dashboard_url,
    )


def superset_chart_url(
    *,
    superset_conn: SupersetConn,
    chart_id: Optional[int] = None,
    chart_url: Optional[str] = None,
    dir_path: Optional[str] = None,
) -> str:
    cfg = _resolve_superset_config(superset_conn, dir_path=dir_path)
    return _build_superset_chart_url(
        cfg=cfg,
        chart_id=chart_id,
        chart_url=chart_url,
    )


def superset_dashboard_link(
    *,
    superset_conn: SupersetConn,
    dashboard_name: Optional[str] = None,
    dashboard_id: Optional[int] = None,
    dashboard_url: Optional[str] = None,
    dir_path: Optional[str] = None,
) -> str:
    url = superset_dashboard_url(
        superset_conn=superset_conn,
        dashboard_id=dashboard_id,
        dashboard_url=dashboard_url,
        dir_path=dir_path,
    )
    label = dashboard_name
    if not label:
        label = f"Dashboard {dashboard_id}" if dashboard_id is not None else "Dashboard"
    return _markdown_link(label, url)


def superset_chart_link(
    *,
    superset_conn: SupersetConn,
    chart_name: Optional[str] = None,
    chart_id: Optional[int] = None,
    chart_url: Optional[str] = None,
    dir_path: Optional[str] = None,
) -> str:
    url = superset_chart_url(
        superset_conn=superset_conn,
        chart_id=chart_id,
        chart_url=chart_url,
        dir_path=dir_path,
    )
    label = chart_name
    if not label:
        label = f"Chart {chart_id}" if chart_id is not None else "Chart"
    return _markdown_link(label, url)


def superset_dashboard_thumbnail(
    *,
    superset_conn: SupersetConn,
    dashboard_name: Optional[str] = None,
    dashboard_id: Optional[int] = None,
    dashboard_url: Optional[str] = None,
    refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
) -> bytes:
    client = get_superset_client(
        superset_conn=superset_conn,
        dir_path=dir_path,
        security_cookie_auth_token=security_cookie_auth_token,
    )

    if hasattr(client, "dashboard_thumbnail"):
        if not dashboard_name:
            raise ValueError(
                "dashboard_name обязателен для dashboard_thumbnail. "
                "Передайте имя дашборда или используйте старый API с dashboard_id/dashboard_url."
            )
        kwargs = {
            "dashboard_name": dashboard_name,
            "refresh": refresh,
            "refresh_wait_sec": refresh_wait_sec,
            "thumb_size": _normalize_size_str(thumb_size),
            "window_size": _normalize_size_str(window_size),
            "dashboard_url": dashboard_url,
        }
        return _call_with_supported_kwargs(client.dashboard_thumbnail, kwargs)

    if hasattr(client, "dashboard_screenshot"):
        if dashboard_id is None and dashboard_url is None:
            raise ValueError("Для dashboard_screenshot нужен dashboard_id или dashboard_url")
        kwargs = {
            "dashboard_id": dashboard_id,
            "dashboard_url": dashboard_url,
            "refresh": refresh,
            "refresh_wait_sec": refresh_wait_sec,
            "thumb_size": _normalize_size_tuple(thumb_size),
            "window_size": _normalize_size_tuple(window_size),
            "retry_count": 5,
        }
        return _call_with_supported_kwargs(client.dashboard_screenshot, kwargs)

    raise AttributeError(
        "Не найден метод dashboard_thumbnail/dashboard_screenshot в клиенте Superset API"
    )


def superset_chart_thumbnail(
    *,
    superset_conn: SupersetConn,
    chart_id: int,
    force_refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
) -> bytes:
    client = get_superset_client(
        superset_conn=superset_conn,
        dir_path=dir_path,
        security_cookie_auth_token=security_cookie_auth_token,
    )

    chart_method = None
    if hasattr(client, "chart_thumbnail"):
        chart_method = client.chart_thumbnail
    elif hasattr(client, "chart_screenshot"):
        chart_method = client.chart_screenshot

    if chart_method is None:
        raise AttributeError(
            "Не найден метод chart_thumbnail/chart_screenshot в клиенте Superset API"
        )

    kwargs = {
        "chart_id": chart_id,
        "force_refresh": force_refresh,
        "refresh_wait_sec": refresh_wait_sec,
        "thumb_size": _normalize_size_str(thumb_size),
        "window_size": _normalize_size_str(window_size),
    }
    return _call_with_supported_kwargs(chart_method, kwargs)


def superset_screenshot_dashboard(
    conn_id: SupersetConn,
    dashboards: List[Dict],
    dir_path: Optional[str] = None,
    output_dir: str = ".",
) -> List[str]:
    os.makedirs(output_dir, exist_ok=True)
    saved_files: List[str] = []

    for i, dashboard in enumerate(dashboards, start=1):
        screenshot = superset_dashboard_thumbnail(
            superset_conn=conn_id,
            dashboard_name=dashboard.get("dashboard_name") or dashboard.get("name"),
            dashboard_id=dashboard.get("id"),
            dashboard_url=dashboard.get("dashboard_url") or dashboard.get("url"),
            refresh=dashboard.get("refresh", True),
            refresh_wait_sec=dashboard.get("refresh_wait_sec", 60),
            thumb_size=dashboard.get("thumb_size", "2048,1536"),
            window_size=dashboard.get("window_size", "2048,1536"),
            dir_path=dir_path,
            security_cookie_auth_token=dashboard.get("security_cookie_auth_token"),
        )

        name = (
            dashboard.get("file_name")
            or dashboard.get("dashboard_name")
            or dashboard.get("name")
            or str(dashboard.get("id") or "dashboard")
        )
        filename = os.path.join(output_dir, f"{i}_{_safe_name(str(name))}.png")

        with open(filename, "wb") as f:
            f.write(screenshot)
        saved_files.append(filename)

    return saved_files


def superset_screenshot_charts(
    conn_id: SupersetConn,
    charts: List[Dict],
    dir_path: Optional[str] = None,
    output_dir: str = ".",
) -> List[str]:
    os.makedirs(output_dir, exist_ok=True)
    saved_files: List[str] = []

    for i, chart in enumerate(charts, start=1):
        chart_id = chart.get("chart_id") or chart.get("id")
        if chart_id is None:
            raise ValueError("В списке charts у каждого элемента должен быть chart_id (или id)")

        screenshot = superset_chart_thumbnail(
            superset_conn=conn_id,
            chart_id=int(chart_id),
            force_refresh=chart.get("force_refresh", True),
            refresh_wait_sec=chart.get("refresh_wait_sec", 60),
            thumb_size=chart.get("thumb_size", "2048,1536"),
            window_size=chart.get("window_size", "2048,1536"),
            dir_path=dir_path,
            security_cookie_auth_token=chart.get("security_cookie_auth_token"),
        )

        name = chart.get("file_name") or f"chart_{chart_id}"
        filename = os.path.join(output_dir, f"{i}_{_safe_name(str(name))}.png")

        with open(filename, "wb") as f:
            f.write(screenshot)
        saved_files.append(filename)

    return saved_files


def _post_png_to_mattermost(
    *,
    channel_id: str,
    bot_id: str,
    png_bytes: bytes,
    file_name: str,
    text: str = "",
    root_id: Optional[str] = None,
) -> Dict[str, Optional[str]]:
    driver = _get_mattermost_driver(bot_id)
    upload = driver.files.upload_file(
        channel_id=channel_id,
        files={"files": (file_name, png_bytes, "image/png")},
    )

    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
    return {"file_id": file_id, "post_id": post_id, "root_id": root_id}


def send_superset_dashboard_screenshot(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    dashboard_name: str,
    dashboard_url: Optional[str] = None,
    dashboard_id: Optional[int] = None,
    text: str = "",
    file_name: Optional[str] = None,
    root_id: Optional[str] = None,
    refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    png_bytes = superset_dashboard_thumbnail(
        superset_conn=superset_conn,
        dashboard_name=dashboard_name,
        dashboard_id=dashboard_id,
        dashboard_url=dashboard_url,
        refresh=refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        dir_path=dir_path,
        security_cookie_auth_token=security_cookie_auth_token,
    )

    final_name = file_name or f"dashboard_{_safe_name(dashboard_name)}.png"
    result = _post_png_to_mattermost(
        channel_id=channel_id,
        bot_id=bot_id,
        png_bytes=png_bytes,
        file_name=final_name,
        text=text,
        root_id=root_id,
    )
    if not silent:
        logging.info(
            "Скриншот дашборда отправлен в Mattermost: channel_id=%s, file=%s",
            channel_id,
            final_name,
        )
    return {
        "channel_id": channel_id,
        "file_name": final_name,
        "file_id": result.get("file_id"),
        "post_id": result.get("post_id"),
        "root_id": result.get("root_id"),
    }


def send_superset_dashboard_permalink_screenshot(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    dashboard_url: str,
    dashboard_name: str,
    text: str = "",
    include_link: bool = True,
    link_text: Optional[str] = None,
    file_name: Optional[str] = None,
    root_id: Optional[str] = None,
    refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    final_text = text
    if include_link:
        link = _markdown_link(link_text or dashboard_name, dashboard_url)
        final_text = _join_text_parts(text, link)

    return send_superset_dashboard_screenshot(
        channel_id=channel_id,
        bot_id=bot_id,
        superset_conn=superset_conn,
        dashboard_name=dashboard_name,
        dashboard_url=dashboard_url,
        text=final_text,
        file_name=file_name,
        root_id=root_id,
        refresh=refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        dir_path=dir_path,
        security_cookie_auth_token=security_cookie_auth_token,
        silent=silent,
    )


def send_superset_chart_screenshot(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    chart_id: int,
    text: str = "",
    file_name: Optional[str] = None,
    root_id: Optional[str] = None,
    force_refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    png_bytes = superset_chart_thumbnail(
        superset_conn=superset_conn,
        chart_id=chart_id,
        force_refresh=force_refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        dir_path=dir_path,
        security_cookie_auth_token=security_cookie_auth_token,
    )

    final_name = file_name or f"chart_{chart_id}.png"
    result = _post_png_to_mattermost(
        channel_id=channel_id,
        bot_id=bot_id,
        png_bytes=png_bytes,
        file_name=final_name,
        text=text,
        root_id=root_id,
    )
    if not silent:
        logging.info(
            "Скриншот чарта отправлен в Mattermost: channel_id=%s, chart_id=%s",
            channel_id,
            chart_id,
        )
    return {
        "channel_id": channel_id,
        "file_name": final_name,
        "file_id": result.get("file_id"),
        "post_id": result.get("post_id"),
        "root_id": result.get("root_id"),
    }


def superset_dashboard_screenshot_to_file(
    *,
    superset_conn: SupersetConn,
    output_path: str,
    dashboard_name: Optional[str] = None,
    dashboard_url: Optional[str] = None,
    dashboard_id: Optional[int] = None,
    refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
) -> str:
    png_bytes = superset_dashboard_thumbnail(
        superset_conn=superset_conn,
        dashboard_name=dashboard_name,
        dashboard_id=dashboard_id,
        dashboard_url=dashboard_url,
        refresh=refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        dir_path=dir_path,
        security_cookie_auth_token=security_cookie_auth_token,
    )

    output_dir = os.path.dirname(output_path)
    if output_dir:
        os.makedirs(output_dir, exist_ok=True)
    with open(output_path, "wb") as f:
        f.write(png_bytes)
    return output_path


def superset_chart_screenshot_to_file(
    *,
    superset_conn: SupersetConn,
    chart_id: int,
    output_path: str,
    force_refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
) -> str:
    png_bytes = superset_chart_thumbnail(
        superset_conn=superset_conn,
        chart_id=chart_id,
        force_refresh=force_refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        dir_path=dir_path,
        security_cookie_auth_token=security_cookie_auth_token,
    )

    output_dir = os.path.dirname(output_path)
    if output_dir:
        os.makedirs(output_dir, exist_ok=True)
    with open(output_path, "wb") as f:
        f.write(png_bytes)
    return output_path


def _merge_text_with_link(text: str, link: str) -> str:
    clean_text = text.strip()
    if clean_text:
        return f"{clean_text}\n{link}"
    return link


def _join_text_parts(*parts: str) -> str:
    cleaned = [p.strip() for p in parts if p and p.strip()]
    return "\n".join(cleaned)


def send_superset_dashboard_link(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    dashboard_name: Optional[str] = None,
    dashboard_id: Optional[int] = None,
    dashboard_url: Optional[str] = None,
    text: str = "",
    root_id: Optional[str] = None,
    dir_path: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    from mnemosynecore.mattermost import post_message

    link = superset_dashboard_link(
        superset_conn=superset_conn,
        dashboard_name=dashboard_name,
        dashboard_id=dashboard_id,
        dashboard_url=dashboard_url,
        dir_path=dir_path,
    )
    result = post_message(
        channel_id=channel_id,
        bot_id=bot_id,
        text=_merge_text_with_link(text, link),
        root_id=root_id,
        silent=silent,
    )
    result["link"] = link
    return result


def send_superset_chart_link(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    chart_name: Optional[str] = None,
    chart_id: Optional[int] = None,
    chart_url: Optional[str] = None,
    text: str = "",
    root_id: Optional[str] = None,
    dir_path: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    from mnemosynecore.mattermost import post_message

    link = superset_chart_link(
        superset_conn=superset_conn,
        chart_name=chart_name,
        chart_id=chart_id,
        chart_url=chart_url,
        dir_path=dir_path,
    )
    result = post_message(
        channel_id=channel_id,
        bot_id=bot_id,
        text=_merge_text_with_link(text, link),
        root_id=root_id,
        silent=silent,
    )
    result["link"] = link
    return result


def send_superset_dashboard_links_to_thread(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    dashboards: List[Dict[str, Any]],
    header_text: str = "",
    root_id: Optional[str] = None,
    thread_messages: Optional[List[str]] = None,
    dir_path: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Any]:
    from mnemosynecore.mattermost import send_thread_messages

    if not dashboards:
        return {
            "main_post_id": None,
            "root_id": root_id,
            "links_sent": [],
            "thread_messages": {"sent": [], "failed": []},
        }

    links_sent: List[Dict[str, Optional[str]]] = []
    main_post_id = None
    active_root_id = root_id

    for index, dashboard in enumerate(dashboards):
        dashboard_name = dashboard.get("dashboard_name") or dashboard.get("name")
        dashboard_id = dashboard.get("dashboard_id") or dashboard.get("id")
        dashboard_url = dashboard.get("dashboard_url") or dashboard.get("url")
        text = dashboard.get("text", "")
        if index == 0:
            text = _join_text_parts(header_text, text)

        result = send_superset_dashboard_link(
            channel_id=channel_id,
            bot_id=bot_id,
            superset_conn=superset_conn,
            dashboard_name=dashboard_name,
            dashboard_id=dashboard_id,
            dashboard_url=dashboard_url,
            text=text,
            root_id=active_root_id if index > 0 or active_root_id else None,
            dir_path=dir_path,
            silent=silent,
        )
        links_sent.append(result)
        if index == 0 and not active_root_id:
            main_post_id = result.get("post_id")
            active_root_id = main_post_id

    thread_result = {"sent": [], "failed": []}
    if thread_messages and active_root_id:
        thread_result = send_thread_messages(
            channel_id=channel_id,
            bot_id=bot_id,
            root_id=active_root_id,
            messages=thread_messages,
            silent=silent,
            continue_on_error=True,
        )

    return {
        "main_post_id": main_post_id,
        "root_id": active_root_id,
        "links_sent": links_sent,
        "thread_messages": thread_result,
    }


def send_superset_chart_links_to_thread(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    charts: List[Dict[str, Any]],
    header_text: str = "",
    root_id: Optional[str] = None,
    thread_messages: Optional[List[str]] = None,
    dir_path: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Any]:
    from mnemosynecore.mattermost import send_thread_messages

    if not charts:
        return {
            "main_post_id": None,
            "root_id": root_id,
            "links_sent": [],
            "thread_messages": {"sent": [], "failed": []},
        }

    links_sent: List[Dict[str, Optional[str]]] = []
    main_post_id = None
    active_root_id = root_id

    for index, chart in enumerate(charts):
        chart_name = chart.get("chart_name") or chart.get("name")
        chart_id = chart.get("chart_id") or chart.get("id")
        chart_url = chart.get("chart_url") or chart.get("url")
        text = chart.get("text", "")
        if index == 0:
            text = _join_text_parts(header_text, text)

        result = send_superset_chart_link(
            channel_id=channel_id,
            bot_id=bot_id,
            superset_conn=superset_conn,
            chart_name=chart_name,
            chart_id=chart_id,
            chart_url=chart_url,
            text=text,
            root_id=active_root_id if index > 0 or active_root_id else None,
            dir_path=dir_path,
            silent=silent,
        )
        links_sent.append(result)
        if index == 0 and not active_root_id:
            main_post_id = result.get("post_id")
            active_root_id = main_post_id

    thread_result = {"sent": [], "failed": []}
    if thread_messages and active_root_id:
        thread_result = send_thread_messages(
            channel_id=channel_id,
            bot_id=bot_id,
            root_id=active_root_id,
            messages=thread_messages,
            silent=silent,
            continue_on_error=True,
        )

    return {
        "main_post_id": main_post_id,
        "root_id": active_root_id,
        "links_sent": links_sent,
        "thread_messages": thread_result,
    }


def send_superset_dashboards_to_channels(
    *,
    bot_id: str,
    superset_conn: SupersetConn,
    dashboards: List[Dict[str, Any]],
    default_channel_id: Optional[str] = None,
    dir_path: Optional[str] = None,
    silent: bool = False,
) -> List[Dict[str, Optional[str]]]:
    results: List[Dict[str, Optional[str]]] = []

    for dashboard in dashboards:
        channel_id = dashboard.get("channel_id") or default_channel_id
        if not channel_id:
            raise ValueError("Для каждого dashboard нужен channel_id или default_channel_id")

        dashboard_name = dashboard.get("dashboard_name") or dashboard.get("name")
        if not dashboard_name:
            raise ValueError("Для каждого dashboard нужен dashboard_name (или name)")

        result = send_superset_dashboard_screenshot(
            channel_id=channel_id,
            bot_id=bot_id,
            superset_conn=superset_conn,
            dashboard_name=dashboard_name,
            dashboard_url=dashboard.get("dashboard_url") or dashboard.get("url"),
            dashboard_id=dashboard.get("id"),
            text=dashboard.get("text", ""),
            file_name=dashboard.get("file_name"),
            root_id=dashboard.get("root_id"),
            refresh=dashboard.get("refresh", True),
            refresh_wait_sec=dashboard.get("refresh_wait_sec", 60),
            thumb_size=dashboard.get("thumb_size", "2048,1536"),
            window_size=dashboard.get("window_size", "2048,1536"),
            dir_path=dir_path,
            security_cookie_auth_token=dashboard.get("security_cookie_auth_token"),
            silent=silent,
        )
        results.append(result)

    return results


def send_superset_charts_to_channels(
    *,
    bot_id: str,
    superset_conn: SupersetConn,
    charts: List[Dict[str, Any]],
    default_channel_id: Optional[str] = None,
    dir_path: Optional[str] = None,
    silent: bool = False,
) -> List[Dict[str, Optional[str]]]:
    results: List[Dict[str, Optional[str]]] = []

    for chart in charts:
        channel_id = chart.get("channel_id") or default_channel_id
        if not channel_id:
            raise ValueError("Для каждого chart нужен channel_id или default_channel_id")

        chart_id = chart.get("chart_id") or chart.get("id")
        if chart_id is None:
            raise ValueError("Для каждого chart нужен chart_id (или id)")

        result = send_superset_chart_screenshot(
            channel_id=channel_id,
            bot_id=bot_id,
            superset_conn=superset_conn,
            chart_id=int(chart_id),
            text=chart.get("text", ""),
            file_name=chart.get("file_name"),
            root_id=chart.get("root_id"),
            force_refresh=chart.get("force_refresh", True),
            refresh_wait_sec=chart.get("refresh_wait_sec", 60),
            thumb_size=chart.get("thumb_size", "2048,1536"),
            window_size=chart.get("window_size", "2048,1536"),
            dir_path=dir_path,
            security_cookie_auth_token=chart.get("security_cookie_auth_token"),
            silent=silent,
        )
        results.append(result)

    return results


def get_superset_client_test(
    *,
    superset_conn: SupersetConn,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
):
    return get_superset_client(
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        security_cookie_auth_token=security_cookie_auth_token,
    )


def superset_dashboard_thumbnail_test(
    *,
    superset_conn: SupersetConn,
    dashboard_name: Optional[str] = None,
    dashboard_id: Optional[int] = None,
    dashboard_url: Optional[str] = None,
    refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
) -> bytes:
    return superset_dashboard_thumbnail(
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        dashboard_name=dashboard_name,
        dashboard_id=dashboard_id,
        dashboard_url=dashboard_url,
        refresh=refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        security_cookie_auth_token=security_cookie_auth_token,
    )


def superset_chart_thumbnail_test(
    *,
    superset_conn: SupersetConn,
    chart_id: int,
    force_refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
) -> bytes:
    return superset_chart_thumbnail(
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        chart_id=chart_id,
        force_refresh=force_refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        security_cookie_auth_token=security_cookie_auth_token,
    )


def superset_screenshot_dashboard_test(
    conn_id: SupersetConn,
    dashboards: List[Dict],
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    output_dir: str = ".",
) -> List[str]:
    return superset_screenshot_dashboard(
        conn_id=_resolve_superset_config_test(
            superset_conn=conn_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        dashboards=dashboards,
        output_dir=output_dir,
    )


def superset_screenshot_charts_test(
    conn_id: SupersetConn,
    charts: List[Dict],
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    output_dir: str = ".",
) -> List[str]:
    return superset_screenshot_charts(
        conn_id=_resolve_superset_config_test(
            superset_conn=conn_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        charts=charts,
        output_dir=output_dir,
    )


def send_superset_dashboard_screenshot_test(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    dashboard_name: str,
    dashboard_url: Optional[str] = None,
    dashboard_id: Optional[int] = None,
    text: str = "",
    file_name: Optional[str] = None,
    root_id: Optional[str] = None,
    refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_superset_dashboard_screenshot(
        channel_id=channel_id,
        bot_id=_resolve_mattermost_bot_id_test(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        dashboard_name=dashboard_name,
        dashboard_url=dashboard_url,
        dashboard_id=dashboard_id,
        text=text,
        file_name=file_name,
        root_id=root_id,
        refresh=refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        security_cookie_auth_token=security_cookie_auth_token,
        silent=silent,
    )


def send_superset_chart_screenshot_test(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    chart_id: int,
    text: str = "",
    file_name: Optional[str] = None,
    root_id: Optional[str] = None,
    force_refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_superset_chart_screenshot(
        channel_id=channel_id,
        bot_id=_resolve_mattermost_bot_id_test(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        chart_id=chart_id,
        text=text,
        file_name=file_name,
        root_id=root_id,
        force_refresh=force_refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        security_cookie_auth_token=security_cookie_auth_token,
        silent=silent,
    )


def send_superset_dashboard_permalink_screenshot_test(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    dashboard_url: str,
    dashboard_name: str,
    text: str = "",
    include_link: bool = True,
    link_text: Optional[str] = None,
    file_name: Optional[str] = None,
    root_id: Optional[str] = None,
    refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_superset_dashboard_permalink_screenshot(
        channel_id=channel_id,
        bot_id=_resolve_mattermost_bot_id_test(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        dashboard_url=dashboard_url,
        dashboard_name=dashboard_name,
        text=text,
        include_link=include_link,
        link_text=link_text,
        file_name=file_name,
        root_id=root_id,
        refresh=refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        security_cookie_auth_token=security_cookie_auth_token,
        silent=silent,
    )


def send_superset_dashboards_to_channels_test(
    *,
    bot_id: str,
    superset_conn: SupersetConn,
    dashboards: List[Dict[str, Any]],
    default_channel_id: Optional[str] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    silent: bool = False,
) -> List[Dict[str, Optional[str]]]:
    return send_superset_dashboards_to_channels(
        bot_id=_resolve_mattermost_bot_id_test(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        dashboards=dashboards,
        default_channel_id=default_channel_id,
        silent=silent,
    )


def send_superset_charts_to_channels_test(
    *,
    bot_id: str,
    superset_conn: SupersetConn,
    charts: List[Dict[str, Any]],
    default_channel_id: Optional[str] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    silent: bool = False,
) -> List[Dict[str, Optional[str]]]:
    return send_superset_charts_to_channels(
        bot_id=_resolve_mattermost_bot_id_test(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        charts=charts,
        default_channel_id=default_channel_id,
        silent=silent,
    )


def superset_dashboard_url_test(
    *,
    superset_conn: SupersetConn,
    dashboard_id: Optional[int] = None,
    dashboard_url: Optional[str] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
) -> str:
    return superset_dashboard_url(
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        dashboard_id=dashboard_id,
        dashboard_url=dashboard_url,
    )


def superset_chart_url_test(
    *,
    superset_conn: SupersetConn,
    chart_id: Optional[int] = None,
    chart_url: Optional[str] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
) -> str:
    return superset_chart_url(
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        chart_id=chart_id,
        chart_url=chart_url,
    )


def superset_dashboard_link_test(
    *,
    superset_conn: SupersetConn,
    dashboard_name: Optional[str] = None,
    dashboard_id: Optional[int] = None,
    dashboard_url: Optional[str] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
) -> str:
    return superset_dashboard_link(
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        dashboard_name=dashboard_name,
        dashboard_id=dashboard_id,
        dashboard_url=dashboard_url,
    )


def superset_chart_link_test(
    *,
    superset_conn: SupersetConn,
    chart_name: Optional[str] = None,
    chart_id: Optional[int] = None,
    chart_url: Optional[str] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
) -> str:
    return superset_chart_link(
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        chart_name=chart_name,
        chart_id=chart_id,
        chart_url=chart_url,
    )


def superset_dashboard_screenshot_to_file_test(
    *,
    superset_conn: SupersetConn,
    output_path: str,
    dashboard_name: Optional[str] = None,
    dashboard_url: Optional[str] = None,
    dashboard_id: Optional[int] = None,
    refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
) -> str:
    return superset_dashboard_screenshot_to_file(
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        output_path=output_path,
        dashboard_name=dashboard_name,
        dashboard_url=dashboard_url,
        dashboard_id=dashboard_id,
        refresh=refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        security_cookie_auth_token=security_cookie_auth_token,
    )


def superset_chart_screenshot_to_file_test(
    *,
    superset_conn: SupersetConn,
    chart_id: int,
    output_path: str,
    force_refresh: Union[bool, str] = True,
    refresh_wait_sec: int = 60,
    thumb_size: SizeValue = "2048,1536",
    window_size: SizeValue = "2048,1536",
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    security_cookie_auth_token: Optional[str] = None,
) -> str:
    return superset_chart_screenshot_to_file(
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        chart_id=chart_id,
        output_path=output_path,
        force_refresh=force_refresh,
        refresh_wait_sec=refresh_wait_sec,
        thumb_size=thumb_size,
        window_size=window_size,
        security_cookie_auth_token=security_cookie_auth_token,
    )


def send_superset_dashboard_link_test(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    dashboard_name: Optional[str] = None,
    dashboard_id: Optional[int] = None,
    dashboard_url: Optional[str] = None,
    text: str = "",
    root_id: Optional[str] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_superset_dashboard_link(
        channel_id=channel_id,
        bot_id=_resolve_mattermost_bot_id_test(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        dashboard_name=dashboard_name,
        dashboard_id=dashboard_id,
        dashboard_url=dashboard_url,
        text=text,
        root_id=root_id,
        silent=silent,
    )


def send_superset_chart_link_test(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    chart_name: Optional[str] = None,
    chart_id: Optional[int] = None,
    chart_url: Optional[str] = None,
    text: str = "",
    root_id: Optional[str] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Optional[str]]:
    return send_superset_chart_link(
        channel_id=channel_id,
        bot_id=_resolve_mattermost_bot_id_test(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        chart_name=chart_name,
        chart_id=chart_id,
        chart_url=chart_url,
        text=text,
        root_id=root_id,
        silent=silent,
    )


def send_superset_dashboard_links_to_thread_test(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    dashboards: List[Dict[str, Any]],
    header_text: str = "",
    root_id: Optional[str] = None,
    thread_messages: Optional[List[str]] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Any]:
    return send_superset_dashboard_links_to_thread(
        channel_id=channel_id,
        bot_id=_resolve_mattermost_bot_id_test(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        dashboards=dashboards,
        header_text=header_text,
        root_id=root_id,
        thread_messages=thread_messages,
        silent=silent,
    )


def send_superset_chart_links_to_thread_test(
    *,
    channel_id: str,
    bot_id: str,
    superset_conn: SupersetConn,
    charts: List[Dict[str, Any]],
    header_text: str = "",
    root_id: Optional[str] = None,
    thread_messages: Optional[List[str]] = None,
    dir_path: Optional[str] = None,
    creds_path: Optional[str] = None,
    silent: bool = False,
) -> Dict[str, Any]:
    return send_superset_chart_links_to_thread(
        channel_id=channel_id,
        bot_id=_resolve_mattermost_bot_id_test(
            bot_id=bot_id,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        superset_conn=_resolve_superset_config_test(
            superset_conn=superset_conn,
            dir_path=dir_path,
            creds_path=creds_path,
        ),
        charts=charts,
        header_text=header_text,
        root_id=root_id,
        thread_messages=thread_messages,
        silent=silent,
    )
