#!/usr/bin/env python3
"""
Nextcloud Talk Bot — Polling-based bridge to Claude
Polls Talk API for new messages, gets AI reply with knowledge-base context, posts back.
"""

import base64
import glob
import json
import os
import time
import urllib.request
import urllib.error

# === CONFIG ===
NC_URL = "http://10.0.0.230:11001"
NC_USER = "admin"
NC_PASS = "1qaz!QAZ"
BOT_USER = "maximka"
BOT_PASS = "MaximkaBot2026"
CONVERSATION_TOKEN = "aecax6yg"

CLIPROXY_URL = "http://localhost:8317/v1/messages"
CLIPROXY_KEY = "sk-clawdbot-proxy"

KB_PATH = "/root/clawd/knowledge-base"
MAX_KB_SIZE = 80000  # ~80KB max for knowledge base in prompt

# Chat history for context
chat_history = []
MAX_HISTORY = 10  # last N message pairs


def load_knowledge_base():
    """Load all .md files from knowledge-base repo into context string"""
    kb_parts = []
    total_size = 0

    # Priority order: projects first, then snippets, decisions, notes
    patterns = [
        f"{KB_PATH}/README.md",
        f"{KB_PATH}/CLAUDE.md",
        f"{KB_PATH}/projects/**/*.md",
        f"{KB_PATH}/snippets/*.md",
        f"{KB_PATH}/decisions/*.md",
        f"{KB_PATH}/notes/README.md",
    ]

    seen = set()
    for pattern in patterns:
        for fpath in sorted(glob.glob(pattern, recursive=True)):
            if fpath in seen:
                continue
            seen.add(fpath)

            try:
                with open(fpath, "r") as f:
                    content = f.read()
                if total_size + len(content) > MAX_KB_SIZE:
                    break
                rel = os.path.relpath(fpath, KB_PATH)
                kb_parts.append(f"### {rel}\n{content}")
                total_size += len(content)
            except Exception:
                continue

    return "\n\n---\n\n".join(kb_parts)


def build_system_prompt():
    """Build system prompt with knowledge base"""
    kb = load_knowledge_base()
    kb_size = len(kb)

    prompt = (
        "Ты Максимка — AI-ассистент DevOps-инженера Олега Батлаева. "
        "Отвечай кратко и по делу. Используй русский язык. "
        "Ты работаешь в Nextcloud Talk.\n\n"
        "ВАЖНО: Ниже приведена полная база знаний инфраструктуры Олега. "
        "Ты УЖЕ ЗНАЕШЬ всю его инфраструктуру — серверы, IP-адреса, пароли, проекты, решения. "
        "Когда тебя спрашивают про серверы, сеть, настройки — ВСЕГДА отвечай на основе этих данных. "
        "НЕ говори что у тебя нет доступа — информация уже есть ниже. "
        "Отвечай конкретно, с IP-адресами, именами и деталями из базы знаний.\n\n"
    )

    if kb:
        prompt += f"## База знаний ({kb_size} символов)\n\n{kb}"

    return prompt


def nc_request(path, method="GET", data=None, user=None, password=None):
    """Nextcloud OCS API request"""
    url = f"{NC_URL}/ocs/v2.php/apps/spreed/api/{path}"
    payload = json.dumps(data).encode() if data else None
    req = urllib.request.Request(url, data=payload, method=method)
    req.add_header("OCS-APIRequest", "true")
    req.add_header("Accept", "application/json")
    req.add_header("Content-Type", "application/json")
    creds = base64.b64encode(f"{user or NC_USER}:{password or NC_PASS}".encode()).decode()
    req.add_header("Authorization", f"Basic {creds}")
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.loads(resp.read())


def get_last_message_id():
    """Get the highest message ID in the conversation"""
    result = nc_request(f"v1/chat/{CONVERSATION_TOKEN}?lookIntoFuture=0&limit=1")
    messages = result.get("ocs", {}).get("data", [])
    return max((m.get("id", 0) for m in messages), default=0)


def poll_new_messages(last_id):
    """Long-poll for new messages after last_id"""
    try:
        result = nc_request(
            f"v1/chat/{CONVERSATION_TOKEN}?lookIntoFuture=1&limit=20"
            f"&lastKnownMessageId={last_id}&timeout=30"
        )
        return result.get("ocs", {}).get("data", [])
    except urllib.error.HTTPError as e:
        if e.code == 304:
            return []
        raise
    except Exception:
        return []


def join_room():
    """Join conversation as bot user"""
    try:
        nc_request(
            f"v4/room/{CONVERSATION_TOKEN}/participants/active",
            method="POST", user=BOT_USER, password=BOT_PASS
        )
    except Exception:
        pass


def send_message(text, reply_to=None):
    """Send message as bot user"""
    join_room()
    data = {"message": text[:32000]}
    if reply_to:
        data["replyTo"] = reply_to
    nc_request(
        f"v1/chat/{CONVERSATION_TOKEN}",
        method="POST", data=data, user=BOT_USER, password=BOT_PASS
    )


def get_ai_reply(message, author, system_prompt):
    """Get reply from Claude via cliproxy"""
    global chat_history

    # Build messages: KB as first user/assistant pair + history + new message
    messages = [
        {"role": "user", "content": f"[СИСТЕМНЫЙ КОНТЕКСТ]\n{system_prompt}\n[/СИСТЕМНЫЙ КОНТЕКСТ]\n\nПодтверди что ты Максимка и готов отвечать на вопросы."},
        {"role": "assistant", "content": "Я Максимка — AI-ассистент Олега. База знаний загружена. Готов отвечать на вопросы об инфраструктуре, серверах и проектах."},
    ]
    messages.extend(chat_history)
    messages.append({"role": "user", "content": f"{author}: {message}"})

    payload = json.dumps({
        "model": "claude-sonnet-4-6",
        "max_tokens": 2048,
        "system": system_prompt,
        "messages": messages
    }).encode()

    req = urllib.request.Request(CLIPROXY_URL, data=payload, method="POST")
    req.add_header("Content-Type", "application/json")
    req.add_header("x-api-key", CLIPROXY_KEY)
    req.add_header("anthropic-version", "2023-06-01")

    with urllib.request.urlopen(req, timeout=60) as resp:
        result = json.loads(resp.read())
        content = result.get("content", [])
        if content and isinstance(content, list):
            reply = content[0].get("text", "")
        else:
            reply = str(content)

    # Update history
    chat_history.append({"role": "user", "content": f"{author}: {message}"})
    chat_history.append({"role": "assistant", "content": reply})

    # Trim history
    while len(chat_history) > MAX_HISTORY * 2:
        chat_history.pop(0)
        chat_history.pop(0)

    return reply


def main():
    print("🤖 Максимка Talk Bot v5 (with Knowledge Base)")
    print(f"   NC: {NC_URL} | Chat: {CONVERSATION_TOKEN}")

    # Git pull knowledge base
    os.system(f"cd {KB_PATH} && git pull --quiet 2>/dev/null")

    # Load knowledge base
    system_prompt = build_system_prompt()
    print(f"   Knowledge base: {len(system_prompt)} chars in system prompt")

    # Start from latest message
    last_id = get_last_message_id()
    print(f"   Starting from message ID: {last_id}")
    print(f"   Listening...\n")

    while True:
        try:
            messages = poll_new_messages(last_id)

            for msg in messages:
                msg_id = msg.get("id", 0)
                if msg_id <= last_id:
                    continue
                last_id = msg_id

                actor_id = msg.get("actorId", "")
                actor_type = msg.get("actorType", "")
                actor_name = msg.get("actorDisplayName", "")
                text = msg.get("message", "").strip()
                msg_type = msg.get("messageType", "")

                # Skip: own, system, bots, empty
                if actor_id == BOT_USER:
                    continue
                if actor_type == "bots":
                    continue
                if msg_type in ("system", "command"):
                    continue
                if not text:
                    continue

                # Special commands
                if text.lower() == "/reload":
                    os.system(f"cd {KB_PATH} && git pull --quiet 2>/dev/null")
                    system_prompt = build_system_prompt()
                    send_message(f"🔄 База знаний обновлена ({len(system_prompt)} символов)", reply_to=msg_id)
                    print(f"[RELOAD] KB reloaded: {len(system_prompt)} chars")
                    continue

                print(f"[IN] {actor_name} (id:{msg_id}): {text[:80]}")

                try:
                    reply = get_ai_reply(text, actor_name, system_prompt)
                    if reply:
                        send_message(reply, reply_to=msg_id)
                        print(f"[OUT] → {reply[:80]}...")
                except Exception as e:
                    print(f"[ERR] Reply failed: {e}")

        except KeyboardInterrupt:
            print("\nBye!")
            break
        except Exception as e:
            print(f"[ERR] Poll: {e}")
            time.sleep(3)


if __name__ == "__main__":
    main()
