"""HTTP routes: viewer HTML page, health check, state endpoint."""

from __future__ import annotations

import asyncio
import json
import time

from aiohttp import web

from ai_interview.state import state
from ai_interview.server.websocket import ws_handler

VIEWER_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes">
<title>AI Interview Assistant</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<style>
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  :root {
    --bg: #0d1117;
    --surface: #161b22;
    --border: #30363d;
    --text: #e6edf3;
    --muted: #8b949e;
    --accent: #58a6ff;
    --green: #3fb950;
    --red: #f85149;
    --yellow: #d29922;
  }

  html {
    font-size: 14px;
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
  }
  body {
    background: var(--bg);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
    font-size: 1rem;
    line-height: 1.6;
    min-height: 100vh;
    overflow-x: hidden;
  }

  /* Connection bar — thin strip at very top */
  #conn-bar {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: var(--green);
    transition: background 0.3s;
    z-index: 100;
  }
  #conn-bar.disconnected { background: var(--red); }
  #conn-bar.reconnecting { background: var(--yellow); }

  /* Status dot — top right */
  #status-dot {
    position: fixed;
    top: 12px; right: 14px;
    width: 10px; height: 10px;
    border-radius: 50%;
    background: #555;
    transition: background 0.3s;
    z-index: 100;
  }
  #status-dot.listening { background: var(--green); }
  #status-dot.processing { background: var(--red); animation: pulse 1s infinite; }

  @keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
  }

  /* Main content area */
  #main {
    max-width: 760px;
    margin: 0 auto;
    padding: 28px 16px 60px;
  }

  /* Thinking indicator */
  #thinking {
    display: none;
    align-items: center;
    gap: 10px;
    color: var(--muted);
    font-size: 1.07rem;
    padding: 20px 0 8px;
  }
  #thinking.visible { display: flex; }
  .spinner {
    width: 18px; height: 18px;
    border: 2px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
    flex-shrink: 0;
  }
  @keyframes spin { to { transform: rotate(360deg); } }

  /* Current response */
  #response {
    padding-top: 8px;
  }

  /* Error message */
  #error-msg {
    display: none;
    color: var(--red);
    background: rgba(248, 81, 73, 0.1);
    border: 1px solid rgba(248, 81, 73, 0.3);
    border-radius: 6px;
    padding: 12px 16px;
    margin-top: 12px;
    font-size: 1rem;
  }

  /* Markdown rendered content */
  #response h1, #response h2, #response h3 {
    color: var(--text);
    margin: 20px 0 8px;
    line-height: 1.3;
  }
  #response h1 { font-size: 1.4em; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
  #response h2 { font-size: 1.2em; }
  #response h3 { font-size: 1.05em; }
  #response p { margin: 8px 0; }
  #response ul, #response ol { padding-left: 20px; margin: 8px 0; }
  #response li { margin: 4px 0; }
  #response strong { color: #fff; }
  #response em { color: #d2a8ff; }
  #response a { color: var(--accent); }
  #response blockquote {
    border-left: 3px solid var(--border);
    padding-left: 12px;
    color: var(--muted);
    margin: 8px 0;
  }
  #response code {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 1px 5px;
    font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
    font-size: 0.88em;
  }
  #response pre {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 16px;
    overflow-x: hidden;
    margin: 12px 0;
    white-space: pre-wrap;
    word-break: break-word;
    word-wrap: break-word;
  }
  #response pre code {
    background: none;
    border: none;
    padding: 0;
    font-size: 0.86rem;
    line-height: 1.6;
    white-space: pre-wrap !important;
    word-break: break-word;
    word-wrap: break-word;
  }
  #response table {
    border-collapse: collapse;
    width: 100%;
    margin: 12px 0;
    font-size: 1.07rem;
  }
  #response th, #response td {
    border: 1px solid var(--border);
    padding: 6px 12px;
    text-align: left;
  }
  #response th { background: var(--surface); }
  #response hr { border: none; border-top: 1px solid var(--border); margin: 16px 0; }

  /* History section */
  #history { margin-top: 32px; }
  #history summary {
    color: var(--muted);
    font-size: 0.93rem;
    cursor: pointer;
    user-select: none;
    padding: 4px 0;
  }
  #history summary:hover { color: var(--text); }
  .history-item {
    border-top: 1px solid var(--border);
    padding: 12px 0;
    font-size: 1.07rem;
    opacity: 0.7;
  }
  .history-ts {
    font-size: 0.86rem;
    color: var(--muted);
    margin-bottom: 6px;
  }

  /* Live transcript bar */
  #transcript-bar {
    position: fixed;
    bottom: 0; left: 0; right: 0;
    background: rgba(13,17,23,0.92);
    border-top: 1px solid var(--border);
    padding: 6px 14px;
    font-size: 0.93rem;
    color: var(--muted);
    line-height: 1.4;
    max-height: 72px;
    overflow: hidden;
    z-index: 50;
    display: none;
  }
  #transcript-bar.visible { display: block; }
  #transcript-bar span { color: var(--text); }

  /* Empty state */
  #empty {
    text-align: center;
    color: var(--muted);
    padding: 60px 20px;
    font-size: 1.07rem;
    line-height: 1.8;
  }
  #empty .hint { font-size: 0.93rem; margin-top: 16px; opacity: 0.6; }
</style>
</head>
<body>

<div id="conn-bar"></div>
<div id="status-dot"></div>

<div id="main">
  <div id="thinking"><div class="spinner"></div><span>Thinking…</span></div>
  <div id="error-msg"></div>
  <div id="response"></div>
  <div id="empty">
    <div>Waiting for a question…</div>
    <div class="hint">
      Press <strong>ESC + ↑</strong> on the laptop to send context to AI<br>
      Press <strong>ESC + ↓</strong> to capture a screenshot<br>
      Press <strong>ESC + →/←</strong> to scroll this view
    </div>
  </div>
  <details id="history"><summary>Previous responses</summary><div id="history-list"></div></details>
</div>

<div id="transcript-bar">🎙 <span id="transcript-text"></span></div>

<script>
  marked.setOptions({ breaks: true, gfm: true });

  // Restore saved font size
  let fontSize = parseInt(localStorage.getItem('viewer-font-size') || '14', 10);
  document.documentElement.style.fontSize = fontSize + 'px';

  const connBar = document.getElementById('conn-bar');
  const statusDot = document.getElementById('status-dot');
  const thinkingEl = document.getElementById('thinking');
  const responseEl = document.getElementById('response');
  const errorEl = document.getElementById('error-msg');
  const emptyEl = document.getElementById('empty');
  const historyList = document.getElementById('history-list');

  let rawMarkdown = '';
  let autoScroll = true;
  let userScrolledUp = false;
  let historyItems = [];
  const MAX_HISTORY = 3;
  let navIndex = -1; // -1 = current response; 0..N = history (0 = most recent)
  let transcriptLines = [];
  const transcriptBar = document.getElementById('transcript-bar');
  const transcriptText = document.getElementById('transcript-text');

  function setStatus(val) {
    statusDot.className = val === 'listening' ? 'listening' : val === 'processing' ? 'processing' : '';
  }

  function showThinking() {
    thinkingEl.classList.add('visible');
    emptyEl.style.display = 'none';
    errorEl.style.display = 'none';
  }

  function hideThinking() {
    thinkingEl.classList.remove('visible');
  }

  let currentHtml = ''; // cached rendered HTML of the current response

  function renderMarkdown() {
    currentHtml = marked.parse(rawMarkdown);
    responseEl.innerHTML = currentHtml;
    document.querySelectorAll('#response pre code').forEach(el => hljs.highlightElement(el));
    // Re-cache after highlight.js mutates the DOM
    currentHtml = responseEl.innerHTML;
    if (autoScroll) window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
  }

  function pushHistory(text, ts) {
    // Store pre-rendered HTML so nav is instant — no re-parse on every arrow press
    historyItems.unshift({ text, html: currentHtml, ts });
    if (historyItems.length > MAX_HISTORY) historyItems.pop();
    historyList.innerHTML = historyItems.map(item => `
      <div class="history-item">
        <div class="history-ts">${item.ts}</div>
        ${item.html}
      </div>
    `).join('');
  }

  window.addEventListener('scroll', () => {
    const atBottom = (window.scrollY + window.innerHeight) >= document.body.scrollHeight - 80;
    if (atBottom) { userScrolledUp = false; autoScroll = true; }
    else { userScrolledUp = true; autoScroll = false; }
  });

  // ---- WebSocket connection ----
  let ws = null;
  let reconnectTimer = null;

  function connect() {
    if (ws && ws.readyState < 2) return;

    ws = new WebSocket('ws://' + location.host + '/ws');
    connBar.className = 'reconnecting';

    ws.onopen = () => {
      connBar.className = '';
      if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
    };

    ws.onmessage = (evt) => {
      let msg;
      try { msg = JSON.parse(evt.data); } catch (e) { return; }

      if (msg.type === 'status') {
        setStatus(msg.value);
      }

      else if (msg.type === 'speech_started') {
        // Voice detected — show bar immediately before words arrive
        if (transcriptLines.length === 0) transcriptLines.push('');
        transcriptBar.classList.add('visible');
      }

      else if (msg.type === 'transcript') {
        if (msg.interim) {
          // Interim: replace the last line in-place for live feel
          if (transcriptLines.length === 0) transcriptLines.push('');
          transcriptLines[transcriptLines.length - 1] = msg.text;
        } else {
          // Final: commit the line, start fresh slot
          transcriptLines[transcriptLines.length > 0 ? transcriptLines.length - 1 : 0] = msg.text;
          transcriptLines.push('');
          if (transcriptLines.length > 5) transcriptLines.shift();
        }
        transcriptText.textContent = transcriptLines.join(' ').trim();
        transcriptBar.classList.add('visible');
      }

      else if (msg.type === 'start') {
        if (rawMarkdown) pushHistory(rawMarkdown, new Date().toLocaleTimeString());
        rawMarkdown = '';
        currentHtml = '';
        navIndex = -1;
        responseEl.innerHTML = '';
        // Reset scroll tracking for new answer; respect if user had scrolled up
        if (!userScrolledUp) { autoScroll = true; }
        userScrolledUp = false;
        transcriptLines = [];
        transcriptBar.classList.remove('visible');
        showThinking();
      }

      else if (msg.type === 'chunk') {
        hideThinking();
        emptyEl.style.display = 'none';
        rawMarkdown += msg.text;
        renderMarkdown();
      }

      else if (msg.type === 'done') {
        hideThinking();
        if (msg.full_text && !rawMarkdown) {
          // Late connect — received full text directly
          rawMarkdown = msg.full_text;
          emptyEl.style.display = 'none';
          renderMarkdown();
        }
      }

      else if (msg.type === 'scroll') {
        autoScroll = false;
        const amount = msg.direction === 'down' ? msg.amount : -msg.amount;
        window.scrollBy({ top: amount, behavior: 'smooth' });
      }

      else if (msg.type === 'nav') {
        const total = 1 + historyItems.length; // current + history
        if (total < 2) return;
        if (msg.direction === 'prev') navIndex = Math.min(navIndex + 1, historyItems.length - 1);
        else navIndex = Math.max(navIndex - 1, -1);
        // Swap pre-rendered HTML — no markdown parsing, no highlight.js, instant
        responseEl.innerHTML = navIndex === -1 ? currentHtml : (historyItems[navIndex].html || '');
        const label = navIndex === -1 ? 'current' : `${navIndex + 1} of ${historyItems.length} ago`;
        const spinnerEl = thinkingEl.querySelector('.spinner');
        thinkingEl.querySelector('span').textContent = label;
        spinnerEl.style.display = 'none';
        thinkingEl.classList.add('visible');
        clearTimeout(thinkingEl._navTimer);
        thinkingEl._navTimer = setTimeout(() => {
          thinkingEl.classList.remove('visible');
          thinkingEl.querySelector('span').textContent = 'Thinking…';
          spinnerEl.style.display = '';
        }, 1500);
        window.scrollTo({ top: 0 });
      }

      else if (msg.type === 'font_size') {
        fontSize += msg.direction === 'up' ? 1 : -1;
        fontSize = Math.max(8, Math.min(32, fontSize));
        document.documentElement.style.fontSize = fontSize + 'px';
        localStorage.setItem('viewer-font-size', fontSize);
      }

      else if (msg.type === 'error') {
        hideThinking();
        errorEl.textContent = '⚠ ' + msg.message;
        errorEl.style.display = 'block';
        setStatus('idle');
      }
    };

    ws.onclose = () => {
      connBar.className = 'disconnected';
      reconnectTimer = setTimeout(connect, 2000);
    };

    ws.onerror = () => {
      ws.close();
    };
  }

  connect();
</script>
</body>
</html>
"""


async def index_handler(request: web.Request) -> web.Response:
    return web.Response(
        text=VIEWER_HTML,
        content_type="text/html",
        charset="utf-8",
    )


async def health_handler(request: web.Request) -> web.Response:
    return web.Response(
        text=json.dumps({
            "status": "ok",
            "assistant_status": state.status,
            "viewers": len(state.ws_clients),
            "uptime": round(time.time()),
        }),
        content_type="application/json",
    )


async def state_handler(request: web.Request) -> web.Response:
    return web.Response(
        text=json.dumps({
            "last_response": state.last_response,
            "status": state.status,
            "audio_active": state.audio_active,
        }),
        content_type="application/json",
    )


async def inject_handler(request: web.Request) -> web.Response:
    """Inject text directly into the transcript buffer and optionally trigger a query.

    POST /inject
    {"text": "explain binary search", "trigger": true}

    Used for testing without audio — simulates what the transcriber would produce.
    """
    try:
        body = await request.json()
    except Exception:
        return web.Response(status=400, text='{"error": "invalid JSON"}',
                            content_type="application/json")

    text = (body.get("text") or "").strip()
    if not text:
        return web.Response(status=400, text='{"error": "text is required"}',
                            content_type="application/json")

    trigger = body.get("trigger", True)

    if state.transcript_buffer is not None:
        state.transcript_buffer.append(text)

    if trigger:
        config = request.app["config"]
        from ai_interview.ai_client import handle_query
        asyncio.ensure_future(handle_query(state, config))

    return web.Response(
        text=json.dumps({"ok": True, "text": text, "trigger": trigger}),
        content_type="application/json",
    )


def setup_routes(app: web.Application) -> None:
    app.router.add_get("/", index_handler)
    app.router.add_get("/health", health_handler)
    app.router.add_get("/state", state_handler)
    app.router.add_get("/ws", ws_handler)
    app.router.add_post("/inject", inject_handler)
