from __future__ import annotations

import base64
import json
import os
import subprocess
import threading
import time
from collections.abc import Callable
from io import BytesIO
from pathlib import Path
from typing import Any
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
from uuid import uuid4

import httpx
import qrcode
import qrcode.image.svg

from external_connector.providers.fake import _session_view
from external_connector.state import ConnectorSessionState, SidecarStateStore


BridgePoster = Callable[[str, dict[str, Any], dict[str, str]], None]
ReceiverStart = Callable[[ConnectorSessionState], object]


class FeishuBotProvider:
    provider_id = "feishu_bot"

    def __init__(
        self,
        *,
        store: SidecarStateStore,
        http_client: Any | None = None,
        bridge_base_url: str,
        public_base_url: str | None = None,
        bridge_token: str,
        bridge_post: BridgePoster | None = None,
        api_base_url: str = "",
        start_receivers: bool = True,
        receiver_start: ReceiverStart | None = None,
    ) -> None:
        self.store = store
        self.http = http_client or httpx.Client(timeout=30)
        self.bridge_base_url = bridge_base_url.rstrip("/")
        self.public_base_url = (public_base_url or bridge_base_url).rstrip("/")
        self.bridge_token = bridge_token
        self.bridge_post = bridge_post or self._default_bridge_post
        self.api_base_url = api_base_url.rstrip("/")
        self.start_receivers = start_receivers
        self.receiver_start = receiver_start or self._start_receiver_process
        self._receiver_processes: dict[str, object] = {}
        self._receiver_lock = threading.Lock()
        self._start_existing_connected_receivers()

    def connectors(self) -> list[dict[str, Any]]:
        return [
            {
                "kind": "feishu",
                "displayName": "Feishu/Lark",
                "authType": "qr_or_bot_app",
                "providerId": self.provider_id,
                "capabilities": ["receive_text", "send_text", "groups"],
            }
        ]

    def health(self) -> dict[str, Any]:
        return {"ok": True, "providerId": self.provider_id}

    def start_session(self, payload: dict[str, Any]) -> dict[str, Any]:
        kind = str(payload["kind"])
        if kind != "feishu":
            raise KeyError(f"Unsupported connector kind: {kind}")
        options = dict(payload.get("options") or {})
        session = self.store.create_session(
            kind=kind,
            connection_id=str(payload["connectionId"]),
            channel_id=str(payload["channelId"]),
            display_name=str(payload["displayName"]),
            options=options,
        )
        metadata = {
            "eventCallbackPath": "/feishu/events",
            "eventCallbackUrl": f"{self.public_base_url}/feishu/events",
            "bridgeBaseUrl": str(payload.get("callbackBaseUrl") or self.bridge_base_url),
        }
        app_id = str(options.get("appId") or options.get("app_id") or "").strip()
        app_secret = str(options.get("appSecret") or options.get("app_secret") or "").strip()
        verification_token = str(options.get("verificationToken") or options.get("verification_token") or "").strip()
        mode = str(options.get("mode") or "create").strip().lower()
        domain = _domain(options)
        metadata.update(_policy_metadata(options))
        if not app_id or not app_secret:
            if mode != "link":
                return self._start_registration_session(session, metadata=metadata, domain=domain)
            session = self.store.update_session(
                session.session_id,
                status="waiting_for_user",
                instructions=_link_instructions(metadata["eventCallbackUrl"]),
                metadata=metadata,
            )
            return _session_view(session)
        token_data = self._tenant_token(app_id, app_secret, domain=domain)
        metadata.update(
            {
                "appId": app_id,
                "appSecret": app_secret,
                "verificationToken": verification_token,
                "tenantAccessToken": token_data["token"],
                "tenantTokenExpiresAt": token_data["expires_at"],
                "domain": domain,
            }
        )
        session = self.store.update_session(
            session.session_id,
            status="connected",
            account_id=f"feishu:{app_id}",
            metadata=metadata,
            instructions=_connected_instructions(),
        )
        self._ensure_receiver(session)
        return _session_view(session)

    def get_session(self, session_id: str) -> dict[str, Any]:
        session = self.store.get_session(session_id)
        if session.kind != "feishu":
            raise KeyError(session_id)
        if session.kind == "feishu" and session.status in {"qr_ready", "scanned", "confirmed", "waiting_for_user"} and session.metadata.get("deviceCode"):
            session = self._poll_registration_session(session)
        if session.status == "connected":
            self._ensure_receiver(session)
        return _session_view(session)

    def cancel_session(self, session_id: str) -> None:
        session = self.store.get_session(session_id)
        if session.kind != "feishu":
            raise KeyError(session_id)
        self.store.update_session(session_id, status="cancelled")
        self._stop_receiver(session.connection_id)

    def logout(self, connection_id: str) -> None:
        try:
            session = self.store.find_session_by_connection_id(connection_id)
        except KeyError:
            return None
        self._stop_receiver(connection_id)
        self.store.update_session(session.session_id, status="cancelled")
        return None

    def send(self, payload: dict[str, Any]) -> dict[str, Any]:
        begin = self.store.begin_send(connection_id=str(payload["connectionId"]), request_id=str(payload["requestId"]))
        if not begin.should_send:
            if begin.http_status == 409:
                return {"ok": False, "status": begin.status, "retryAfterSeconds": begin.retry_after_seconds, "httpStatus": 409}
            return {"ok": True, "providerMessageId": begin.provider_message_id}
        session = self.store.find_session_by_connection_id(str(payload["connectionId"]))
        token = self._tenant_token_for_session(session)
        target = dict(payload.get("target") or {})
        metadata = dict(session.metadata)
        api_base = _open_api_base_url(str(metadata.get("domain") or "feishu"), self.api_base_url)
        peer_type = str(target.get("peerType") or "dm")
        receive_id_type = "chat_id" if peer_type == "group" else "open_id"
        receive_id = str(target.get("threadId") or target.get("peerId") or "") if receive_id_type == "chat_id" else str(target.get("peerId") or "")
        chunks = _text_chunks(str(payload.get("content") or ""), _positive_int(metadata.get("maxMessageChars"), default=20000))
        provider_message_ids: list[str] = []
        try:
            for chunk in chunks:
                response = self.http.post(
                    f"{api_base}/open-apis/im/v1/messages?receive_id_type={receive_id_type}",
                    json={
                        "receive_id": receive_id,
                        "msg_type": "text",
                        "content": json.dumps({"text": chunk}, ensure_ascii=False),
                    },
                    headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
                    timeout=20,
                )
                response.raise_for_status()
                data = dict(response.json())
                if int(data.get("code") or 0) != 0:
                    error = str(data.get("msg") or data)
                    self.store.fail_send(begin.dedupe_key, error=error)
                    return {"ok": False, "error": error, "httpStatus": 502}
                provider_message_ids.append(str((data.get("data") or {}).get("message_id") or f"feishu_{payload['requestId']}"))
        except Exception as exc:
            error = str(exc)
            self.store.fail_send(begin.dedupe_key, error=error)
            return {"ok": False, "error": error, "httpStatus": 502}
        provider_message_id = ",".join(provider_message_ids) or f"feishu_{payload['requestId']}"
        self.store.complete_send(begin.dedupe_key, provider_message_id=provider_message_id)
        return {"ok": True, "providerMessageId": provider_message_id}

    def handle_event(self, payload: dict[str, Any]) -> dict[str, Any]:
        challenge = payload.get("challenge")
        if challenge:
            token = str(payload.get("token") or (payload.get("header") or {}).get("token") or "")
            if not token or self._session_for_verification_token(token) is None:
                return {"ok": False, "error": "verification token is required", "httpStatus": 401}
            return {"challenge": challenge}
        header = dict(payload.get("header") or {})
        event = dict(payload.get("event") or {})
        app_id = str(header.get("app_id") or "")
        session = self._session_for_app_id(app_id)
        expected_token = str(session.metadata.get("verificationToken") or "")
        received_token = str(header.get("token") or payload.get("token") or "")
        if not expected_token or received_token != expected_token:
            return {"ok": False, "error": "invalid verification token", "httpStatus": 401}
        ignored = _ignore_reason(event)
        if ignored:
            return {"ok": True, "ignored": ignored}
        bridge_event = _bridge_event_from_feishu(session, header, event)
        if bridge_event is None:
            return {"ok": True, "ignored": "empty_or_oversized"}
        self.bridge_post(
            f"{_bridge_base_url(session, self.bridge_base_url)}/api/channel-connector-bridge/events",
            bridge_event,
            {"Authorization": f"Bearer {self.bridge_token}"},
        )
        return {"ok": True}

    def _tenant_token_for_session(self, session: ConnectorSessionState) -> str:
        metadata = dict(session.metadata)
        expires_at = float(metadata.get("tenantTokenExpiresAt") or 0)
        token = str(metadata.get("tenantAccessToken") or "")
        if token and expires_at - time.time() > 60:
            return token
        app_id = str(metadata.get("appId") or "")
        app_secret = str(metadata.get("appSecret") or "")
        token_data = self._tenant_token(app_id, app_secret, domain=str(metadata.get("domain") or "feishu"))
        metadata.update({"tenantAccessToken": token_data["token"], "tenantTokenExpiresAt": token_data["expires_at"]})
        self.store.update_session(session.session_id, metadata=metadata)
        return str(token_data["token"])

    def _tenant_token(self, app_id: str, app_secret: str, *, domain: str = "feishu") -> dict[str, Any]:
        response = self.http.post(
            f"{_open_api_base_url(domain, self.api_base_url)}/open-apis/auth/v3/tenant_access_token/internal",
            json={"app_id": app_id, "app_secret": app_secret},
            timeout=20,
        )
        response.raise_for_status()
        data = dict(response.json())
        if int(data.get("code") or 0) != 0:
            raise RuntimeError(str(data.get("msg") or data))
        return {"token": str(data["tenant_access_token"]), "expires_at": time.time() + int(data.get("expire") or 7200)}

    def _session_for_app_id(self, app_id: str) -> ConnectorSessionState:
        sessions = self.store.list_sessions()
        for session in sorted(sessions, key=lambda item: item.updated_at, reverse=True):
            if session.kind == "feishu" and session.status == "connected" and session.metadata.get("appId") == app_id:
                return session
        raise KeyError(app_id)

    def _session_for_verification_token(self, token: str) -> ConnectorSessionState | None:
        sessions = self.store.list_sessions()
        for session in sorted(sessions, key=lambda item: item.updated_at, reverse=True):
            if (
                session.kind == "feishu"
                and session.status == "connected"
                and token
                and session.metadata.get("verificationToken") == token
            ):
                return session
        return None

    def _default_bridge_post(self, url: str, payload: dict[str, Any], headers: dict[str, str]) -> None:
        response = self.http.post(url, json=payload, headers=headers, timeout=20)
        response.raise_for_status()

    def _start_registration_session(
        self,
        session: ConnectorSessionState,
        *,
        metadata: dict[str, Any],
        domain: str,
    ) -> dict[str, Any]:
        try:
            init_data = self._registration_post(domain, {"action": "init"})
            supported = init_data.get("supported_auth_methods") or []
            if "client_secret" not in supported:
                session = self.store.update_session(
                    session.session_id,
                    status="error",
                    error="Current Feishu/Lark environment does not support client_secret bot registration",
                    metadata=metadata,
                )
                return _session_view(session)
            begin_data = self._registration_post(
                domain,
                {
                    "action": "begin",
                    "archetype": "PersonalAgent",
                    "auth_method": "client_secret",
                    "request_user_info": "open_id",
                },
            )
        except Exception as exc:
            session = self.store.update_session(
                session.session_id,
                status="error",
                error=str(exc),
                metadata=metadata,
            )
            return _session_view(session)

        qr_code = _with_onboard_from(str(begin_data.get("verification_uri_complete") or ""))
        if not qr_code:
            session = self.store.update_session(
                session.session_id,
                status="error",
                error="Feishu/Lark registration did not return a QR URL",
                metadata=metadata,
            )
            return _session_view(session)
        metadata.update(
            {
                "domain": domain,
                "registrationBaseUrl": _registration_base_url(domain),
                "deviceCode": str(begin_data.get("device_code") or ""),
                "pollIntervalSeconds": int(begin_data.get("interval") or 5),
                "expiresAt": time.time() + int(begin_data.get("expire_in") or 600),
            }
        )
        session = self.store.update_session(
            session.session_id,
            status="qr_ready",
            qr_code=qr_code,
            qr_image=_qr_svg_data_uri(qr_code),
            instructions=_create_instructions(metadata["eventCallbackUrl"]),
            metadata=metadata,
        )
        return _session_view(session)

    def _poll_registration_session(self, session: ConnectorSessionState) -> ConnectorSessionState:
        metadata = dict(session.metadata)
        expires_at = float(metadata.get("expiresAt") or 0)
        if expires_at and time.time() > expires_at:
            return self.store.update_session(session.session_id, status="expired", metadata=metadata)
        domain = str(metadata.get("domain") or "feishu")
        device_code = str(metadata.get("deviceCode") or "")
        if not device_code:
            return session
        try:
            poll_data = self._registration_post(domain, {"action": "poll", "device_code": device_code})
        except Exception as exc:
            return self.store.update_session(session.session_id, status="error", error=str(exc), metadata=metadata)

        user_info = dict(poll_data.get("user_info") or {})
        if user_info.get("tenant_brand") == "lark":
            domain = "lark"
            metadata["domain"] = domain
        app_id = str(poll_data.get("client_id") or "").strip()
        app_secret = str(poll_data.get("client_secret") or "").strip()
        if app_id and app_secret:
            token_data = self._tenant_token(app_id, app_secret, domain=domain)
            metadata.update(
                {
                    "appId": app_id,
                    "appSecret": app_secret,
                    "tenantAccessToken": token_data["token"],
                    "tenantTokenExpiresAt": token_data["expires_at"],
                    "registrationOpenId": str(user_info.get("open_id") or ""),
                }
            )
            return self.store.update_session(
                session.session_id,
                status="connected",
                account_id=f"feishu:{app_id}",
                metadata=metadata,
                instructions=_connected_instructions(),
            )

        error = str(poll_data.get("error") or "")
        if error == "authorization_pending":
            return session
        if error == "slow_down":
            metadata["pollIntervalSeconds"] = int(metadata.get("pollIntervalSeconds") or 5) + 5
            return self.store.update_session(session.session_id, metadata=metadata)
        if error == "expired_token":
            return self.store.update_session(session.session_id, status="expired", metadata=metadata)
        if error == "access_denied":
            return self.store.update_session(session.session_id, status="error", error="Feishu/Lark authorization was denied", metadata=metadata)
        if error:
            description = str(poll_data.get("error_description") or error)
            return self.store.update_session(session.session_id, status="error", error=description, metadata=metadata)
        return session

    def _registration_post(self, domain: str, values: dict[str, str]) -> dict[str, Any]:
        response = self.http.post(
            f"{_registration_base_url(domain)}/oauth/v1/app/registration",
            data=urlencode(values),
            headers={"Content-Type": "application/x-www-form-urlencoded"},
            timeout=20,
        )
        response.raise_for_status()
        return dict(response.json())

    def _start_existing_connected_receivers(self) -> None:
        if not self.start_receivers:
            return None
        for session in self.store.list_sessions():
            if session.kind == "feishu" and session.status == "connected":
                self._ensure_receiver(session)
        return None

    def _ensure_receiver(self, session: ConnectorSessionState) -> None:
        if not self.start_receivers or not _has_receiver_material(session):
            return None
        with self._receiver_lock:
            existing = self._receiver_processes.get(session.connection_id)
            if existing is not None and _receiver_is_alive(existing):
                return None
            receiver = self.receiver_start(session)
            self._receiver_processes[session.connection_id] = receiver
        return None

    def _stop_receiver(self, connection_id: str) -> None:
        with self._receiver_lock:
            receiver = self._receiver_processes.pop(connection_id, None)
        if receiver is None:
            return None
        terminate = getattr(receiver, "terminate", None)
        if callable(terminate):
            terminate()
        wait = getattr(receiver, "wait", None)
        if callable(wait):
            try:
                wait(timeout=5)
            except subprocess.TimeoutExpired:
                kill = getattr(receiver, "kill", None)
                if callable(kill):
                    kill()
        return None

    def _start_receiver_process(self, session: ConnectorSessionState) -> subprocess.Popen[bytes]:
        metadata = dict(session.metadata)
        script = Path(__file__).resolve().parents[1] / "node" / "feishu_ws_receiver.js"
        env = os.environ.copy()
        env.update(
            {
                "FEISHU_APP_ID": str(metadata.get("appId") or ""),
                "FEISHU_APP_SECRET": str(metadata.get("appSecret") or ""),
                "FEISHU_DOMAIN": str(metadata.get("domain") or "feishu"),
                "FEISHU_CONNECTION_ID": session.connection_id,
                "FEISHU_CHANNEL_ID": session.channel_id,
                "FEISHU_ACCOUNT_ID": str(session.account_id or ""),
                "FEISHU_REQUIRE_MENTION_IN_GROUPS": _env_bool(metadata.get("requireMentionInGroups"), default=True),
                "FEISHU_RESPOND_TO_MENTION_ALL": _env_bool(metadata.get("respondToMentionAll"), default=False),
                "FEISHU_DM_MODE": str(metadata.get("dmMode") or "open"),
                "FEISHU_ALLOW_FROM": ",".join(_string_list(metadata.get("allowFrom"))),
                "FEISHU_GROUP_ALLOW_FROM": ",".join(_string_list(metadata.get("groupAllowFrom"))),
                "FEISHU_MAX_MESSAGE_CHARS": str(_positive_int(metadata.get("maxMessageChars"), default=20000)),
                "FEISHU_TEXT_BATCH_DELAY_MS": str(_positive_int(metadata.get("textBatchDelayMs"), default=0)),
                "FEISHU_TEXT_BATCH_MAX_MESSAGES": str(_positive_int(metadata.get("textBatchMaxMessages"), default=10)),
                "FEISHU_TEXT_BATCH_MAX_CHARS": str(_positive_int(metadata.get("textBatchMaxChars"), default=20000)),
                "BEAVER_BRIDGE_BASE_URL": _bridge_base_url(session, self.bridge_base_url),
                "BEAVER_BRIDGE_TOKEN": self.bridge_token,
            }
        )
        return subprocess.Popen(["node", str(script)], env=env, cwd=str(script.parent))


def _bridge_base_url(session: ConnectorSessionState, fallback: str) -> str:
    return str(session.metadata.get("bridgeBaseUrl") or fallback).rstrip("/")


def _bridge_event_from_feishu(session: ConnectorSessionState, header: dict[str, Any], event: dict[str, Any]) -> dict[str, Any] | None:
    message = dict(event.get("message") or {})
    sender = dict(event.get("sender") or {})
    sender_id = dict(sender.get("sender_id") or {})
    is_group = message.get("chat_type") == "group"
    sender_open_id = str(sender_id.get("open_id") or sender_id.get("user_id") or "")
    chat_id = str(message.get("chat_id") or "")
    peer_id = chat_id if is_group else sender_open_id
    message_id = str(message.get("message_id") or uuid4().hex)
    event_id = str(header.get("event_id") or f"{session.channel_id}:{message_id}")
    content = _extract_text(message).strip()
    max_chars = _positive_int(session.metadata.get("maxMessageChars"), default=20000)
    if not content or len(content) > max_chars:
        return None
    return {
        "eventId": event_id,
        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "deliveryAttempt": 1,
        "connectionId": session.connection_id,
        "channelId": session.channel_id,
        "kind": "feishu",
        "accountId": session.account_id,
        "peerId": peer_id,
        "peerType": "group" if is_group else "dm",
        "userId": sender_open_id,
        "threadId": chat_id or None,
        "messageId": message_id,
        "messageType": str(message.get("message_type") or "text"),
        "content": content,
        "metadata": {
            "chatId": message.get("chat_id"),
            "rawMessageType": message.get("message_type"),
            "senderType": sender.get("sender_type"),
            "mentions": message.get("mentions") if isinstance(message.get("mentions"), list) else [],
        },
    }


def _has_receiver_material(session: ConnectorSessionState) -> bool:
    metadata = dict(session.metadata)
    return bool(
        session.status == "connected"
        and str(metadata.get("appId") or "").strip()
        and str(metadata.get("appSecret") or "").strip()
        and session.connection_id
        and session.channel_id
    )


def _receiver_is_alive(receiver: object) -> bool:
    poll = getattr(receiver, "poll", None)
    if callable(poll):
        return poll() is None
    return True


def _extract_text(message: dict[str, Any]) -> str:
    content = message.get("content")
    if isinstance(content, str):
        try:
            parsed = json.loads(content)
        except json.JSONDecodeError:
            return content
        text = parsed.get("text")
        if text is not None:
            return str(text)
        return content
    return ""


def _text_chunks(text: str, max_chars: int) -> list[str]:
    cleaned = str(text or "")
    if not cleaned:
        return [""]
    size = max(1, int(max_chars))
    return [cleaned[index : index + size] for index in range(0, len(cleaned), size)]


def _ignore_reason(event: dict[str, Any]) -> str:
    sender = dict(event.get("sender") or {})
    sender_type = str(sender.get("sender_type") or "").strip().lower()
    if sender_type and sender_type != "user":
        return f"sender_type:{sender_type}"
    message = dict(event.get("message") or {})
    content = _extract_text(message).strip()
    if content.startswith("/feishu"):
        return "feishu_command"
    return ""


def _policy_metadata(options: dict[str, Any]) -> dict[str, Any]:
    metadata: dict[str, Any] = {}
    for key in ("allowFrom", "allow_from"):
        items = _string_list(options.get(key))
        if items:
            metadata["allowFrom"] = items
            break
    for key in ("groupAllowFrom", "group_allow_from"):
        items = _string_list(options.get(key))
        if items:
            metadata["groupAllowFrom"] = items
            break
    if "requireMentionInGroups" in options or "require_mention_in_groups" in options:
        metadata["requireMentionInGroups"] = _bool(options.get("requireMentionInGroups", options.get("require_mention_in_groups")))
    else:
        metadata["requireMentionInGroups"] = True
    if "respondToMentionAll" in options or "respond_to_mention_all" in options:
        metadata["respondToMentionAll"] = _bool(options.get("respondToMentionAll", options.get("respond_to_mention_all")))
    dm_mode = str(options.get("dmMode") or options.get("dm_mode") or "open").strip()
    metadata["dmMode"] = dm_mode if dm_mode in {"open", "allowlist", "pair", "disabled"} else "open"
    for key, metadata_key, default in (
        ("maxMessageChars", "maxMessageChars", 20000),
        ("textBatchDelayMs", "textBatchDelayMs", 0),
        ("textBatchMaxMessages", "textBatchMaxMessages", 10),
        ("textBatchMaxChars", "textBatchMaxChars", 20000),
    ):
        alt_key = _camel_to_snake(key)
        if key in options or alt_key in options:
            metadata[metadata_key] = _positive_int(options.get(key, options.get(alt_key)), default=default)
    return metadata


def _string_list(value: Any) -> list[str]:
    if isinstance(value, str):
        raw_items = value.replace("\n", ",").split(",")
    elif isinstance(value, list):
        raw_items = value
    else:
        raw_items = []
    return [str(item).strip() for item in raw_items if str(item).strip()]


def _bool(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    return str(value).strip().lower() in {"1", "true", "yes", "on"}


def _positive_int(value: Any, *, default: int) -> int:
    try:
        number = int(value)
    except (TypeError, ValueError):
        return default
    return number if number > 0 else default


def _env_bool(value: Any, *, default: bool) -> str:
    if value is None:
        return "true" if default else "false"
    return "true" if _bool(value) else "false"


def _camel_to_snake(value: str) -> str:
    result: list[str] = []
    for char in value:
        if char.isupper() and result:
            result.append("_")
        result.append(char.lower())
    return "".join(result)


def _domain(options: dict[str, Any]) -> str:
    domain = str(options.get("domain") or "feishu").strip().lower()
    return "lark" if domain == "lark" else "feishu"


def _registration_base_url(domain: str) -> str:
    return "https://accounts.larksuite.com" if domain == "lark" else "https://accounts.feishu.cn"


def _open_api_base_url(domain: str, configured_base_url: str) -> str:
    if configured_base_url:
        return configured_base_url.rstrip("/")
    return "https://open.larksuite.com" if domain == "lark" else "https://open.feishu.cn"


def _with_onboard_from(value: str) -> str:
    if not value:
        return ""
    parts = urlsplit(value)
    query = dict(parse_qsl(parts.query, keep_blank_values=True))
    query.setdefault("from", "onboard")
    return urlunsplit((parts.scheme, parts.netloc, parts.path, urlencode(query), parts.fragment))


def _qr_svg_data_uri(value: str) -> str:
    image = qrcode.make(value, image_factory=qrcode.image.svg.SvgPathImage)
    buffer = BytesIO()
    image.save(buffer)
    return "data:image/svg+xml;base64," + base64.b64encode(buffer.getvalue()).decode("ascii")


def _create_instructions(event_callback_url: str) -> list[str]:
    return [
        "使用飞书客户端扫描二维码，选择一键创建飞书机器人。",
        "创建完成后，点击打开机器人，在飞书中向机器人发送任意消息即可开始对话。",
        "在飞书开放平台启用接收消息事件 im.message.receive_v1，并选择长连接事件通道。",
        f"如果使用 HTTP 回调模式，事件请求 URL 可设为 {event_callback_url}。",
        "如需用户身份授权，在飞书对话中发送 /feishu auth。",
        "建议发送：学习一下我安装的新飞书插件，列出有哪些能力。",
        "验证安装：在飞书对话中发送 /feishu start，返回版本号代表安装成功。",
        "如果 Windows 设备扫码异常，通常是终端二维码分辨率问题，可换用 Cmder 或使用本窗口二维码。",
    ]


def _link_instructions(event_callback_url: str) -> list[str]:
    return [
        "选择关联已有机器人时，请输入正确的 App ID 和 App Secret。",
        "如果提示无效的 App ID 或 App Secret，请回到飞书开放平台复制最新应用凭证。",
        "在飞书开放平台启用接收消息事件 im.message.receive_v1，并选择长连接事件通道。",
        f"如果使用 HTTP 回调模式，事件请求 URL 可设为 {event_callback_url}。",
    ]


def _connected_instructions() -> list[str]:
    return [
        "飞书机器人凭据已连接。",
        "sidecar 会通过飞书长连接保持应用在线。",
        "请确认飞书开放平台已启用接收消息事件 im.message.receive_v1。",
        "在飞书对话中发送 /feishu start 验证插件返回版本号；发送任意消息验证 Beaver 是否收到。",
    ]
