"""Feishu/Lark channel adapter."""

from __future__ import annotations

import json
from collections.abc import Callable
from typing import Any

from beaver.foundation.events import InboundMessage, OutboundMessage
from beaver.interfaces.channels.base import ChannelInboundSink

from .base import (
    build_inbound_message,
    chunk_text,
    compact_media_summary,
    config_bool,
    config_list,
    mark_unclaimed,
    outbound_target,
)

EventRecorder = Callable[..., None]


class FeishuAdapter:
    """Feishu/Lark bot adapter with injectable client support."""

    KIND = "feishu"

    def __init__(
        self,
        *,
        channel_id: str,
        kind: str,
        mode: str,
        account_id: str,
        display_name: str | None,
        inbound_sink: ChannelInboundSink,
        secrets: dict[str, Any] | None = None,
        config: dict[str, Any] | None = None,
        event_recorder: EventRecorder | None = None,
        client: Any | None = None,
    ) -> None:
        self.channel_id = channel_id
        self.kind = kind
        self.mode = mode
        self.account_id = account_id
        self.display_name = display_name
        self.inbound_sink = inbound_sink
        self.secrets = secrets or {}
        self.config = config or {}
        self.event_recorder = event_recorder
        self._client = client
        self.max_message_chars = int(self.config.get("maxMessageChars") or 4096)

    async def start(self) -> None:
        if self._client is not None:
            return
        if self.mode not in {"websocket", "webhook"}:
            raise ValueError(f"Unsupported feishu mode: {self.mode}")
        self._client = self._build_client()

    async def stop(self) -> None:
        close = getattr(self._client, "close", None)
        if close is not None:
            result = close()
            if hasattr(result, "__await__"):
                await result

    async def handle_event_payload(self, payload: dict[str, Any]) -> None:
        message = self._normalize_payload(payload)
        if message is None:
            return
        await self.inbound_sink.accept_inbound(message)

    async def send(self, message: OutboundMessage) -> None:
        target = outbound_target(message)
        if not target.peer_id:
            mark_unclaimed(message)
            return
        client = self._require_client()
        for chunk in chunk_text(message.content, max_chars=self.max_message_chars):
            await client.send_text(receive_id_type="chat_id", receive_id=target.peer_id, text=chunk)

    def _normalize_payload(self, payload: dict[str, Any]) -> InboundMessage | None:
        event = payload.get("event") if isinstance(payload.get("event"), dict) else payload
        message = event.get("message") if isinstance(event.get("message"), dict) else {}
        sender = event.get("sender") if isinstance(event.get("sender"), dict) else {}

        peer_id = _string_or_none(message.get("chat_id"))
        if not peer_id:
            return None

        message_id = _string_or_none(message.get("message_id"))
        message_type = str(message.get("message_type") or "unknown")
        chat_type = str(message.get("chat_type") or "unknown")
        peer_type = "dm" if chat_type == "p2p" else "group"
        user_id = _sender_open_id(sender)

        if peer_type == "dm" and not self._dm_allowed(user_id or peer_id):
            return None
        if peer_type == "group" and not self._group_allowed(peer_id, user_id):
            return None
        if peer_type == "group" and config_bool(self.config, "requireMentionInGroups", default=False):
            if not self._message_mentions_bot(message):
                return None

        content = self._message_content(message_type, message)
        if not content:
            return None

        metadata = {
            "chat_id": peer_id,
            "message_id": message_id,
            "chat_type": chat_type,
            "message_type": message_type,
        }

        return build_inbound_message(
            channel_id=self.channel_id,
            kind=self.kind,
            account_id=self.account_id,
            peer_id=peer_id,
            peer_type=peer_type,
            user_id=user_id,
            message_id=message_id,
            content=content,
            metadata=metadata,
        )

    def _message_content(self, message_type: str, message: dict[str, Any]) -> str:
        content = _parse_json_object(message.get("content"))
        if message_type == "text":
            return str(content.get("text") or "").strip()
        file_name = _string_or_none(content.get("file_name") or content.get("name"))
        return compact_media_summary(message_type, file_name=file_name)

    def _message_mentions_bot(self, message: dict[str, Any]) -> bool:
        bot_open_id = _string_or_none(self.config.get("botOpenId"))
        if not bot_open_id:
            return False
        mentions = message.get("mentions")
        if not isinstance(mentions, list):
            return False
        for mention in mentions:
            if not isinstance(mention, dict):
                continue
            mention_id = mention.get("id") if isinstance(mention.get("id"), dict) else {}
            if _string_or_none(mention_id.get("open_id")) == bot_open_id:
                return True
        return False

    def _dm_allowed(self, identifier: str | None) -> bool:
        allowlist = config_list(self.config, "allowFrom")
        if not allowlist:
            return True
        return bool(identifier and identifier in allowlist)

    def _group_allowed(self, peer_id: str | None, user_id: str | None) -> bool:
        allowlist = config_list(self.config, "groupAllowFrom")
        if not allowlist:
            return True
        return bool((peer_id and peer_id in allowlist) or (user_id and user_id in allowlist))

    def _require_client(self) -> Any:
        if self._client is None:
            self._client = self._build_client()
        return self._client

    def _build_client(self) -> Any:
        self._require_secret("appId")
        self._require_secret("appSecret")
        try:
            import lark_oapi  # noqa: F401
        except ImportError as exc:  # pragma: no cover - optional live dependency
            raise RuntimeError("Install beaver-backend[feishu] to enable FeishuAdapter") from exc
        raise RuntimeError("Feishu live client is not configured for direct construction")

    def _require_secret(self, key: str) -> str:
        value = self.secrets.get(key)
        if not value:
            raise ValueError(f"{key} is required")
        return str(value)


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


def _sender_open_id(sender: dict[str, Any]) -> str | None:
    sender_id = sender.get("sender_id") if isinstance(sender.get("sender_id"), dict) else {}
    return _string_or_none(sender_id.get("open_id"))


def _string_or_none(value: Any) -> str | None:
    if value is None:
        return None
    text = str(value).strip()
    return text or None
