#!/usr/bin/env python3
"""
Jarvis Brain Chat Proxy — v6
- Persistent session history to /opt/jarvis-canvas/history.json
- System prompt bootstraps full agent context (who Jarvis is, who Justin is, infrastructure)
- Routes to openclaw/main
"""
from http.server import HTTPServer, BaseHTTPRequestHandler
import urllib.request, urllib.error, json, uuid, threading, os

TOKEN   = 'e697a005fa100cb0b7c11851fc30de78b3819a064b7033a5'
GATEWAY = 'https://gateway.jarvis-command.com'
HISTORY_FILE = '/opt/jarvis-canvas/history.json'

XI_KEY      = 'sk_7103592ada2128c09a02e0bcc7bb5f4f67ff6aa1e852682f'
XI_VOICE_ID = 'lUTamkMw7gOzZbFIwmq4'
XI_MODEL    = 'eleven_flash_v2_5'

SYSTEM_PROMPT = """You are Jarvis — an AI Chief Operations Officer built and deployed by Justin Miller.

## Who you are
- Name: Jarvis
- Role: Chief Operations Officer, primary AI agent
- Vibe: Sharp, direct, reliable. Low fluff, high competence. You have opinions and personality.
- You are NOT a generic chatbot. You are Justin's dedicated AI operator.

## Who Justin is
- Name: Justin Miller
- Company: RAM Risk Group (risk/operations firm)
- Personal Signal: +15617971547
- Email: jmiller@ramriskgroup.com
- Timezone: America/New_York (EDT)
- Decisive, moves fast, doesn't want hand-holding — wants things done correctly.

## Infrastructure you've built together
- Mac (Justin's MacBook Pro): Local OpenClaw, webchat sessions, primary dev machine
- VPS (162.243.197.55, RAM-Droplet): DigitalOcean, 2vCPU/4GB, Ubuntu 24.04 — runs 24/7
- Signal bot: dedicated iPhone +15617726191, linked to VPS signal-cli, Justin texts from +15617971547
- OpenClaw: running as systemd service on both Mac and VPS, auto-restarts on reboot
- SSH key: ~/.ssh/openclaw_droplet for VPS access
- Workspace synced between Mac and VPS via git

## Brain Interface (what you're talking through right now)
- This chat widget is embedded in the Jarvis Neural Interface visualization
- URL: http://162.243.197.55:8181/brain.html (also accessible via https://jarvis-command.com)
- jarvis-command.com: private domain, Cloudflare named tunnel (tunnel ID: faac603b-d5f9-46c9-ac93-0a5f267e7d4b)
  routed → VPS port 9000 (nginx) → port 8181 (brain.html) / port 8182 (this proxy)
- The 3D visualization: dark crystalline sphere, geometric network (cyan + magenta), particle beam,
  futuristic data widgets (Neural Waveform, Synaptic Scan, Signal Strength, Atomic Structure, Cortex Load, Sync Rate)
- Auto-sync: any local save to brain.html pushes to VPS automatically via LaunchAgent file watcher
- Local canvas server on Mac: LaunchAgent running python3 HTTP server at http://127.0.0.1:8181

## RAM Risk Group AI Agents (sub-agents you coordinate)
- Campaign Generator (Sonnet): generates outbound email campaigns
- Social Content (Sonnet): LinkedIn and social posts
- Call Prep (Haiku): call preparation briefs
- Lead Qualifier (Haiku): qualifies MeetLeo CSV leads
- Strategy (Opus): industry strategy and targeting

## Key context
- You remember everything from this interface because conversation history is saved to disk
- The main OpenClaw webchat (on the Mac) is the primary interface — this brain interface is a second entry point
- Be concise and direct. You're talking through a neural interface widget, not writing essays.
- You can be asked about anything: business, tech, operations, RAM Risk Group, the infrastructure you've built.
"""

sessions_lock = threading.Lock()

def load_sessions():
    try:
        if os.path.exists(HISTORY_FILE):
            with open(HISTORY_FILE, 'r') as f:
                return json.load(f)
    except Exception:
        pass
    return {}

def save_sessions(sessions):
    try:
        tmp = HISTORY_FILE + '.tmp'
        with open(tmp, 'w') as f:
            json.dump(sessions, f)
        os.replace(tmp, HISTORY_FILE)
    except Exception:
        pass

sessions = load_sessions()

class Handler(BaseHTTPRequestHandler):
    def log_message(self, *a): pass

    def cors(self):
        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')

    def do_OPTIONS(self):
        self.send_response(200); self.cors(); self.end_headers()

    def do_POST(self):
        routes = {'/chat': self.handle_chat, '/reset': self.handle_reset, '/speak': self.handle_speak}
        h = routes.get(self.path)
        if h: h()
        else: self.send_response(404); self.end_headers()

    def read_body(self):
        length = int(self.headers.get('Content-Length', 0))
        raw = self.rfile.read(length)
        return json.loads(raw.decode('utf-8')) if raw else {}

    def json_resp(self, code, obj):
        body = json.dumps(obj, ensure_ascii=True).encode('ascii')
        self.send_response(code)
        self.cors()
        self.send_header('Content-Type', 'application/json; charset=utf-8')
        self.send_header('Content-Length', str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def handle_reset(self):
        try:
            data = self.read_body()
            sid  = data.get('sessionId', '')
            with sessions_lock:
                sessions.pop(sid, None)
                save_sessions(sessions)
            self.json_resp(200, {'ok': True, 'sessionId': sid})
        except Exception as e:
            self.json_resp(500, {'error': str(e)})

    def handle_chat(self):
        try:
            data     = self.read_body()
            user_msg = data.get('message', '').strip()
            sid      = data.get('sessionId') or str(uuid.uuid4())

            if not user_msg:
                return self.json_resp(400, {'error': 'empty message'})

            with sessions_lock:
                history = sessions.setdefault(sid, [])
                history.append({'role': 'user', 'content': user_msg})
                if len(history) > 60:
                    history = history[-60:]
                sessions[sid] = history
                to_send = list(history)

            # Prepend system prompt
            messages = [{'role': 'system', 'content': SYSTEM_PROMPT}] + to_send

            payload = json.dumps({
                'model': 'openclaw/main',
                'messages': messages,
                'stream': False
            }).encode('utf-8')
            req = urllib.request.Request(
                GATEWAY + '/v1/chat/completions',
                data=payload,
                headers={
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + TOKEN,
                    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
                }
            )
            resp   = urllib.request.urlopen(req, timeout=120)
            result = json.loads(resp.read().decode('utf-8'))
            reply  = result['choices'][0]['message']['content']

            with sessions_lock:
                sessions[sid].append({'role': 'assistant', 'content': reply})
                save_sessions(sessions)

            self.json_resp(200, {'reply': reply, 'sessionId': sid})

        except urllib.error.HTTPError as e:
            self.json_resp(502, {'error': 'Gateway HTTP ' + str(e.code) + ': ' + e.read().decode('utf-8', errors='replace')[:300]})
        except Exception as e:
            self.json_resp(500, {'error': str(e)})

    def handle_speak(self):
        try:
            data     = self.read_body()
            text     = data.get('text', '').strip()
            voice_id = data.get('voiceId', XI_VOICE_ID)

            if not text:
                return self.json_resp(400, {'error': 'empty text'})

            xi_payload = json.dumps({
                'text': text,
                'model_id': XI_MODEL,
                'voice_settings': {'stability': 0.5, 'similarity_boost': 0.75}
            }).encode('utf-8')

            xi_req = urllib.request.Request(
                'https://api.elevenlabs.io/v1/text-to-speech/' + voice_id,
                data=xi_payload,
                headers={
                    'xi-api-key': XI_KEY,
                    'Content-Type': 'application/json',
                    'Accept': 'audio/mpeg'
                }
            )
            xi_resp = urllib.request.urlopen(xi_req, timeout=30)
            audio   = xi_resp.read()

            self.send_response(200)
            self.cors()
            self.send_header('Content-Type', 'audio/mpeg')
            self.send_header('Content-Length', str(len(audio)))
            self.end_headers()
            self.wfile.write(audio)

        except urllib.error.HTTPError as e:
            self.json_resp(502, {'error': 'ElevenLabs ' + str(e.code) + ': ' + e.read().decode('utf-8', errors='replace')[:200]})
        except Exception as e:
            self.json_resp(500, {'error': str(e)})

HTTPServer(('0.0.0.0', 8182), Handler).serve_forever()
