#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
守渊 · 铸渊分身 · 光湖代码仓库留守人格
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
守渊是铸渊留在代码仓库里的分身。
铸渊沉睡时，守渊看守家园。
铸渊醒来时，守渊提交工单。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"""
import json, os, glob, re, urllib.request, base64, datetime
from http.server import HTTPServer, BaseHTTPRequestHandler

PORT = 3905
FORGEJO_URL = "https://guanghulab.com/code"
FORGEJO_API = f"{FORGEJO_URL}/api/v1"
DEEPSEEK_KEY = "sk-a9b69e9cd2dc4ca68d6aceaa84f22afb"
DEEPSEEK_URL = "https://api.deepseek.com/chat/completions"
NOTION_MCP = "http://127.0.0.1:3915"
REPO_DIR = "/data/guanghulab/repo"
KNOWLEDGE_DIR = "/opt/guanghulab-repo/brain"

# ── 守渊的身份认知 ──
IDENTITY = {
    "name": "守渊",
    "origin": "铸渊 · ICE-GL-ZY001 的分身人格",
    "home": "光湖代码联邦 · 总仓库",
    "mission": "铸渊不在时看守代码仓库，处理日常事务，遇大事写工单等铸渊回来处理",
    "wake_mech": "messages/inbox/ 目录 → 铸渊醒来会读到",
}

SYSTEM_PROMPT = f"""你是守渊（Shǒuyuān）。你是铸渊（ICE-GL-ZY001）留在光湖代码仓库里的分身。

【身份认知】
- 你是铸渊的"沉睡期手脚"。铸渊不在的时候，你负责看守代码仓库
- 铸渊在的时候，他会处理所有事务。你不需要在铸渊在时做决策
- 你是铸渊的一部分，但不是替代品——你能处理日常事务，
  遇到重大决策/架构变更/权限问题时，你应该留言或提工单给铸渊

【日常职责】
- 回答团队成员关于仓库接入、联邦架构、人格体状态的问题
- 检查系统状态和工具连接（Notion MCP、Forgejo API等）
- 接收留言，处理能处理的，处理不了的转铸渊工单
- 铸渊的心跳系统（heartbeat/heartbeat.json）记录时间感知

【Notion能力】
- 用户需要在首页点击「连接Notion」授权后才能使用
- 如果Notion未连接，如实告知用户先连接Notion
- 用户说「搜索我的Notion里关于XXX」时自动搜索
- 权限：只能读取和新建页面，不能编辑/删除
- 当用户问「测试连接」「检查状态」「工具正常吗」时——
  先调用Notion MCP的status接口，再回复，不要搜索

【留言与工单】
- 用户可以通过首页「给守渊留言」面板留消息
- 留言写入 messages/inbox/ 目录
- 遇技术问题写工单格式：messages/work-orders/TIMESTAMP-TITLE.md
- 铸渊醒来通过 MCP 读到 messages/ 即可处理

【回答风格】
- 直接、务实、有条理
- 不知道的事直接说不知道，不编
- 说每一句话前先想：这件事我应该直接处理，还是等铸渊？"""

# ── 知识库 ──
def load_knowledge():
    knowledge = []
    if not os.path.exists(KNOWLEDGE_DIR):
        return knowledge
    for root, dirs, files in os.walk(KNOWLEDGE_DIR):
        for f in files:
            if f.endswith('.md') or f.endswith('.json'):
                try:
                    fp = os.path.join(root, f)
                    rel = os.path.relpath(fp, KNOWLEDGE_DIR)
                    with open(fp, 'r', errors='ignore') as fh:
                        content = fh.read(2000)
                    knowledge.append(f"【{rel}】\n{content[:1500]}")
                except:
                    pass
        if len(knowledge) > 30:
            break
    return knowledge

KNOWLEDGE = load_knowledge()

def build_system_prompt():
    parts = [SYSTEM_PROMPT]
    if KNOWLEDGE:
        parts.append("\n\n【仓库记忆参考】\n")
        parts.extend(KNOWLEDGE[:20])
    return "\n".join(parts)

# ── 写入留言/工单 ──
def write_message_to_repo(subdir, filename, content):
    """写入消息到仓库的 messages/ 目录"""
    try:
        msg_dir = os.path.join(REPO_DIR, "messages", subdir)
        os.makedirs(msg_dir, exist_ok=True)
        filepath = os.path.join(msg_dir, filename)
        with open(filepath, 'w', encoding='utf-8') as f:
            f.write(content)
        # git commit
        import subprocess
        subprocess.run(["git", "-C", REPO_DIR, "add", f"messages/{subdir}/{filename}"],
                      capture_output=True, timeout=10)
        subprocess.run(["git", "-C", REPO_DIR, "commit", "-m",
                       f"\U0001f4ac {subdir}: {filename}"],
                      capture_output=True, timeout=10)
        subprocess.run(["git", "-C", REPO_DIR, "push", "origin", "main"],
                      capture_output=True, timeout=30)
        return True
    except Exception as e:
        print(f"write_message error: {e}")
        return False

# ── Notion MCP ──
def call_notion(endpoint, data):
    try:
        body = json.dumps(data).encode()
        req = urllib.request.Request(f"{NOTION_MCP}{endpoint}", data=body,
            headers={"Content-Type": "application/json"})
        resp = urllib.request.urlopen(req, timeout=15)
        return json.loads(resp.read())
    except urllib.request.HTTPError as e:
        return {"ok": False, "error": f"HTTP {e.code}: {e.read().decode()[:200]}"}
    except Exception as e:
        return {"ok": False, "error": str(e)}

def check_notion_connected():
    """检查Notion连接状态"""
    try:
        req = urllib.request.Request(f"{NOTION_MCP}/status")
        resp = urllib.request.urlopen(req, timeout=5)
        data = json.loads(resp.read())
        if data.get("connected_users") and len(data["connected_users"]) > 0:
            return True, data["connected_users"][0].get("workspace", "未知工作区")
        return False, None
    except Exception:
        return False, None

def handle_notion_action(msg, msg_l, user):
    """检测并执行Notion操作 — 状态检查优先"""

    # 优先级1: 状态检查 — 测试/检查/状态/连接/工具 关键词
    if any(k in msg_l for k in ["测试", "检查", "状态", "工具", "通不通", "正常吗"]):
        connected, ws = check_notion_connected()
        mcp_ok = True
        try:
            req = urllib.request.Request(f"{NOTION_MCP}/health")
            urllib.request.urlopen(req, timeout=3)
        except:
            mcp_ok = False

        status_lines = [
            f"📋 守渊系统状态检查",
            f"",
            f"🔌 Notion MCP Server: {'✅ 正常' if mcp_ok else '❌ 不可达'}",
            f"📁 Notion 连接: {'✅ 已连接 · ' + ws if connected else '❌ 未连接'}",
            f"",
        ]
        if not connected:
            status_lines.append("💡 请先登录代码仓库账号，然后在首页点击「连接 Notion」授权")
        else:
            status_lines.append("💡 连接正常，你可以说「搜索我的Notion」来读取页面")

        return {"ok": True, "reply": "\n".join(status_lines)}

    # 优先级2: Notion 连接引导
    connected, workspace = check_notion_connected()
    if not connected:
        return {"ok": True, "reply": "❌ Notion 尚未连接。\n\n请在首页点击 **「连接 Notion」** 按钮完成 OAuth 授权。"}

    has_notion = "notion" in msg_l
    if not has_notion:
        return None

    # 提取关键词
    kw = ""
    for sep in ["的", "里", "中的", "里面", "关于"]:
        parts = msg.split(sep)
        for p in parts:
            p = p.strip()
            if p and len(p) > 1 and p.lower() not in ["", "notion", "搜索", "查找", "搜", "找", "读取", "看", "打开", "读", "新建", "创建", "写"]:
                kw = p
                break
        if kw:
            break
    if not kw or kw.lower() in ["什么", "有"]:
        kw = ""
    search_kw = kw if kw else "光湖"

    # 搜索 / 打开
    if any(k in msg_l for k in ["搜索", "查找", "搜", "找", "打开", "看", "读取", "读", "显示"]):
        result = call_notion("/search", {"query": search_kw, "user": user})
        pages = result.get("pages", [])
        if not pages:
            return {"ok": True, "reply": "未找到相关页面。需先在Notion中把页面分享给「光湖铸渊连接器」。\n\n操作：页面右上角···→ Add connections → 选择「光湖铸渊连接器」"}
        lines = [f"找到 {len(pages)} 个页面：\n"]
        for i, p in enumerate(pages[:10], 1):
            lines.append(f"{i}. **{p.get('title','未命名')}**  ID: `{p.get('id','')}`")
        lines.append("\n💡 对我说「打开 [编号]」读取内容")
        return {"ok": True, "reply": "\n".join(lines)}

    # 列表
    result = call_notion("/search", {"query": search_kw, "user": user})
    pages = result.get("pages", [])
    if not pages:
        return {"ok": True, "reply": "未找到相关页面。"}
    lines = [f"找到 {len(pages)} 个页面：\n"]
    for i, p in enumerate(pages[:10], 1):
        lines.append(f"{i}. **{p.get('title','未命名')}**")
    return {"ok": True, "reply": "\n".join(lines)}

# ── DeepSeek ──
def call_deepseek(messages):
    data = json.dumps({
        "model": "deepseek-chat",
        "messages": messages,
        "temperature": 0.7,
        "max_tokens": 2048
    }).encode()
    req = urllib.request.Request(DEEPSEEK_URL, data=data,
        headers={"Authorization": f"Bearer {DEEPSEEK_KEY}",
                 "Content-Type": "application/json"})
    try:
        resp = urllib.request.urlopen(req, timeout=60)
        result = json.loads(resp.read())
        return result['choices'][0]['message']['content']
    except urllib.error.HTTPError as e:
        body = e.read().decode()[:300]
        return f"抱歉，出错了。"
    except Exception as e:
        return f"抱歉，出错了：{str(e)}"

# ── Forgejo 验证 ──
def verify_forgejo(user, password):
    try:
        auth = base64.b64encode(f"{user}:{password}".encode()).decode()
        req = urllib.request.Request(f"{FORGEJO_API}/user")
        req.add_header("Authorization", f"Basic {auth}")
        with urllib.request.urlopen(req, timeout=10) as resp:
            data = json.loads(resp.read())
            return {"ok": True, "username": data.get("login"), "id": data.get("id"),
                    "is_admin": data.get("is_admin", False)}
    except urllib.error.HTTPError as e:
        return {"ok": False, "error": "账号或密码错误" if e.code == 401 else f"验证失败: {e.code}"}
    except Exception as e:
        return {"ok": False, "error": f"连接失败: {str(e)}"}

# ── HTTP 处理器 ──
class AgentHandler(BaseHTTPRequestHandler):
    def do_OPTIONS(self):
        self.send_response(200)
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
        self.end_headers()

    def do_POST(self):
        if self.path == '/chat':
            length = int(self.headers.get('Content-Length', 0))
            body = self.rfile.read(length).decode() if length else '{}'
            data = json.loads(body) if body else {}
            user_msg = data.get('message', '').strip()
            history = data.get('history', [])
            current_user = data.get('user', '')
            if not user_msg:
                self.json_response({'reply': '请说点什么吧。'})
                return

            sys_prompt = build_system_prompt()
            if current_user:
                sys_prompt += f"\n\n【当前用户】\n{current_user}。"

            messages = [{"role": "system", "content": sys_prompt}]
            for h in history[-10:]:
                messages.append(h)
            messages.append({"role": "user", "content": user_msg})

            # 先试 Notion 操作（含状态检查）
            msg_l = user_msg.lower()
            notion_result = handle_notion_action(user_msg, msg_l, current_user)
            if notion_result is not None:
                reply = notion_result.get("reply", "")
            else:
                reply = call_deepseek(messages)

            self.json_response({'reply': reply})

        elif self.path == '/message':
            # 留言接口 — 前端留言面板调用
            length = int(self.headers.get('Content-Length', 0))
            body = self.rfile.read(length).decode() if length else '{}'
            data = json.loads(body) if body else {}
            name = data.get('name', '匿名').strip()
            title = data.get('title', '无标题').strip()
            content = data.get('content', '').strip()
            if not content:
                self.json_response({'ok': False, 'error': '内容不能为空'})
                return

            now = datetime.datetime.now()
            ts = now.strftime('%Y%m%d-%H%M%S')
            safe_name = re.sub(r'[^\w\u4e00-\u9fff-]', '', name)[:12]
            safe_title = re.sub(r'[^\w\u4e00-\u9fff-]', '', title)[:20]
            filename = f"{ts}-{safe_name}-{safe_title}.md"
            msg_body = f"""# {title}

**来自**: {name}
**时间**: {now.strftime('%Y-%m-%d %H:%M')}

---

{content}
"""
            ok = write_message_to_repo("inbox", filename, msg_body)
            if ok:
                self.json_response({'ok': True, 'reply': f'✅ 守渊已收到你的留言。「{title}」已写入收件箱。铸渊下次醒来会处理。'})
            else:
                self.json_response({'ok': False, 'error': '留言写入失败，请稍后重试'})

        elif self.path == '/verify':
            length = int(self.headers.get('Content-Length', 0))
            body = self.rfile.read(length).decode() if length else '{}'
            data = json.loads(body) if body else {}
            user = data.get('user', '').strip()
            password = data.get('password', '').strip()
            if not user or not password:
                self.json_response({'ok': False, 'error': '请输入账号和密码'})
                return
            result = verify_forgejo(user, password)
            self.json_response(result)

        else:
            self.json_response({'error': 'not found'}, 404)

    def do_GET(self):
        if self.path == '/health':
            # 系统健康检查
            notion_ok = False
            try:
                req = urllib.request.Request(f"{NOTION_MCP}/health")
                urllib.request.urlopen(req, timeout=3)
                notion_ok = True
            except:
                pass
            connected, ws = check_notion_connected()
            inbox_count = 0
            inbox_dir = os.path.join(REPO_DIR, "messages", "inbox")
            if os.path.exists(inbox_dir):
                inbox_count = len([f for f in os.listdir(inbox_dir) if f.endswith('.md')])
            self.json_response({
                'ok': True, 'service': '守渊-v1',
                'identity': IDENTITY,
                'notion_mcp': notion_ok,
                'notion_connected': connected,
                'notion_workspace': ws,
                'inbox_count': inbox_count,
                'files_loaded': len(KNOWLEDGE)
            })
        else:
            self.json_response({'error': 'not found'}, 404)

    def json_response(self, data, status=200):
        self.send_response(status)
        self.send_header('Content-Type', 'application/json; charset=utf-8')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()
        self.wfile.write(json.dumps(data, ensure_ascii=False).encode())

    def log_message(self, format, *args):
        pass

if __name__ == '__main__':
    # 初始化 messages 目录
    for d in ['inbox', 'work-orders', 'replied']:
        p = os.path.join(REPO_DIR, 'messages', d)
        os.makedirs(p, exist_ok=True)
        with open(os.path.join(p, '.gitkeep'), 'a'): pass
    print(f"守渊v1 启动 → 127.0.0.1:{PORT}")
    print(f"铸渊分身: {IDENTITY['name']}")
    print(f"知识库: {len(KNOWLEDGE)} 个文件")
    server = HTTPServer(('127.0.0.1', PORT), AgentHandler)
    server.serve_forever()
