#!/usr/bin/env python3
from __future__ import annotations

import json
import threading
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple

import requests

from cheapclaw_channel_base import ChannelAdapter
from tool_runtime_helpers import now_iso, parse_iso


class FeishuAdapter(ChannelAdapter):
    name = "feishu"

    def __init__(self, config: Dict[str, Any], service: Any, *, logger=None):
        super().__init__(config, service, logger=logger)
        self.app_id = str(config.get("app_id") or "").strip()
        self.app_secret = str(config.get("app_secret") or "").strip()
        self.verify_token = str(config.get("verify_token") or "").strip()
        self.encrypt_key = str(config.get("encrypt_key") or "").strip()
        self.mode = str(config.get("mode") or "long_connection").strip() or "long_connection"
        self.api_root = "https://open.feishu.cn/open-apis"
        self._queue_lock = threading.Lock()
        self._queued_events: List[Dict[str, Any]] = []
        self._long_conn_thread: Optional[threading.Thread] = None
        self._long_conn_started = False
        if self.mode == "long_connection":
            self._start_long_connection()

    def _enqueue_event(self, payload: Dict[str, Any]) -> None:
        with self._queue_lock:
            self._queued_events.append(payload)

    def _normalize_message_event(self, header: Dict[str, Any], event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        message = event.get("message") or {}
        sender = event.get("sender") or {}
        if header.get("event_type") != "im.message.receive_v1":
            return None
        text = ""
        content_raw = message.get("content")
        if isinstance(content_raw, str):
            try:
                content = json.loads(content_raw)
            except Exception:
                content = {}
            text = str(content.get("text") or "").strip()
        chat_type = str(message.get("chat_type") or "")
        chat_id = str(message.get("chat_id") or "").strip()
        if not chat_id:
            return None
        mention = (chat_type != "group") or ("<at" in text)
        return {
            "event_id": f"feishu_{message.get('message_id')}",
            "channel": "feishu",
            "conversation_id": chat_id,
            "conversation_type": "group" if chat_type == "group" else "person",
            "display_name": chat_id,
            "sender_id": str((sender.get("sender_id") or {}).get("open_id") or ""),
            "sender_name": str((sender.get("sender_id") or {}).get("user_id") or ""),
            "message_id": str(message.get("message_id") or ""),
            "message_text": text,
            "attachments": [],
            "timestamp": now_iso(),
            "is_mention_to_bot": mention,
            "require_mention": chat_type == "group",
        }

    def _start_long_connection(self) -> None:
        if self._long_conn_started:
            return
        self._long_conn_started = True
        if not self.app_id or not self.app_secret:
            self.log("Feishu long connection skipped: missing app_id/app_secret")
            return

        def _runner() -> None:
            try:
                import lark_oapi as lark

                def _handle_message(data) -> None:
                    try:
                        payload = json.loads(lark.JSON.marshal(data) or "{}")
                        normalized = self._normalize_message_event(payload.get("header") or {}, payload.get("event") or {})
                        if normalized:
                            self._enqueue_event(normalized)
                    except Exception as exc:
                        self.log(f"Feishu long connection event parse failed: {exc}")

                event_handler = lark.EventDispatcherHandler.builder(
                    self.encrypt_key,
                    self.verify_token,
                ).register_p2_im_message_receive_v1(_handle_message).build()

                client = lark.ws.Client(
                    self.app_id,
                    self.app_secret,
                    event_handler=event_handler,
                    log_level=lark.LogLevel.INFO,
                )
                self.log("Feishu long connection started")
                client.start()
            except Exception as exc:
                detail = str(exc)
                if "python-socks" in detail:
                    detail += " (detected SOCKS proxy; clear all_proxy/ALL_PROXY or install python-socks)"
                self.log(f"Feishu long connection stopped: {detail}")

        self._long_conn_thread = threading.Thread(target=_runner, daemon=True, name="cheapclaw-feishu-long-conn")
        self._long_conn_thread.start()

    def poll_events(self) -> List[Dict[str, Any]]:
        if self.mode != "long_connection":
            return []
        with self._queue_lock:
            items = list(self._queued_events)
            self._queued_events.clear()
            return items

    def _tenant_access_token(self) -> str:
        state = self.service.load_runtime_state()
        cached = state.get("feishu_token") or {}
        expire_at = parse_iso(cached.get("expire_at", ""))
        if cached.get("token") and expire_at and expire_at > datetime.now().astimezone() + timedelta(seconds=60):
            return str(cached["token"])
        if not self.app_id or not self.app_secret:
            return ""
        response = requests.post(
            f"{self.api_root}/auth/v3/tenant_access_token/internal",
            json={"app_id": self.app_id, "app_secret": self.app_secret},
            timeout=30,
        )
        response.raise_for_status()
        data = response.json()
        token = str(data.get("tenant_access_token") or "")
        expire_at = datetime.now().astimezone() + timedelta(seconds=int(data.get("expire", 0) or 0))
        state["feishu_token"] = {"token": token, "expire_at": expire_at.isoformat(timespec="seconds")}
        self.service.save_runtime_state(state)
        return token

    def handle_webhook_post(self, path: str, body: bytes, headers: Dict[str, str]) -> Tuple[int, Dict[str, str], bytes]:
        try:
            payload = json.loads(body.decode("utf-8"))
        except Exception:
            return 400, {"Content-Type": "application/json"}, b'{"error":"invalid json"}'

        challenge = payload.get("challenge")
        if challenge:
            return 200, {"Content-Type": "application/json"}, json.dumps({"challenge": challenge}).encode("utf-8")

        normalized = self._normalize_message_event(payload.get("header") or {}, payload.get("event") or {})
        if normalized:
            self.service.ingest_event(normalized)
        return 200, {"Content-Type": "application/json"}, b'{"ok":true}'

    def send_message(
        self,
        conversation_id: str,
        message: str,
        attachments: Optional[List[Dict[str, Any]]] = None,
    ) -> Tuple[bool, str]:
        token = self._tenant_access_token()
        if not token:
            return False, "feishu app_id/app_secret are missing"
        last_remote_id = ""
        normalized = self._normalize_attachments(attachments)
        if message:
            response = requests.post(
                f"{self.api_root}/im/v1/messages",
                params={"receive_id_type": "chat_id"},
                headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json; charset=utf-8"},
                json={"receive_id": conversation_id, "msg_type": "text", "content": json.dumps({"text": message}, ensure_ascii=False)},
                timeout=30,
            )
            if response.status_code >= 300:
                return False, response.text
            data = response.json()
            last_remote_id = str(((data.get("data") or {}).get("message_id")) or "")
        for item in normalized:
            mime_type = item["mime_type"]
            is_image = mime_type.startswith("image/")
            upload_url = f"{self.api_root}/im/v1/images" if is_image else f"{self.api_root}/im/v1/files"
            with open(item["path"], "rb") as fh:
                files = {"image" if is_image else "file": (item["filename"], fh, mime_type)}
                data_payload = {"image_type": "message"} if is_image else {"file_type": "stream", "file_name": item["filename"]}
                upload_resp = requests.post(
                    upload_url,
                    headers={"Authorization": f"Bearer {token}"},
                    data=data_payload,
                    files=files,
                    timeout=60,
                )
            if upload_resp.status_code >= 300:
                return False, upload_resp.text
            upload_data = upload_resp.json()
            key_name = "image_key" if is_image else "file_key"
            media_key = str(((upload_data.get("data") or {}).get(key_name)) or "")
            if not media_key:
                return False, json.dumps(upload_data, ensure_ascii=False)
            msg_type = "image" if is_image else "file"
            content = {key_name: media_key}
            send_resp = requests.post(
                f"{self.api_root}/im/v1/messages",
                params={"receive_id_type": "chat_id"},
                headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json; charset=utf-8"},
                json={"receive_id": conversation_id, "msg_type": msg_type, "content": json.dumps(content, ensure_ascii=False)},
                timeout=30,
            )
            if send_resp.status_code >= 300:
                return False, send_resp.text
            data = send_resp.json()
            last_remote_id = str(((data.get("data") or {}).get("message_id")) or "")
        if not message and not normalized:
            return False, "feishu message or attachments are required"
        return True, last_remote_id
