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

import json
from typing import Any, Dict, List, Optional, Tuple

import requests

from cheapclaw_channel_base import ChannelAdapter
from tool_runtime_helpers import now_iso


class WhatsAppCloudAdapter(ChannelAdapter):
    name = "whatsapp"

    def __init__(self, config: Dict[str, Any], service: Any, *, logger=None):
        super().__init__(config, service, logger=logger)
        self.access_token = str(config.get("access_token") or "").strip()
        self.phone_number_id = str(config.get("phone_number_id") or "").strip()
        self.verify_token = str(config.get("verify_token") or "").strip()
        self.api_version = str(config.get("api_version") or "v21.0").strip()
        self.api_root = f"https://graph.facebook.com/{self.api_version}"

    def handle_webhook_get(self, path: str, query: Dict[str, List[str]], headers: Dict[str, str]) -> Tuple[int, Dict[str, str], bytes]:
        mode = (query.get("hub.mode") or [""])[0]
        token = (query.get("hub.verify_token") or [""])[0]
        challenge = (query.get("hub.challenge") or [""])[0]
        if mode == "subscribe" and token and token == self.verify_token:
            return 200, {"Content-Type": "text/plain; charset=utf-8"}, challenge.encode("utf-8")
        return 403, {"Content-Type": "text/plain; charset=utf-8"}, b"forbidden"

    def _download_inbound_media(
        self,
        *,
        conversation_id: str,
        message_id: str,
        media_id: str,
        filename: str,
        mime_type: str,
        kind: str,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        payload = {
            "kind": kind,
            "filename": filename,
            "mime_type": mime_type,
            "whatsapp_media_id": media_id,
        }
        if isinstance(metadata, dict):
            payload.update({k: v for k, v in metadata.items() if v not in (None, "")})
        if not self.access_token or not media_id:
            return payload
        try:
            meta_resp = requests.get(
                f"{self.api_root}/{media_id}",
                headers={"Authorization": f"Bearer {self.access_token}"},
                timeout=30,
            )
            meta_resp.raise_for_status()
            meta = meta_resp.json() if meta_resp.content else {}
            url = str(meta.get("url") or "").strip()
            if url:
                payload["url"] = url
            response = requests.get(
                url,
                headers={"Authorization": f"Bearer {self.access_token}"},
                timeout=60,
            )
            response.raise_for_status()
            return self._store_inbound_attachment_bytes(
                conversation_id=conversation_id,
                message_id=message_id,
                filename=filename,
                content=response.content,
                mime_type=mime_type,
                kind=kind,
                metadata=payload,
            )
        except Exception as exc:
            payload["download_error"] = str(exc)
            return payload

    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"}'
        for entry in payload.get("entry", []):
            for change in entry.get("changes", []):
                value = change.get("value", {})
                contacts = value.get("contacts") or []
                contact_name = str((contacts[0].get("profile") or {}).get("name") or "") if contacts else ""
                for message in value.get("messages", []) or []:
                    msg_type = str(message.get("type") or "").strip().lower()
                    text = str(((message.get("text") or {}).get("body")) or "").strip()
                    attachments: List[Dict[str, Any]] = []
                    if msg_type in {"image", "document", "audio", "video", "sticker"}:
                        media = message.get(msg_type) or {}
                        media_id = str(media.get("id") or "").strip()
                        caption = str(media.get("caption") or "").strip()
                        if caption and not text:
                            text = caption
                        mime_type = str(media.get("mime_type") or "").strip()
                        filename = str(media.get("filename") or "").strip() or f"{msg_type}_{media_id}"
                        kind = "image" if msg_type in {"image", "sticker"} else ("video" if msg_type == "video" else ("audio" if msg_type == "audio" else "file"))
                        if media_id:
                            attachments.append(
                                self._download_inbound_media(
                                    conversation_id=str(message.get("from") or "").strip(),
                                    message_id=str(message.get("id") or ""),
                                    media_id=media_id,
                                    filename=filename,
                                    mime_type=mime_type or "application/octet-stream",
                                    kind=kind,
                                    metadata={"whatsapp_message_type": msg_type},
                                )
                            )
                    from_id = str(message.get("from") or "").strip()
                    self.service.ingest_event(
                        {
                            "event_id": f"whatsapp_{message.get('id')}",
                            "channel": "whatsapp",
                            "conversation_id": from_id,
                            "conversation_type": "person",
                            "display_name": contact_name or from_id,
                            "sender_id": from_id,
                            "sender_name": contact_name,
                            "message_id": str(message.get("id") or ""),
                            "message_text": text,
                            "attachments": attachments,
                            "timestamp": now_iso(),
                            "is_mention_to_bot": True,
                            "require_mention": False,
                        }
                    )
        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.access_token or not self.phone_number_id:
            return False, "whatsapp access_token or phone_number_id is missing"
        last_remote_id = ""
        normalized = self._normalize_attachments(attachments)
        if message:
            response = requests.post(
                f"{self.api_root}/{self.phone_number_id}/messages",
                headers={"Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json"},
                json={"messaging_product": "whatsapp", "to": conversation_id, "type": "text", "text": {"body": message}},
                timeout=30,
            )
            if response.status_code >= 300:
                return False, response.text
            data = response.json()
            if data.get("messages"):
                last_remote_id = str((data.get("messages") or [{}])[0].get("id") or "")
        for item in normalized:
            mime_type = item["mime_type"]
            media_type = "image" if mime_type.startswith("image/") else "document"
            with open(item["path"], "rb") as fh:
                files = {"file": (item["filename"], fh, mime_type)}
                upload_resp = requests.post(
                    f"{self.api_root}/{self.phone_number_id}/media",
                    headers={"Authorization": f"Bearer {self.access_token}"},
                    data={"messaging_product": "whatsapp", "type": mime_type},
                    files=files,
                    timeout=60,
                )
            if upload_resp.status_code >= 300:
                return False, upload_resp.text
            upload_data = upload_resp.json()
            media_id = str(upload_data.get("id") or "")
            if not media_id:
                return False, json.dumps(upload_data, ensure_ascii=False)
            payload = {"messaging_product": "whatsapp", "to": conversation_id, "type": media_type, media_type: {"id": media_id}}
            if media_type == "document":
                payload[media_type]["filename"] = item["filename"]
            caption = item["caption"] or (message if not last_remote_id else "")
            if caption:
                payload[media_type]["caption"] = caption
            send_resp = requests.post(
                f"{self.api_root}/{self.phone_number_id}/messages",
                headers={"Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json"},
                json=payload,
                timeout=30,
            )
            if send_resp.status_code >= 300:
                return False, send_resp.text
            data = send_resp.json()
            if data.get("messages"):
                last_remote_id = str((data.get("messages") or [{}])[0].get("id") or "")
        if not message and not normalized:
            return False, "whatsapp message or attachments are required"
        return True, last_remote_id
