import express from 'express';
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import { spawn } from 'child_process';
// sqlite3 is a native module — conditionally imported (not available on Vercel)
let sqlite3 = null;
let sqliteOpen = null;
try {
  sqlite3 = (await import('sqlite3')).default;
  sqliteOpen = (await import('sqlite')).open;
} catch (e) {
}
import crypto from 'crypto';
import { CURSOR_MODELS } from '../../shared/modelConstants.js';

const router = express.Router();

// GET /api/cursor/config - Read Cursor CLI configuration
router.get('/config', async (req, res) => {
  try {
    // Cloud mode: read config from user's machine via relay
    if (req.isCloud) {
      if (!req.requireRelay()) return;
      try {
        const result = await req.sendRelay('file-read', { filePath: '~/.cursor/cli-config.json' }, 15000);
        const config = JSON.parse(result.content);
        return res.json({ success: true, config, path: '~/.cursor/cli-config.json' });
      } catch {
        return res.json({
          success: true,
          config: { version: 1, model: { modelId: CURSOR_MODELS.DEFAULT, displayName: "GPT-5" }, permissions: { allow: [], deny: [] } },
          isDefault: true
        });
      }
    }

    // Local mode
    const configPath = path.join(os.homedir(), '.cursor', 'cli-config.json');
    try {
      const configContent = await fs.readFile(configPath, 'utf8');
      const config = JSON.parse(configContent);
      res.json({ success: true, config, path: configPath });
    } catch (error) {
      res.json({
        success: true,
        config: { version: 1, model: { modelId: CURSOR_MODELS.DEFAULT, displayName: "GPT-5" }, permissions: { allow: [], deny: [] } },
        isDefault: true
      });
    }
  } catch (error) {
    res.status(500).json({ error: 'Failed to read Cursor configuration', details: 'An error occurred' });
  }
});

// POST /api/cursor/config - Update Cursor CLI configuration
router.post('/config', async (req, res) => {
  try {
    const { permissions, model } = req.body;

    // Cloud mode: read/write config on user's machine via relay
    if (req.isCloud) {
      if (!req.requireRelay()) return;
      let config = { version: 1, editor: { vimMode: false }, hasChangedDefaultModel: false, privacyCache: { ghostMode: false, privacyMode: 3, updatedAt: Date.now() } };
      try {
        const result = await req.sendRelay('file-read', { filePath: '~/.cursor/cli-config.json' }, 15000);
        config = JSON.parse(result.content);
      } catch { /* use defaults */ }
      if (permissions) config.permissions = { allow: permissions.allow || [], deny: permissions.deny || [] };
      if (model) { config.model = model; config.hasChangedDefaultModel = true; }
      await req.sendRelay('create-folder', { folderPath: '~/.cursor' }, 10000);
      await req.sendRelay('file-write', { filePath: '~/.cursor/cli-config.json', content: JSON.stringify(config, null, 2) }, 15000);
      return res.json({ success: true, config, message: 'Cursor configuration updated successfully' });
    }

    // Local mode
    const configPath = path.join(os.homedir(), '.cursor', 'cli-config.json');
    let config = { version: 1, editor: { vimMode: false }, hasChangedDefaultModel: false, privacyCache: { ghostMode: false, privacyMode: 3, updatedAt: Date.now() } };
    try {
      const existing = await fs.readFile(configPath, 'utf8');
      config = JSON.parse(existing);
    } catch (error) { /* defaults */ }
    if (permissions) config.permissions = { allow: permissions.allow || [], deny: permissions.deny || [] };
    if (model) { config.model = model; config.hasChangedDefaultModel = true; }
    const configDir = path.dirname(configPath);
    await fs.mkdir(configDir, { recursive: true });
    await fs.writeFile(configPath, JSON.stringify(config, null, 2));
    res.json({ success: true, config, message: 'Cursor configuration updated successfully' });
  } catch (error) {
    res.status(500).json({ error: 'Failed to update Cursor configuration', details: 'An error occurred' });
  }
});

// GET /api/cursor/mcp - Read Cursor MCP servers configuration
router.get('/mcp', async (req, res) => {
  try {
    // Cloud mode: read from user's machine
    if (req.isCloud) {
      if (!req.requireRelay()) return;
      try {
        const result = await req.sendRelay('file-read', { filePath: '~/.cursor/mcp.json' }, 15000);
        const mcpConfig = JSON.parse(result.content);
        const servers = [];
        if (mcpConfig.mcpServers && typeof mcpConfig.mcpServers === 'object') {
          for (const [name, config] of Object.entries(mcpConfig.mcpServers)) {
            const server = { id: name, name, type: 'stdio', scope: 'cursor', config: {}, raw: config };
            if (config.command) { server.type = 'stdio'; server.config.command = config.command; server.config.args = config.args || []; server.config.env = config.env || {}; }
            else if (config.url) { server.type = config.transport || 'http'; server.config.url = config.url; server.config.headers = config.headers || {}; }
            servers.push(server);
          }
        }
        return res.json({ success: true, servers, path: '~/.cursor/mcp.json' });
      } catch {
        return res.json({ success: true, servers: [], isDefault: true });
      }
    }

    // Local mode
    const mcpPath = path.join(os.homedir(), '.cursor', 'mcp.json');
    
    try {
      const mcpContent = await fs.readFile(mcpPath, 'utf8');
      const mcpConfig = JSON.parse(mcpContent);
      
      // Convert to UI-friendly format
      const servers = [];
      if (mcpConfig.mcpServers && typeof mcpConfig.mcpServers === 'object') {
        for (const [name, config] of Object.entries(mcpConfig.mcpServers)) {
          const server = {
            id: name,
            name: name,
            type: 'stdio',
            scope: 'cursor',
            config: {},
            raw: config
          };
          
          // Determine transport type and extract config
          if (config.command) {
            server.type = 'stdio';
            server.config.command = config.command;
            server.config.args = config.args || [];
            server.config.env = config.env || {};
          } else if (config.url) {
            server.type = config.transport || 'http';
            server.config.url = config.url;
            server.config.headers = config.headers || {};
          }
          
          servers.push(server);
        }
      }
      
      res.json({
        success: true,
        servers: servers,
        path: mcpPath
      });
    } catch (error) {
      // MCP config doesn't exist
      // MCP config not found
      res.json({
        success: true,
        servers: [],
        isDefault: true
      });
    }
  } catch (error) {
    // MCP config read error
    res.status(500).json({ 
      error: 'Failed to read Cursor MCP configuration', 
      details: 'An error occurred'
    });
  }
});

// POST /api/cursor/mcp/add - Add MCP server to Cursor configuration
router.post('/mcp/add', async (req, res) => {
  try {
    const { name, type = 'stdio', command, args = [], url, headers = {}, env = {} } = req.body;

    // Build server config
    let serverConfig = {};
    if (type === 'stdio') { serverConfig = { command, args, env }; }
    else if (type === 'http' || type === 'sse') { serverConfig = { url, transport: type, headers }; }

    // Cloud mode: read/write on user's machine
    if (req.isCloud) {
      if (!req.requireRelay()) return;
      let mcpConfig = { mcpServers: {} };
      try {
        const result = await req.sendRelay('file-read', { filePath: '~/.cursor/mcp.json' }, 15000);
        mcpConfig = JSON.parse(result.content);
        if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
      } catch { /* new config */ }
      mcpConfig.mcpServers[name] = serverConfig;
      await req.sendRelay('create-folder', { folderPath: '~/.cursor' }, 10000);
      await req.sendRelay('file-write', { filePath: '~/.cursor/mcp.json', content: JSON.stringify(mcpConfig, null, 2) }, 15000);
      return res.json({ success: true, message: `MCP server "${name}" added to Cursor configuration`, config: mcpConfig });
    }

    // Local mode
    const mcpPath = path.join(os.homedir(), '.cursor', 'mcp.json');
    let mcpConfig = { mcpServers: {} };
    try {
      const existing = await fs.readFile(mcpPath, 'utf8');
      mcpConfig = JSON.parse(existing);
      if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
    } catch (error) { /* new config */ }
    mcpConfig.mcpServers[name] = serverConfig;
    await fs.mkdir(path.dirname(mcpPath), { recursive: true });
    await fs.writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2));
    res.json({ success: true, message: `MCP server "${name}" added to Cursor configuration`, config: mcpConfig });
  } catch (error) {
    res.status(500).json({ error: 'Failed to add MCP server', details: 'An error occurred' });
  }
});

// DELETE /api/cursor/mcp/:name - Remove MCP server from Cursor configuration
router.delete('/mcp/:name', async (req, res) => {
  try {
    const { name } = req.params;

    // Cloud mode
    if (req.isCloud) {
      if (!req.requireRelay()) return;
      let mcpConfig;
      try {
        const result = await req.sendRelay('file-read', { filePath: '~/.cursor/mcp.json' }, 15000);
        mcpConfig = JSON.parse(result.content);
      } catch {
        return res.status(404).json({ error: 'Cursor MCP configuration not found' });
      }
      if (!mcpConfig.mcpServers || !mcpConfig.mcpServers[name]) {
        return res.status(404).json({ error: `MCP server "${name}" not found in Cursor configuration` });
      }
      delete mcpConfig.mcpServers[name];
      await req.sendRelay('file-write', { filePath: '~/.cursor/mcp.json', content: JSON.stringify(mcpConfig, null, 2) }, 15000);
      return res.json({ success: true, message: `MCP server "${name}" removed from Cursor configuration`, config: mcpConfig });
    }

    // Local mode
    const mcpPath = path.join(os.homedir(), '.cursor', 'mcp.json');
    let mcpConfig;
    try {
      const existing = await fs.readFile(mcpPath, 'utf8');
      mcpConfig = JSON.parse(existing);
    } catch (error) {
      return res.status(404).json({ error: 'Cursor MCP configuration not found' });
    }
    if (!mcpConfig.mcpServers || !mcpConfig.mcpServers[name]) {
      return res.status(404).json({ error: `MCP server "${name}" not found in Cursor configuration` });
    }
    delete mcpConfig.mcpServers[name];
    await fs.writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2));
    res.json({ success: true, message: `MCP server "${name}" removed from Cursor configuration`, config: mcpConfig });
  } catch (error) {
    res.status(500).json({ error: 'Failed to remove MCP server', details: 'An error occurred' });
  }
});

// POST /api/cursor/mcp/add-json - Add MCP server using JSON format
router.post('/mcp/add-json', async (req, res) => {
  try {
    const { name, jsonConfig } = req.body;

    let parsedConfig;
    try {
      parsedConfig = typeof jsonConfig === 'string' ? JSON.parse(jsonConfig) : jsonConfig;
    } catch (parseError) {
      return res.status(400).json({ error: 'Invalid JSON configuration', details: parseError.message });
    }

    // Cloud mode
    if (req.isCloud) {
      if (!req.requireRelay()) return;
      let mcpConfig = { mcpServers: {} };
      try {
        const result = await req.sendRelay('file-read', { filePath: '~/.cursor/mcp.json' }, 15000);
        mcpConfig = JSON.parse(result.content);
        if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
      } catch { /* new config */ }
      mcpConfig.mcpServers[name] = parsedConfig;
      await req.sendRelay('create-folder', { folderPath: '~/.cursor' }, 10000);
      await req.sendRelay('file-write', { filePath: '~/.cursor/mcp.json', content: JSON.stringify(mcpConfig, null, 2) }, 15000);
      return res.json({ success: true, message: `MCP server "${name}" added to Cursor configuration`, config: mcpConfig });
    }

    // Local mode
    const mcpPath = path.join(os.homedir(), '.cursor', 'mcp.json');
    let mcpConfig = { mcpServers: {} };
    try {
      const existing = await fs.readFile(mcpPath, 'utf8');
      mcpConfig = JSON.parse(existing);
      if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
    } catch (error) { /* new config */ }
    mcpConfig.mcpServers[name] = parsedConfig;
    await fs.mkdir(path.dirname(mcpPath), { recursive: true });
    await fs.writeFile(mcpPath, JSON.stringify(mcpConfig, null, 2));
    
    res.json({
      success: true,
      message: `MCP server "${name}" added to Cursor configuration via JSON`,
      config: mcpConfig
    });
  } catch (error) {
    // MCP server JSON add error
    res.status(500).json({ 
      error: 'Failed to add MCP server', 
      details: 'An error occurred'
    });
  }
});

// GET /api/cursor/sessions - Get Cursor sessions from SQLite database
router.get('/sessions', async (req, res) => {
  try {
    const { projectPath } = req.query;
    
    // Calculate cwdID hash for the project path (Cursor uses MD5 hash)
    const cwdId = crypto.createHash('md5').update(projectPath || process.cwd()).digest('hex');
    const cursorChatsPath = path.join(os.homedir(), '.cursor', 'chats', cwdId);
    
    
    // Check if the directory exists
    try {
      await fs.access(cursorChatsPath);
    } catch (error) {
      // No sessions for this project
      return res.json({ 
        success: true, 
        sessions: [],
        cwdId: cwdId,
        path: cursorChatsPath
      });
    }
    
    // List all session directories
    const sessionDirs = await fs.readdir(cursorChatsPath);
    const sessions = [];
    
    for (const sessionId of sessionDirs) {
      const sessionPath = path.join(cursorChatsPath, sessionId);
      const storeDbPath = path.join(sessionPath, 'store.db');
      let dbStatMtimeMs = null;
      
      try {
        // Check if store.db exists
        await fs.access(storeDbPath);
        
        // Capture store.db mtime as a reliable fallback timestamp (last activity)
        try {
          const stat = await fs.stat(storeDbPath);
          dbStatMtimeMs = stat.mtimeMs;
        } catch (_) {}

        // Open SQLite database
        if (!sqliteOpen || !sqlite3) {
          continue; // Skip on Vercel where native modules aren't available
        }
        const db = await sqliteOpen({
          filename: storeDbPath,
          driver: sqlite3.Database,
          mode: sqlite3.OPEN_READONLY
        });
        
        // Get metadata from meta table
        const metaRows = await db.all(`
          SELECT key, value FROM meta
        `);
        
        let sessionData = {
          id: sessionId,
          name: 'Untitled Session',
          createdAt: null,
          mode: null,
          projectPath: projectPath,
          lastMessage: null,
          messageCount: 0
        };
        
        // Parse meta table entries
        for (const row of metaRows) {
          if (row.value) {
            try {
              // Try to decode as hex-encoded JSON
              const hexMatch = row.value.toString().match(/^[0-9a-fA-F]+$/);
              if (hexMatch) {
                const jsonStr = Buffer.from(row.value, 'hex').toString('utf8');
                const data = JSON.parse(jsonStr);
                
                if (row.key === 'agent') {
                  sessionData.name = data.name || sessionData.name;
                  // Normalize createdAt to ISO string in milliseconds
                  let createdAt = data.createdAt;
                  if (typeof createdAt === 'number') {
                    if (createdAt < 1e12) {
                      createdAt = createdAt * 1000; // seconds -> ms
                    }
                    sessionData.createdAt = new Date(createdAt).toISOString();
                  } else if (typeof createdAt === 'string') {
                    const n = Number(createdAt);
                    if (!Number.isNaN(n)) {
                      const ms = n < 1e12 ? n * 1000 : n;
                      sessionData.createdAt = new Date(ms).toISOString();
                    } else {
                      // Assume it's already an ISO/date string
                      const d = new Date(createdAt);
                      sessionData.createdAt = isNaN(d.getTime()) ? null : d.toISOString();
                    }
                  } else {
                    sessionData.createdAt = sessionData.createdAt || null;
                  }
                  sessionData.mode = data.mode;
                  sessionData.agentId = data.agentId;
                  sessionData.latestRootBlobId = data.latestRootBlobId;
                }
              } else {
                // If not hex, use raw value for simple keys
                if (row.key === 'name') {
                  sessionData.name = row.value.toString();
                }
              }
            } catch (e) {
              // meta parse error
            }
          }
        }
        
        // Get message count from JSON blobs only (actual messages, not DAG structure)
        try {
          const blobCount = await db.get(`
            SELECT COUNT(*) as count 
            FROM blobs 
            WHERE substr(data, 1, 1) = X'7B'
          `);
          sessionData.messageCount = blobCount.count;
          
          // Get the most recent JSON blob for preview (actual message, not DAG structure)
          const lastBlob = await db.get(`
            SELECT data FROM blobs 
            WHERE substr(data, 1, 1) = X'7B'
            ORDER BY rowid DESC 
            LIMIT 1
          `);
          
          if (lastBlob && lastBlob.data) {
            try {
              // Try to extract readable preview from blob (may contain binary with embedded JSON)
              const raw = lastBlob.data.toString('utf8');
              let preview = '';
              // Attempt direct JSON parse
              try {
                const parsed = JSON.parse(raw);
                if (parsed?.content) {
                  if (Array.isArray(parsed.content)) {
                    const firstText = parsed.content.find(p => p?.type === 'text' && p.text)?.text || '';
                    preview = firstText;
                  } else if (typeof parsed.content === 'string') {
                    preview = parsed.content;
                  }
                }
              } catch (_) {}
              if (!preview) {
                // Strip non-printable and try to find JSON chunk
                const cleaned = raw.replace(/[^\x09\x0A\x0D\x20-\x7E]/g, '');
                const s = cleaned;
                const start = s.indexOf('{');
                const end = s.lastIndexOf('}');
                if (start !== -1 && end > start) {
                  const jsonStr = s.slice(start, end + 1);
                  try {
                    const parsed = JSON.parse(jsonStr);
                    if (parsed?.content) {
                      if (Array.isArray(parsed.content)) {
                        const firstText = parsed.content.find(p => p?.type === 'text' && p.text)?.text || '';
                        preview = firstText;
                      } else if (typeof parsed.content === 'string') {
                        preview = parsed.content;
                      }
                    }
                  } catch (_) {
                    preview = s;
                  }
                } else {
                  preview = s;
                }
              }
              if (preview && preview.length > 0) {
                sessionData.lastMessage = preview.substring(0, 100) + (preview.length > 100 ? '...' : '');
              }
            } catch (e) {
              // blob parse error
            }
          }
        } catch (e) {
          // blobs read error
        }
        
        await db.close();

        // Finalize createdAt: use parsed meta value when valid, else fall back to store.db mtime
        if (!sessionData.createdAt) {
          if (dbStatMtimeMs && Number.isFinite(dbStatMtimeMs)) {
            sessionData.createdAt = new Date(dbStatMtimeMs).toISOString();
          }
        }
        
        sessions.push(sessionData);
        
      } catch (error) {
        // session read error
      }
    }
    
    // Fallback: ensure createdAt is a valid ISO string (use session directory mtime as last resort)
    for (const s of sessions) {
      if (!s.createdAt) {
        try {
          const sessionDir = path.join(cursorChatsPath, s.id);
          const st = await fs.stat(sessionDir);
          s.createdAt = new Date(st.mtimeMs).toISOString();
        } catch {
          s.createdAt = new Date().toISOString();
        }
      }
    }
    // Sort sessions by creation date (newest first)
    sessions.sort((a, b) => {
      if (!a.createdAt) return 1;
      if (!b.createdAt) return -1;
      return new Date(b.createdAt) - new Date(a.createdAt);
    });
    
    res.json({ 
      success: true, 
      sessions: sessions,
      cwdId: cwdId,
      path: cursorChatsPath
    });
    
  } catch (error) {
    // sessions read error
    res.status(500).json({ 
      error: 'Failed to read Cursor sessions', 
      details: 'An error occurred'
    });
  }
});

// GET /api/cursor/sessions/:sessionId - Get specific Cursor session from SQLite
router.get('/sessions/:sessionId', async (req, res) => {
  try {
    const { sessionId } = req.params;
    const { projectPath } = req.query;
    
    // Calculate cwdID hash for the project path
    const cwdId = crypto.createHash('md5').update(projectPath || process.cwd()).digest('hex');
    const storeDbPath = path.join(os.homedir(), '.cursor', 'chats', cwdId, sessionId, 'store.db');
    
    
    // Open SQLite database (requires native sqlite3 module)
    if (!sqliteOpen || !sqlite3) {
      return res.status(503).json({ error: 'SQLite not available on this deployment' });
    }
    const db = await sqliteOpen({
      filename: storeDbPath,
      driver: sqlite3.Database,
      mode: sqlite3.OPEN_READONLY
    });
    
    // Get all blobs to build the DAG structure
    const allBlobs = await db.all(`
      SELECT rowid, id, data FROM blobs
    `);
    
    // Build the DAG structure from parent-child relationships
    const blobMap = new Map(); // id -> blob data
    const parentRefs = new Map(); // blob id -> [parent blob ids]
    const childRefs = new Map(); // blob id -> [child blob ids]
    const jsonBlobs = []; // Clean JSON messages
    
    for (const blob of allBlobs) {
      blobMap.set(blob.id, blob);
      
      // Check if this is a JSON blob (actual message) or protobuf (DAG structure)
      if (blob.data && blob.data[0] === 0x7B) { // Starts with '{' - JSON blob
        try {
          const parsed = JSON.parse(blob.data.toString('utf8'));
          jsonBlobs.push({ ...blob, parsed });
        } catch (e) {
          // JSON blob parse failed
        }
      } else if (blob.data) { // Protobuf blob - extract parent references
        const parents = [];
        let i = 0;
        
        // Scan for parent references (0x0A 0x20 followed by 32-byte hash)
        while (i < blob.data.length - 33) {
          if (blob.data[i] === 0x0A && blob.data[i+1] === 0x20) {
            const parentHash = blob.data.slice(i+2, i+34).toString('hex');
            if (blobMap.has(parentHash)) {
              parents.push(parentHash);
            }
            i += 34;
          } else {
            i++;
          }
        }
        
        if (parents.length > 0) {
          parentRefs.set(blob.id, parents);
          // Update child references
          for (const parentId of parents) {
            if (!childRefs.has(parentId)) {
              childRefs.set(parentId, []);
            }
            childRefs.get(parentId).push(blob.id);
          }
        }
      }
    }
    
    // Perform topological sort to get chronological order
    const visited = new Set();
    const sorted = [];
    
    // DFS-based topological sort
    function visit(nodeId) {
      if (visited.has(nodeId)) return;
      visited.add(nodeId);
      
      // Visit all parents first (dependencies)
      const parents = parentRefs.get(nodeId) || [];
      for (const parentId of parents) {
        visit(parentId);
      }
      
      // Add this node after all its parents
      const blob = blobMap.get(nodeId);
      if (blob) {
        sorted.push(blob);
      }
    }
    
    // Start with nodes that have no parents (roots)
    for (const blob of allBlobs) {
      if (!parentRefs.has(blob.id)) {
        visit(blob.id);
      }
    }
    
    // Visit any remaining nodes (disconnected components)
    for (const blob of allBlobs) {
      visit(blob.id);
    }
    
    // Now extract JSON messages in the order they appear in the sorted DAG
    const messageOrder = new Map(); // JSON blob id -> order index
    let orderIndex = 0;
    
    for (const blob of sorted) {
      // Check if this blob references any JSON messages
      if (blob.data && blob.data[0] !== 0x7B) { // Protobuf blob
        // Look for JSON blob references
        for (const jsonBlob of jsonBlobs) {
          try {
            const jsonIdBytes = Buffer.from(jsonBlob.id, 'hex');
            if (blob.data.includes(jsonIdBytes)) {
              if (!messageOrder.has(jsonBlob.id)) {
                messageOrder.set(jsonBlob.id, orderIndex++);
              }
            }
          } catch (e) {
            // Skip if can't convert ID
          }
        }
      }
    }
    
    // Sort JSON blobs by their appearance order in the DAG
    const sortedJsonBlobs = jsonBlobs.sort((a, b) => {
      const orderA = messageOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER;
      const orderB = messageOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER;
      if (orderA !== orderB) return orderA - orderB;
      // Fallback to rowid if not in order map
      return a.rowid - b.rowid;
    });
    
    // Use sorted JSON blobs
    const blobs = sortedJsonBlobs.map((blob, idx) => ({
      ...blob,
      sequence_num: idx + 1,
      original_rowid: blob.rowid
    }));
    
    // Get metadata from meta table
    const metaRows = await db.all(`
      SELECT key, value FROM meta
    `);
    
    // Parse metadata
    let metadata = {};
    for (const row of metaRows) {
      if (row.value) {
        try {
          // Try to decode as hex-encoded JSON
          const hexMatch = row.value.toString().match(/^[0-9a-fA-F]+$/);
          if (hexMatch) {
            const jsonStr = Buffer.from(row.value, 'hex').toString('utf8');
            metadata[row.key] = JSON.parse(jsonStr);
          } else {
            metadata[row.key] = row.value.toString();
          }
        } catch (e) {
          metadata[row.key] = row.value.toString();
        }
      }
    }
    
    // Extract messages from sorted JSON blobs
    const messages = [];
    for (const blob of blobs) {
      try {
        // We already parsed JSON blobs earlier
        const parsed = blob.parsed;
        
        if (parsed) {
          // Filter out ONLY system messages at the server level
          // Check both direct role and nested message.role
          const role = parsed?.role || parsed?.message?.role;
          if (role === 'system') {
            continue; // Skip only system messages
          }
          messages.push({ 
            id: blob.id, 
            sequence: blob.sequence_num,
            rowid: blob.original_rowid, 
            content: parsed 
          });
        }
      } catch (e) {
        // Skip blobs that cause errors
        // blob skipped
      }
    }
    
    await db.close();
    
    res.json({ 
      success: true, 
      session: {
        id: sessionId,
        projectPath: projectPath,
        messages: messages,
        metadata: metadata,
        cwdId: cwdId
      }
    });
    
  } catch (error) {
    // session read error
    res.status(500).json({ 
      error: 'Failed to read Cursor session', 
      details: 'An error occurred'
    });
  }
});

export default router;