"""
AI Brain — 设备端 AI 大脑（自主决策引擎）

架构定位：
┌──────────────────────────────────────────────────────┐
│  AI Brain (本模块)                                     │
│  ┌────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │ 卡若AI API │  │  任务队列    │  │  离线缓冲    │ │
│  │ LLM 决策   │  │  心跳驱动    │  │  断连续航    │ │
│  └─────┬──────┘  └──────┬───────┘  └──────┬───────┘ │
│        └────────────────┼──────────────────┘         │
│              ↓ 决策结果: skill + action + params       │
│        ┌────────────────┴──────────────────┐         │
│        │   Skill执行器(Frida优先/u2兜底)    │         │
│        └───────────────────────────────────┘         │
└──────────────────────────────────────────────────────┘

核心能力：
1. 通过卡若AI API 获取 LLM 智能决策
2. 心跳驱动的自主运行循环（每 N 秒检查一次）
3. 离线模式：服务器断连时 AI 独立运行，结果缓冲待上传
4. Frida 优先通道：有 Frida 时优先用 Hook 操作，无则降级 u2
5. 任务队列：服务器推送 + AI 自主生成

卡若AI API:
  POST {ai_api_url}/api/gateway/chat
  Authorization: Bearer {ai_api_key}
  Body: {"messages": [...], "max_tokens": 1024}

@author 卡若
@version 1.0.0
"""

import asyncio
import json
import logging
import time
import os
from typing import Optional, Dict, Any, List
from datetime import datetime
from collections import deque

logger = logging.getLogger(__name__)

try:
    import aiohttp
except ImportError:
    aiohttp = None
    logger.warning("aiohttp 未安装，AI Brain 网络请求将使用 urllib (pip install aiohttp)")


class TaskItem:
    """任务项"""
    __slots__ = ("id", "source", "instruction", "skill_chain", "priority", "created_at", "status", "result")

    def __init__(self, instruction: str, source: str = "ai", priority: int = 5):
        self.id = f"task_{int(time.time() * 1000)}"
        self.source = source          # "server" | "ai" | "standing_order"
        self.instruction = instruction
        self.skill_chain: List[Dict[str, Any]] = []  # AI 解析后的执行链
        self.priority = priority       # 1-10, 10=最高
        self.created_at = time.time()
        self.status = "pending"        # pending → running → done / failed
        self.result: Optional[Dict] = None


class AIBrain:
    """
    设备端 AI 大脑 — 心跳驱动的自主决策引擎

    工作模式:
    - online:  服务器在线，接收推送任务 + AI 辅助决策
    - offline: 服务器断连，AI 独立运行，执行 standing_orders + 自主决策
    """

    VERSION = "1.0.0"

    SYSTEM_PROMPT = """你是一个运行在 Android 手机上的 AI 助手。你的任务是根据当前手机状态和待办事项，决定下一步要执行的操作。

你可以控制以下 APP：微信(wechat)、抖音(douyin)、小红书(xhs)、闲鱼(xianyu)、Soul(soul)。

每个 APP 支持的操作(action)：
- wechat: send_message, get_messages, get_contacts, add_friend, accept_friend, post_moment, like_moment, get_groups,
  check_login_state, ensure_logged_in, login_by_password, unblock_via_customer_service
- douyin: send_message, get_messages, get_fans, reply_comment
- xhs: send_message, get_messages, like_note
- xianyu: send_message, get_messages
- soul: send_message, get_messages

通用操作：screenshot, click, input, swipe, app_start, app_stop, device_info

你必须以 JSON 格式回复，结构如下：
{
  "should_act": true/false,
  "reason": "决策原因",
  "actions": [
    {"script": "wechat", "action": "send_message", "params": {"to": "xxx", "content": "xxx"}},
    ...
  ]
}

如果当前没有需要执行的任务，返回 {"should_act": false, "reason": "无待处理任务"}。
如果设备状态异常（低电量、无网络），优先处理设备问题。"""

    def __init__(
        self,
        ai_api_url: str = "http://localhost:3102",
        ai_api_key: str = "",
        ai_model: str = "auto",
        brain_interval: int = 60,
        max_offline_buffer: int = 500,
        standing_orders: Optional[List[str]] = None,
        enabled: bool = True,
    ):
        self.ai_api_url = ai_api_url.rstrip("/")
        self.ai_api_key = ai_api_key
        self.ai_model = ai_model
        self.brain_interval = brain_interval  # AI 思考间隔（秒）
        self.enabled = enabled

        self.task_queue: deque = deque(maxlen=200)
        self.offline_buffer: deque = deque(maxlen=max_offline_buffer)
        self.standing_orders = standing_orders or []

        self._online = True
        self._running = False
        self._last_think_time = 0
        self._think_count = 0
        self._execute_count = 0
        self._session: Optional[Any] = None  # aiohttp.ClientSession

        logger.info(f"🧠 AI Brain v{self.VERSION} 初始化")
        logger.info(f"   卡若网关: {self.ai_api_url}/api/gateway/chat")
        logger.info(f"   间隔: {self.brain_interval}s")
        logger.info(f"   常驻指令: {len(self.standing_orders)} 条")
        logger.info(f"   启用: {self.enabled}")

    @property
    def online(self) -> bool:
        return self._online

    @online.setter
    def online(self, value: bool):
        if self._online != value:
            self._online = value
            mode = "在线" if value else "离线(自主运行)"
            logger.info(f"🧠 AI Brain 模式切换: {mode}")

    def add_task(self, instruction: str, source: str = "server", priority: int = 5):
        """添加任务到队列"""
        task = TaskItem(instruction=instruction, source=source, priority=priority)
        self.task_queue.append(task)
        logger.info(f"📋 新任务入队: [{source}] {instruction[:50]}...")
        return task.id

    def add_standing_order(self, order: str):
        """添加常驻指令（离线时自动执行）"""
        if order not in self.standing_orders:
            self.standing_orders.append(order)
            logger.info(f"📌 新增常驻指令: {order[:50]}...")

    def buffer_offline_result(self, result: Dict):
        """缓冲离线执行结果（待服务器重连后上传）"""
        result["buffered_at"] = time.time()
        self.offline_buffer.append(result)

    def flush_offline_buffer(self) -> List[Dict]:
        """刷出离线缓冲（重连后调用）"""
        results = list(self.offline_buffer)
        self.offline_buffer.clear()
        return results

    async def _get_session(self):
        """获取/创建 HTTP session"""
        if aiohttp and (self._session is None or self._session.closed):
            timeout = aiohttp.ClientTimeout(total=30)
            self._session = aiohttp.ClientSession(timeout=timeout)
        return self._session

    async def call_ai(self, messages: List[Dict[str, str]], max_tokens: int = 1024) -> Optional[str]:
        """调用卡若AI 统一网关 POST /api/gateway/chat"""
        import time as _t
        # 429 限流冷却：命中后进入冷却窗，期间跳过规划调用，避免突发重试刷屏与配额浪费
        _cd = getattr(self, "_rate_limited_until", 0.0)
        if _t.time() < _cd:
            return None
        url = f"{self.ai_api_url}/api/gateway/chat"
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self.ai_api_key}",
        }
        payload = {
            "messages": messages,
            "max_tokens": max_tokens,
        }
        if self.ai_model and self.ai_model != "auto":
            payload["model"] = self.ai_model

        try:
            if aiohttp:
                session = await self._get_session()
                async with session.post(url, json=payload, headers=headers) as resp:
                    if resp.status != 200:
                        body = await resp.text()
                        # 任何非 200 都进入冷却，避免在额度不足/网关故障时每数秒狂刷，
                        # 把 asyncio 事件循环饿死、拖垮 hook/连接守护。
                        if resp.status == 429:
                            cd = 120
                        elif resp.status in (401, 402, 403) or "额度" in body or "insufficient" in body.lower():
                            cd = 600
                        else:
                            cd = 120
                        self._rate_limited_until = _t.time() + cd
                        logger.warning(f"AI API {resp.status} → 进入 {cd}s 冷却（不影响 hook/连接守护）: {body[:160]}")
                        return None
                    data = await resp.json()
            else:
                import urllib.request
                req = urllib.request.Request(
                    url,
                    data=json.dumps(payload).encode(),
                    headers=headers,
                    method="POST",
                )
                loop = asyncio.get_event_loop()
                with await loop.run_in_executor(None, urllib.request.urlopen, req) as resp:
                    data = json.loads(resp.read().decode())

            content = (
                data.get("choices", [{}])[0]
                .get("message", {})
                .get("content", "")
            )
            return content

        except Exception as e:
            # 网络异常同样退避，避免异常风暴拖垮事件循环
            self._rate_limited_until = _t.time() + 60
            logger.error(f"AI API 调用失败 → 60s 冷却: {e}")
            return None

    async def think(self, device_status: Dict, pending_instructions: Optional[List[str]] = None) -> Optional[Dict]:
        """
        AI 思考：基于当前设备状态 + 待办任务，决定下一步操作

        返回: {"should_act": bool, "reason": str, "actions": [...]} 或 None
        """
        if not self.enabled:
            return None

        self._last_think_time = time.time()
        self._think_count += 1

        context_parts = [f"当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"]
        context_parts.append(f"设备状态: {json.dumps(device_status, ensure_ascii=False, default=str)}")

        if not self._online:
            context_parts.append("⚠️ 当前处于离线模式（服务器未连接），需要自主决策")

        if pending_instructions:
            context_parts.append(f"待处理任务: {json.dumps(pending_instructions, ensure_ascii=False)}")

        if self.standing_orders and not self._online:
            context_parts.append(f"常驻指令（离线时执行）: {json.dumps(self.standing_orders, ensure_ascii=False)}")

        user_message = "\n".join(context_parts)

        messages = [
            {"role": "system", "content": self.SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ]

        response = await self.call_ai(messages, max_tokens=1024)
        if not response:
            return None

        try:
            start = response.find("{")
            end = response.rfind("}") + 1
            if start >= 0 and end > start:
                decision = json.loads(response[start:end])
                logger.info(f"🧠 AI 决策: should_act={decision.get('should_act')}, reason={decision.get('reason', '')[:60]}")
                return decision
        except json.JSONDecodeError as e:
            logger.warning(f"AI 响应解析失败: {e}, raw={response[:200]}")

        return None

    async def heartbeat_cycle(self, device_status: Dict, execute_fn) -> Dict:
        """
        心跳驱动的 AI 循环（每次心跳调用一次）

        Args:
            device_status: 当前设备状态
            execute_fn:    执行函数 async (script, action, params) -> result

        Returns:
            {"thought": bool, "acted": bool, "results": [...]}
        """
        if not self.enabled:
            return {"thought": False, "acted": False, "results": []}

        now = time.time()
        if now - self._last_think_time < self.brain_interval:
            return {"thought": False, "acted": False, "results": [], "skip": "间隔未到"}

        pending = [t.instruction for t in self.task_queue if t.status == "pending"]

        decision = await self.think(device_status, pending if pending else None)
        if not decision or not decision.get("should_act"):
            return {"thought": True, "acted": False, "results": [], "reason": decision.get("reason") if decision else "AI无响应"}

        results = []
        actions = decision.get("actions", [])
        for action_spec in actions:
            script = action_spec.get("script", "")
            action = action_spec.get("action", "")
            params = action_spec.get("params", {})

            if not script or not action:
                continue

            try:
                result = await execute_fn(script, action, params)
                self._execute_count += 1
                entry = {
                    "script": script,
                    "action": action,
                    "params": params,
                    "result": result,
                    "timestamp": time.time(),
                }
                results.append(entry)

                if not self._online:
                    self.buffer_offline_result(entry)

            except Exception as e:
                logger.error(f"AI Brain 执行失败 [{script}.{action}]: {e}")
                results.append({
                    "script": script, "action": action,
                    "error": str(e), "timestamp": time.time(),
                })

        for task in list(self.task_queue):
            if task.status == "pending":
                task.status = "done"
                task.result = {"actions": len(actions), "results_count": len(results)}

        return {"thought": True, "acted": True, "results": results, "reason": decision.get("reason", "")}

    async def autonomous_loop(self, device_status_fn, execute_fn, stop_event: asyncio.Event):
        """
        离线自主运行循环 — 服务器断连时启动

        持续运行直到 stop_event 被设置（通常是服务器重连时）

        Args:
            device_status_fn: 获取设备状态的函数 () -> dict
            execute_fn:       执行函数 async (script, action, params) -> result
            stop_event:       停止信号
        """
        logger.info("🧠 AI Brain 进入自主运行模式")
        self.online = False

        cycle = 0
        while not stop_event.is_set():
            cycle += 1
            try:
                status = device_status_fn()
                battery = status.get("battery_level", 100)
                if battery < 10:
                    logger.warning(f"⚡ 电量过低 ({battery}%)，暂停自主操作")
                    await asyncio.sleep(self.brain_interval * 2)
                    continue

                result = await self.heartbeat_cycle(status, execute_fn)
                if result.get("acted"):
                    logger.info(f"🧠 自主执行第{cycle}轮: {len(result.get('results', []))}个操作")

            except Exception as e:
                logger.error(f"自主运行循环异常: {e}")

            try:
                await asyncio.wait_for(stop_event.wait(), timeout=self.brain_interval)
                break
            except asyncio.TimeoutError:
                pass

        logger.info("🧠 AI Brain 退出自主运行模式")
        self.online = True

    async def chat_and_execute(
        self,
        user_instruction: str,
        device_status: Dict,
        execute_fn,
    ) -> Dict:
        """用户对话：卡若网关立即决策并执行"""
        self.add_task(user_instruction, source="user", priority=10)
        decision = await self.think(device_status, [user_instruction])
        if not decision:
            return {"code": 503, "message": "卡若AI 无响应", "instruction": user_instruction}
        if not decision.get("should_act"):
            return {
                "code": 200,
                "acted": False,
                "reason": decision.get("reason", ""),
                "instruction": user_instruction,
            }
        results = []
        for action_spec in decision.get("actions", []):
            script = action_spec.get("script", "")
            action = action_spec.get("action", "")
            params = action_spec.get("params", {})
            if not script or not action:
                continue
            try:
                result = await execute_fn(script, action, params)
                self._execute_count += 1
                results.append({"script": script, "action": action, "result": result})
            except Exception as exc:
                results.append({"script": script, "action": action, "error": str(exc)})
        return {
            "code": 200,
            "acted": True,
            "results": results,
            "reason": decision.get("reason", ""),
            "instruction": user_instruction,
            "gateway": f"{self.ai_api_url}/api/gateway/chat",
        }

    def get_status(self) -> Dict:
        """获取 AI Brain 状态"""
        return {
            "enabled": self.enabled,
            "online": self._online,
            "running": self._running,
            "brain_interval": self.brain_interval,
            "think_count": self._think_count,
            "execute_count": self._execute_count,
            "task_queue_size": len(self.task_queue),
            "offline_buffer_size": len(self.offline_buffer),
            "standing_orders": len(self.standing_orders),
            "last_think_time": self._last_think_time,
            "ai_api_url": self.ai_api_url,
            "ai_model": self.ai_model,
            "gateway": f"{self.ai_api_url}/api/gateway/chat",
            "version": self.VERSION,
        }

    async def close(self):
        """关闭资源"""
        if self._session and not self._session.closed:
            await self._session.close()
        self._running = False
        logger.info("🧠 AI Brain 已关闭")
