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

import hashlib
import hmac
import json
import re
import uuid
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple

import requests

from cheapclaw_channel_base import ChannelAdapter
from tool_runtime_helpers import now_iso


class OneBotV11Adapter(ChannelAdapter):
    CQ_AT_PATTERN = re.compile(r"\[CQ:at,qq=([^\],]+)")

    def __init__(self, config: Dict[str, Any], service: Any, *, channel_name: str, logger=None):
        super().__init__(config, service, logger=logger)
        self.channel_name = channel_name
        self.api_base = str(config.get("onebot_api_base") or config.get("api_base") or "").strip().rstrip("/")
        self.access_token = str(config.get("onebot_access_token") or config.get("access_token") or "").strip()
        self.post_secret = str(config.get("onebot_post_secret") or config.get("post_secret") or "").strip()
        self.self_id = str(config.get("onebot_self_id") or config.get("self_id") or "").strip()
        self.bot_id = str(config.get("bot_id") or service.bot_id).strip() or service.bot_id
        self.require_mention_in_group = bool(config.get("require_mention_in_group", True))

    def _auth_headers(self) -> Dict[str, str]:
        headers = {"Content-Type": "application/json"}
        if self.access_token:
            headers["Authorization"] = f"Bearer {self.access_token}"
        return headers

    def _verify_signature(self, body: bytes, headers: Dict[str, str]) -> bool:
        if not self.post_secret:
            return True
        provided = str(headers.get("X-Signature") or headers.get("x-signature") or "").strip()
        expected = "sha1=" + hmac.new(self.post_secret.encode("utf-8"), body, hashlib.sha1).hexdigest()
        return bool(provided) and hmac.compare_digest(provided, expected)

    def _extract_text_and_mention(self, payload: Dict[str, Any]) -> Tuple[str, bool]:
        raw_message = payload.get("message")
        text_parts: List[str] = []
        mention_to_bot = False
        if isinstance(raw_message, str):
            text_parts.append(raw_message)
            if self.self_id and f"[CQ:at,qq={self.self_id}" in raw_message:
                mention_to_bot = True
            if self.self_id:
                for target in self.CQ_AT_PATTERN.findall(raw_message):
                    if str(target).strip() == self.self_id:
                        mention_to_bot = True
                        break
        elif isinstance(raw_message, list):
            for segment in raw_message:
                if not isinstance(segment, dict):
                    continue
                seg_type = str(segment.get("type") or "").strip().lower()
                data = segment.get("data") if isinstance(segment.get("data"), dict) else {}
                if seg_type == "text":
                    text_parts.append(str(data.get("text") or ""))
                    continue
                if seg_type == "at":
                    target = str(data.get("qq") or data.get("id") or data.get("user_id") or "").strip()
                    if self.self_id and target == self.self_id:
                        mention_to_bot = True
                    text_parts.append(f"@{target}" if target else "@")
                    continue
                text_parts.append(f"[{seg_type}]")
        text = "".join(text_parts).strip()
        return text, mention_to_bot

    def handle_webhook_post(self, path: str, body: bytes, headers: Dict[str, str]) -> Tuple[int, Dict[str, str], bytes]:
        if not self._verify_signature(body, headers):
            return 403, {"Content-Type": "application/json"}, b'{"error":"invalid signature"}'
        try:
            payload = json.loads(body.decode("utf-8"))
        except Exception:
            return 400, {"Content-Type": "application/json"}, b'{"error":"invalid json"}'
        if not isinstance(payload, dict):
            return 400, {"Content-Type": "application/json"}, b'{"error":"invalid payload"}'
        if str(payload.get("post_type") or "") != "message":
            return 200, {"Content-Type": "application/json"}, b'{"ok":true}'

        message_type = str(payload.get("message_type") or "").strip().lower()
        sender_id = str(payload.get("user_id") or "").strip()
        sender = payload.get("sender") if isinstance(payload.get("sender"), dict) else {}
        sender_name = str(sender.get("card") or sender.get("nickname") or sender_id).strip() or sender_id
        text, mention_to_bot = self._extract_text_and_mention(payload)
        require_mention = False
        conversation_id = ""
        conversation_type = "person"
        if message_type == "group":
            group_id = str(payload.get("group_id") or "").strip()
            if not group_id:
                return 200, {"Content-Type": "application/json"}, b'{"ok":true}'
            conversation_id = f"group:{group_id}"
            conversation_type = "group"
            require_mention = self.require_mention_in_group
        else:
            if not sender_id:
                return 200, {"Content-Type": "application/json"}, b'{"ok":true}'
            conversation_id = f"user:{sender_id}"
            conversation_type = "person"
            mention_to_bot = True

        ts = payload.get("time")
        timestamp = now_iso()
        if isinstance(ts, (int, float)) and ts > 0:
            timestamp = datetime.fromtimestamp(float(ts), tz=datetime.now().astimezone().tzinfo).isoformat(timespec="seconds")

        message_id = str(payload.get("message_id") or "").strip()
        self.service.ingest_event(
            {
                "event_id": f"{self.channel_name}_{message_id or uuid.uuid4().hex[:10]}",
                "channel": self.channel_name,
                "conversation_id": conversation_id,
                "conversation_type": conversation_type,
                "display_name": conversation_id,
                "sender_id": sender_id,
                "sender_name": sender_name,
                "message_id": message_id,
                "message_text": text,
                "attachments": [],
                "timestamp": timestamp,
                "is_mention_to_bot": bool(mention_to_bot),
                "require_mention": bool(require_mention),
            }
        )
        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]:
        if not self.api_base:
            return False, f"{self.channel_name} onebot_api_base is missing"
        if attachments:
            return False, f"{self.channel_name} attachments are not supported in OneBot adapter yet"
        if not message:
            return False, f"{self.channel_name} message is required"

        conversation_text = str(conversation_id or "").strip()
        action = "send_private_msg"
        payload: Dict[str, Any] = {"message": message}
        if conversation_text.startswith("group:"):
            action = "send_group_msg"
            payload["group_id"] = conversation_text.split("group:", 1)[1].strip()
        elif conversation_text.startswith("user:"):
            action = "send_private_msg"
            payload["user_id"] = conversation_text.split("user:", 1)[1].strip()
        else:
            payload["user_id"] = conversation_text

        try:
            response = requests.post(f"{self.api_base}/{action}", headers=self._auth_headers(), json=payload, timeout=30)
            if response.status_code >= 300:
                return False, response.text
            data = response.json() if response.content else {}
            status = str(data.get("status") or "").strip().lower()
            retcode = int(data.get("retcode") or 0)
            if status not in {"ok", "async"} or retcode != 0:
                return False, json.dumps(data, ensure_ascii=False)
            message_id = str(((data.get("data") or {}).get("message_id")) or "")
            return True, message_id
        except Exception as exc:
            return False, str(exc)
