#!/usr/bin/env python3
"""Hermes Web Chat — a lightweight web frontend for Hermes Agent (FIXED VERSION)"""

import http.server
import json
import os
import sys
import urllib.request
import urllib.error
import urllib.parse
import ssl
import threading
from http.server import HTTPServer

HERMES_API_HOST = os.getenv("HERMES_API_HOST", "localhost")
HERMES_API_PORT = int(os.getenv("HERMES_API_PORT", "8642"))
HERMES_API_BASE = f"http://{HERMES_API_HOST}:{HERMES_API_PORT}"
LISTEN_HOST = os.getenv("HERMES_WEB_HOST", "0.0.0.0")
LISTEN_PORT = int(os.getenv("HERMES_WEB_PORT", "8095"))

# Use the gateway's actual API key
API_KEY = "hms_server_806b81a7e1f79ab04ee61f56c9eb357168400ebfe6daf651"

# ─── UI ──→
UI_INDEX = r'''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Iris — Hermes Web Chat</title>
<style>
  :root {
    --bg: #0d1117; --surface: #161b22; --border: #30363d;
    --text: #c9d1d9; --text-dim: #8b949e; --accent: #58a6ff;
    --user-bg: #1f6feb; --bot-bg: #161b22; --input-bg: #0d1117;
    --scrollbar: #30363d; --scrollbar-bg: #161b22;
  }
  * { margin:0; padding:0; box-sizing:border-box; }
  html, body { height:100%; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--text); }
  #app { display:flex; flex-direction:column; height:100%; max-width: 800px; margin: 0 auto; }

  /* Header */
  #header {
    display:flex; align-items:center; justify-content:space-between;
    padding: 16px 20px; border-bottom:1px solid var(--border);
    background: var(--surface); flex-shrink:0;
  }
  #header .brand { font-size: 18px; font-weight: 700; color: var(--accent); letter-spacing: -0.5px; }
  #header .brand span { color: var(--text-dim); font-weight: 400; font-size: 13px; margin-left: 8px; }
  .header-btns { display:flex; gap:8px; }
  .header-btns button {
    background: transparent; border: 1px solid var(--border); color: var(--text-dim);
    padding: 6px 12px; border-radius: 6px; cursor:pointer; font-size: 13px;
    transition: all .15s;
  }
  .header-btns button:hover { border-color: var(--accent); color: var(--accent); }

  /* Messages */
  #messages {
    flex: 1; overflow-y: auto; padding: 20px;
    display: flex; flex-direction: column; gap: 16px;
    scrollbar-width: thin; scrollbar-color: var(--scrollbar) var(--scrollbar-bg);
  }
  #messages::-webkit-scrollbar { width: 6px; }
  #messages::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 3px; }

  .msg { display: flex; gap: 10px; max-width: 85%; animation: fadeIn .2s ease; }
  .msg.user { align-self: flex-end; flex-direction: row-reverse; }
  .msg.bot { align-self: flex-start; }
  @keyframes fadeIn { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }

  .msg .avatar {
    width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0;
    display:flex; align-items:center; justify-content:center; font-size: 15px; font-weight:600;
  }
  .msg.user .avatar { background: var(--user-bg); color: #fff; }
  .msg.bot .avatar { background: #2d333b; color: var(--accent); }

  .msg .bubble {
    padding: 10px 14px; border-radius: 12px; line-height: 1.55; font-size: 14px;
    word-wrap: break-word; white-space: pre-wrap;
  }
  .msg.user .bubble {
    background: var(--user-bg); color: #fff; border-bottom-right-radius: 4px;
  }
  .msg.bot .bubble {
    background: var(--bot-bg); border: 1px solid var(--border); border-bottom-left-radius: 4px;
  }
  .msg.bot .bubble code {
    background: #0d419d; padding: 2px 5px; border-radius: 4px; font-size: 13px;
    font-family: 'SF Mono', 'Fira Code', monospace;
  }
  .msg.bot .bubble pre {
    background: #0d1117; border: 1px solid var(--border); border-radius: 8px;
    padding: 12px; margin: 8px 0; overflow-x: auto;
  }
  .msg.bot .bubble pre code {
    background: none; padding: 0;
  }

  /* Typing indicator */
  .typing { display:flex; gap:4px; padding:10px 14px; }
  .typing span {
    width:7px; height:7px; border-radius:50%; background: var(--text-dim);
    animation: typDot 1.2s infinite;
  }
  .typing span:nth-child(2) { animation-delay: .2s; }
  .typing span:nth-child(3) { animation-delay: .4s; }
  @keyframes typDot { 0%,60%,100%{opacity:.3;transform:translateY(0)} 30%{opacity:1;transform:translateY(-4px)} }

  /* Input */
  #input-area {
    border-top: 1px solid var(--border); background: var(--surface);
    padding: 12px 20px; flex-shrink: 0;
  }
  #input-wrap {
    display:flex; gap:8px; align-items:flex-end;
  }
  #input-wrap textarea {
    flex: 1; background: var(--input-bg); border: 1px solid var(--border);
    color: var(--text); padding: 12px; border-radius: 10px; resize:none;
    font-size: 14px; font-family: inherit; line-height: 1.5;
    outline:none; transition: border-color .15s;
    max-height: 160px; min-height: 44px;
  }
  #input-wrap textarea:focus { border-color: var(--accent); }
  #input-wrap textarea::placeholder { color: var(--text-dim); }
  #send-btn {
    width: 44px; height: 44px; border-radius: 10px; border: none;
    background: var(--user-bg); color: #fff; cursor: pointer;
    display:flex; align-items:center; justify-content:center;
    transition: background .15s, transform .1s;
  }
  #send-btn:hover { background: #388bfd; }
  #send-btn:active { transform: scale(.95); }
  #send-btn:disabled { opacity: .5; cursor: not-allowed; }
  #send-btn svg { width: 20px; height: 20px; }

  /* New chat welcome */
  .welcome {
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    height: 100%; text-align:center; gap:8px; opacity:.5;
  }
  .welcome h2 { font-size: 22px; font-weight: 600; color: var(--text); }
  .welcome p { font-size: 14px; color: var(--text-dim); }

  /* Timestamps */
  .timestamp { font-size: 11px; color: var(--text-dim); margin-top: 4px; }
  .msg.user .timestamp { text-align: right; }

</style>
</head>
<body>
<div id="app">
  <div id="header">
    <div>
      <div class="brand">Iris <span>Via Hermes Web Chat</span></div>
    </div>
    <div class="header-btns">
      <button onclick="newChat()" title="New Chat">＋</button>
      <button onclick="toggleTheme()" title="Toggle Theme" id="theme-btn">🌙</button>
    </div>
  </div>
  <div id="messages"></div>
  <div id="input-area">
    <div id="input-wrap">
      <textarea id="msg-input" placeholder="Message Iris..." rows="1"></textarea>
      <button id="send-btn" onclick="sendMessage()">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
          <line x1="22" y1="2" x2="11" y2="13"></line>
          <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
        </svg>
      </button>
    </div>
  </div>
</div>

<script>
// Session management
let sessionId = localStorage.getItem('hermes_sid') || '';
let sessionKey = localStorage.getItem('hermes_skey') || '';

// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
  const ta = document.getElementById('msg-input');
  if (ta) {
    ta.addEventListener('keydown', function(e) {
      if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        sendMessage();
      }
    });
    ta.addEventListener('input', function() {
      ta.style.height = 'auto';
      ta.style.height = Math.min(ta.scrollHeight, 160) + 'px';
    });
  }
  
  // Load history if we have a session
  if (sessionId) {
    loadHistory();
  } else {
    showWelcome();
  }
  logDebug('UI initialized');
});

function genId() { return 'sid_' + Math.random().toString(36).slice(2, 10); }
function genKey() { return 'web-' + Math.random().toString(36).slice(2, 12); }

async function loadHistory() {
  logDebug('Loading history for session: ' + sessionId);
  try {
    const resp = await fetch('/api/history', {
      headers: { 'X-Hermes-Session-Id': sessionId }
    });
    if (!resp.ok) {
      logDebug('History load failed with status: ' + resp.status);
      showWelcome();
      return;
    }
    const data = await resp.json();
    logDebug('History response: ' + JSON.stringify(data).substring(0, 100));
    const messages = data.messages || [];
    const messagesDiv = document.getElementById('messages');
    messagesDiv.innerHTML = '';
    if (!messages.length) {
      showWelcome();
    } else {
      messages.forEach(function(m) { appendMessage(m.role, m.content, false); });
    }
  } catch(e) {
    logDebug('Error loading history: ' + e.message);
    showWelcome();
  }
}

async function sendMessage() {
  const input = document.getElementById('msg-input');
  const text = input.value.trim();
  logDebug('sendMessage called with text: ' + text);
  
  if (!text) {
    logDebug('No text, returning early');
    return;
  }

  const messagesDiv = document.getElementById('messages');
  if (!sessionId) {
    sessionId = genId();
    sessionKey = genKey();
    localStorage.setItem('hermes_sid', sessionId);
    localStorage.setItem('hermes_skey', sessionKey);
    logDebug('Created new session: ' + sessionId);
  }

  // Add user message
  appendMessage('user', text, false);
  input.value = '';
  input.style.height = 'auto';

  const sendBtn = document.getElementById('send-btn');
  sendBtn.disabled = true;

  // Show typing indicator
  const typingEl = document.createElement('div');
  typingEl.className = 'msg bot';
  typingEl.innerHTML = '<div class="avatar">✦</div><div class="bubble"><div class="typing"><span></span><span></span><span></span></div></div>';
  messagesDiv.appendChild(typingEl);
  messagesDiv.scrollTop = messagesDiv.scrollHeight;

  // Prepare the request to our proxy server (not to Ollama directly)
  const payload = {
    model: "qwen3.6:latest",
    messages: [{ role: "user", content: text }],
    stream: false
  };
  
  logDebug('Sending proxy request to /api/chat with payload: ' + JSON.stringify(payload).substring(0, 100));

  try {
    const resp = await fetch('/api/chat', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Hermes-Session-Id': sessionId,
        'X-Hermes-Session-Key': sessionKey
      },
      body: JSON.stringify(payload)
    });

    logDebug('Proxy response status: ' + resp.status);

    // Remove typing indicator
    const typingDiv = typingEl.parentNode;
    if (typingDiv) typingDiv.remove();

    const respText = await resp.text();
    logDebug('Proxy response body: ' + respText.substring(0, 200));

    if (!resp.ok) {
      appendMessage('bot', 'Error: ' + respText, true);
      return;
    }
    
    // Parse the response
    let content = '';
    try {
      const data = JSON.parse(respText);
      logDebug('Response parsed: ' + JSON.stringify(data).substring(0, 100));
      content = data.content || data.message || data.reply || data.text || '';
      
      // Handle if content is nested
      if (content && typeof content === 'object') {
        content = JSON.stringify(content);
      }
    } catch(parseErr) {
      logDebug('JSON parse error: ' + parseErr.message);
      content = 'Invalid response format';
    }
    
    if (!content || content === '') {
      logDebug('Empty content, showing raw response');
      content = 'Received empty response from server';
    }
    
    appendMessage('bot', content, true);

  } catch(e) {
    logDebug('Network error: ' + e.message);
    // Remove typing if it exists
    const typingDiv = typingEl.parentNode;
    if (typingDiv) typingDiv.remove();
    
    appendMessage('bot', 'Connection error: ' + e.message, true);
  }

  sendBtn.disabled = false;
  input.focus();
}

function escapeHtml(text) {
  return text
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function appendMessage(role, content, animate) {
  const messagesDiv = document.getElementById('messages');
  const div = document.createElement('div');
  div.className = 'msg ' + role;
  const avatar = role === 'user' ? 'J' : '✦';
  
  // Get current time
  const now = new Date();
  const timeStr = now.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'});
  
  // Escape and format content safely
  let safeContent;
  if (content) {
    safeContent = escapeHtml(content);
  } else {
    safeContent = escapeHtml('');
  }

  // Check if content looks like code (starts with newline + indentation)
  if (typeof content === 'string' && content.trim().startsWith('<pre>')) {
    // Already HTML
    div.innerHTML = '<div class="avatar">' + avatar + '</div><div class="bubble">' + content + '<div class="timestamp">' + timeStr + '</div></div>';
  } else if (typeof content === 'string' && content.indexOf('```') > -1) {
    // Has code blocks - create pre element directly
    const codeMatch = content.match(/```(\w*)\n([\s\S]*?)```/g);
    if (codeMatch) {
      safeContent = content.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
        .replace(/```(\w*)\n([\s\S]*?)```/g, '<pre style="background:#0d1117;padding:12px;border-radius:8px;margin:8px 0;overflow-x:auto"><pre style="background:#0d1117;padding:12px;border-radius:8px;margin:8px 0;overflow-x:auto;position:relative;"><code>$2</code></pre></pre>');
      div.innerHTML = '<div class="avatar">' + avatar + '</div><div class="bubble">' + safeContent + '<div class="timestamp">' + timeStr + '</div></div>';
    } else {
      div.innerHTML = '<div class="avatar">' + avatar + '</div><div class="bubble">' + safeContent + '<div class="timestamp">' + timeStr + '</div></div>';
    }
  } else {
    // Plain text
    div.innerHTML = '<div class="avatar">' + avatar + '</div><div class="bubble">' + safeContent + '<div class="timestamp">' + timeStr + '</div></div>';
  }
  
  messagesDiv.appendChild(div);
  messagesDiv.scrollTop = messagesDiv.scrollHeight;
  logDebug('Appended message, total messages: ' + messagesDiv.children.length);
}

function showWelcome() {
  const messagesDiv = document.getElementById('messages');
  messagesDiv.innerHTML = '<div class="welcome"><h2>🌸 Hello, Josh!</h2><p>I\'m Iris, your Iris boss\'s AI assistant.</p><p>What can I help you with today?</p></div>';
  logDebug('Welcome screen shown');
}

function newChat() {
  sessionId = '';
  sessionKey = '';
  localStorage.setItem('hermes_sid','');
  localStorage.setItem('hermes_skey','');
  const messagesDiv = document.getElementById('messages');
  messagesDiv.innerHTML = '';
  showWelcome();
  logDebug('New chat started');
}

// Theme toggle
let darkTheme = true;
let lastTheme = localStorage.getItem('hermes_theme');
if (lastTheme === 'light') {
  darkTheme = false;
}

function toggleTheme() {
  darkTheme = !darkTheme;
  localStorage.setItem('hermes_theme', darkTheme ? 'dark' : 'light');
  document.getElementById('theme-btn').textContent = darkTheme ? '🌙' : '☀️';
  
  if (darkTheme) {
    document.documentElement.style.setProperty('--bg', '#0d1117');
    document.documentElement.style.setProperty('--surface', '#161b22');
    document.documentElement.style.setProperty('--border', '#30363d');
    document.documentElement.style.setProperty('--text', '#c9d1d9');
    document.documentElement.style.setProperty('--text-dim', '#8b949e');
    document.documentElement.style.setProperty('--input-bg', '#0d1117');
    document.documentElement.style.setProperty('--bot-bg', '#161b22');
    document.documentElement.style.setProperty('--scrollbar', '#30363d');
    document.documentElement.style.setProperty('--scrollbar-bg', '#161b22');
  } else {
    document.documentElement.style.setProperty('--bg', '#f6f8fa');
    document.documentElement.style.setProperty('--surface', '#ffffff');
    document.documentElement.style.setProperty('--border', '#d0d7de');
    document.documentElement.style.setProperty('--text', '#1f2328');
    document.documentElement.style.setProperty('--text-dim', '#656d76');
    document.documentElement.style.setProperty('--input-bg', '#f6f8fa');
    document.documentElement.style.setProperty('--bot-bg', '#f6f8fa');
    document.documentElement.style.setProperty('--scrollbar', '#d0d7de');
    document.documentElement.style.setProperty('--scrollbar-bg', '#ffffff');
  }
  logDebug('Theme toggled to ' + (darkTheme ? 'dark' : 'light'));
}

// Debug logging helper
function logDebug(msg) {
  console.log('[Iris] ' + msg);
}
</script>
</body>
</html>'''

print(f"Generated UI size: {len(UI_INDEX)} bytes")

# ─── Handler ────────────────────
class ChatHandler(http.server.BaseHTTPRequestHandler):
    def log_message(self, format, *args):
        pass  # suppress per-request logs

    def _send_json(self, code, data):
        body = json.dumps(data).encode()
        self.send_response(code)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Content-Length', str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self):
        if self.path == '/' or self.path == '/index.html':
            self.send_response(200)
            self.send_header('Content-Type', 'text/html')
            self.send_header('Content-Length', str(len(UI_INDEX.encode())))
            self.end_headers()
            self.wfile.write(UI_INDEX.encode())
        elif self.path == '/health':
            self._send_json(200, {"status": "ok", "api": f"{HERMES_API_BASE}/v1"})
        else:
            self.send_error(404)

    def do_POST(self):
        if self.path == '/api/chat':
            self._handle_chat()
        elif self.path == '/api/history':
            self._handle_history()
        else:
            self.send_error(404)

    def _handle_chat(self):
        sess_id = self.headers.get('X-Hermes-Session-Id', '')
        sess_key = self.headers.get('X-Hermes-Session-Key', '')
        try:
            length = int(self.headers.get('Content-Length', 0))
            raw_body = self.rfile.read(length)
            body = json.loads(raw_body)
        except:
            self._send_json(400, {"error": "invalid JSON"})
            return

        # Forward to the actual API server
        api_url = f"{HERMES_API_BASE}/v1/chat/completions"
        
        # Build request with proper headers
        req = urllib.request.Request(
            api_url,
            data=raw_body,  # Forward the raw JSON as-is, don't re-encode
            method='POST'
        )
        
        # Add headers
        req.add_header('Content-Type', 'application/json')
        if sess_id:
            req.add_header('X-Hermes-Session-Id', sess_id)
        if sess_key:
            req.add_header('X-Hermes-Session-Key', sess_key)
        if API_KEY:
            req.add_header('X-Hermes-API-Key', API_KEY)
        
        try:
            with urllib.request.urlopen(req, timeout=300) as resp:
                data = json.loads(resp.read())
                content = data.get('choices', [{}])[0].get('message', {}).get('content', '')
                self._send_json(200, {
                    "content": content,
                    "model": data.get('model', 'iris'),
                    "session_id": sess_id or "new"
                })
        except urllib.error.HTTPError as e:
            err_body = e.read().decode()
            self._send_json(e.code, {"error": err_body})
        except Exception as e:
            self._send_json(502, {"error": f"proxy error: {e}"})

    def _handle_history(self):
        sess_id = self.headers.get('X-Hermes-Session-Id', '')
        
        api_url = f"{HERMES_API_BASE}/v1/messages"
        req = urllib.request.Request(api_url, method='GET')
        
        if sess_id:
            req.add_header('X-Hermes-Session-Id', sess_id)
        if API_KEY:
            req.add_header('X-Hermes-API-Key', API_KEY)
        
        try:
            with urllib.request.urlopen(req, timeout=10) as resp:
                data = json.loads(resp.read())
                self._send_json(200, data)
        except Exception:
            self._send_json(200, {"messages": []})

# ─── Main ──────────────
def main():
    server = HTTPServer((LISTEN_HOST, LISTEN_PORT), ChatHandler)
    print(f"{'='*50}")
    print(f"  Iris Web Chat")
    print(f"  Access: http://localhost:{LISTEN_PORT}")
    print(f"  Proxy:  {HERMES_API_BASE}")
    print(f"  API_KEY: {API_KEY[:10]}...")
    print(f"{'='*50}")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        server.shutdown()

if __name__ == '__main__':
    main()
