#!/usr/bin/env node
/**
 * Context Graph → Remote PostgreSQL Sync v3
 *
 * Incremental sync using structured API endpoints:
 *   PUT  /api/v2/cg/entity      — upsert entity
 *   PUT  /api/v2/cg/relation    — upsert relation
 *   PUT  /api/v2/cg/observation — add observation
 *   GET  /api/v2/cg/entities    — list/search
 *   GET  /api/v2/cg/entity/:id  — single entity + relations + observations
 *   GET  /api/v2/cg/graph       — BFS subgraph
 *
 * Usage:
 *   node src/memory/sync-context-graph.js          # push local → remote
 *   node src/memory/sync-context-graph.js --pull   # pull remote → local
 *   node src/memory/sync-context-graph.js --status # diff local vs remote
 */

const { ContextGraph } = require('./context-graph');
const http = require('http');

const REMOTE_URL = process.env.MEMORY_SERVER_URL || 'http://111.229.145.18';
const API_KEY = process.env.MEMORY_API_KEY || '';

function api(method, path, body) {
  return new Promise((resolve, reject) => {
    const url = new URL(REMOTE_URL + path);
    const headers = { 'Content-Type': 'application/json' };
    if (API_KEY) headers['x-api-key'] = API_KEY;
    const req = http.request(url, { method, headers, timeout: 10000 }, res => {
      let d = '';
      res.on('data', c => d += c);
      res.on('end', () => {
        try { resolve({ status: res.statusCode, body: JSON.parse(d) }); }
        catch { resolve({ status: res.statusCode, body: d }); }
      });
    });
    req.on('error', reject);
    req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
    if (body) req.write(JSON.stringify(body));
    req.end();
  });
}

async function pushToRemote(localGraph) {
  let entities = 0, relations = 0, observations = 0;

  for (const [id, entity] of localGraph.entities) {
    try {
      await api('PUT', '/api/v2/cg/entity?project=xiaolong', {
        id, type: entity.type, name: entity.name,
        path: entity.path, summary: entity.summary,
        pinned: entity.pinned, metadata: entity.metadata || {}
      });
      entities++;
    } catch (e) { console.error(`  Entity ${id}: ${e.message}`); }

    for (const obs of (entity.observations || [])) {
      try {
        await api('PUT', '/api/v2/cg/observation?project=xiaolong', {
          entity_id: id, type: obs.type, priority: obs.priority,
          pinned: obs.pinned, summary: obs.summary, details: obs.details || {}
        });
        observations++;
      } catch {}
    }
  }

  for (const rel of localGraph.relations) {
    try {
      await api('PUT', '/api/v2/cg/relation?project=xiaolong', {
        from: rel.from, to: rel.to, kind: rel.type, metadata: rel.metadata || {}
      });
      relations++;
    } catch {}
  }

  return { entities, relations, observations };
}

async function pullFromRemote() {
  const res = await api('GET', '/api/v2/cg/entities?project=xiaolong&limit=100');
  return res.body?.entities || [];
}

async function main() {
  const args = process.argv.slice(2);

  if (args.includes('--pull')) {
    const entities = await pullFromRemote();
    const cg = new ContextGraph();
    for (const e of entities) {
      cg.entity(e.id, { type: e.type, name: e.name, path: e.path, summary: e.summary, pinned: e.pinned });
    }
    cg.save();
    console.log(`Pulled ${entities.length} entities to local`);
    return;
  }

  if (args.includes('--status')) {
    const cg = new ContextGraph();
    console.log(`Local: ${cg.entities.size} entities, ${cg.relations.length} relations`);
    try {
      const remote = await pullFromRemote();
      console.log(`Remote: ${remote.length} entities`);
      console.log(`Drift: ${cg.entities.size - remote.length} ahead`);
    } catch (e) { console.log(`Remote: unreachable (${e.message})`); }
    return;
  }

  // Default: push
  const cg = new ContextGraph();
  const result = await pushToRemote(cg);
  console.log(`Synced: ${result.entities} entities, ${result.relations} relations, ${result.observations} observations`);
}

if (require.main === module) {
  main().catch(e => { console.error(e.message); process.exit(1); });
}

module.exports = { pushToRemote, pullFromRemote };
