#!/usr/bin/env node
'use strict';
/**
 * glean-team-sync — reads team structure from Glean people directory
 * and stores each person's profile in WALL-E's brain.
 *
 * Uses the Glean MCP `employee_search` tool to find people, then
 * recursively traverses the org tree.
 *
 * Usage: node run.js [--owner "Name"] [--depth N]
 */
const path = require('path');

const SKILL_DIR = __dirname;
const PAUSE_MS = 600;

// Parse CLI args
const args = process.argv.slice(2);
let ownerName = process.env.WALLE_OWNER_NAME || '';
let maxDepth = 2;
for (let i = 0; i < args.length; i++) {
  if (args[i] === '--owner' && args[i + 1]) ownerName = args[i + 1];
  if (args[i] === '--depth' && args[i + 1]) maxDepth = parseInt(args[i + 1], 10);
}

// Task-level config overrides
try {
  const envConfig = process.env.WALL_E_SKILL_CONFIG
    ? JSON.parse(process.env.WALL_E_SKILL_CONFIG) : {};
  if (envConfig.owner_name) ownerName = envConfig.owner_name;
  if (envConfig.depth != null) maxDepth = envConfig.depth;
} catch { /* ignore */ }

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// ── Glean MCP ───────────────────────────────────────────────────────

let mcpClient;
let gleanServerName;

async function initGlean() {
  mcpClient = require(path.resolve(SKILL_DIR, '..', '..', '..', 'skills', 'mcp-client'));
  const configs = mcpClient.loadMcpConfigs();
  gleanServerName = Object.keys(configs).find(k => k.includes('glean'));
  if (!gleanServerName) throw new Error('Glean MCP not configured');
}

async function searchPerson(query) {
  const result = await mcpClient.callMcpTool(gleanServerName, 'employee_search', { query });
  // Extract raw text from MCP response
  let raw;
  if (result && result.content) {
    raw = result.content.map(c => c.text || JSON.stringify(c)).join('\n');
  } else {
    raw = JSON.stringify(result);
  }

  // Parse JSON response — Glean returns { entities: [{ person: {...} }] }
  try {
    const data = JSON.parse(raw);
    if (data.entities && data.entities.length > 0) {
      return data.entities.map(e => e.person).filter(Boolean);
    }
  } catch { /* not JSON, try text parsing below */ }

  // Fallback: already an array of person objects
  if (Array.isArray(raw)) return raw;

  return [];
}

// ── Build readable profile ──────────────────────────────────────────

function buildProfile(person, relationship) {
  const parts = [person.name];
  if (person.title) parts.push(`Title: ${person.title}`);
  if (person.department) parts.push(`Department: ${person.department}`);
  if (person.type) parts.push(`Type: ${person.type}`);
  if (person.location) parts.push(`Location: ${person.location}`);
  if (person.email) parts.push(`Email: ${person.email}`);
  if (person.startDate) parts.push(`Start Date: ${person.startDate}`);
  if (person.pronoun && person.pronoun !== 'None') parts.push(`Pronouns: ${person.pronoun}`);
  if (person.manager) {
    const mgr = typeof person.manager === 'object' ? person.manager.name : person.manager;
    parts.push(`Manager: ${mgr}`);
  }
  if (person.directReportsCount) parts.push(`Direct Reports: ${person.directReportsCount}`);
  if (person.totalReportsCount) parts.push(`Total Reports: ${person.totalReportsCount}`);
  if (person.directReports && person.directReports.length > 0) {
    const names = person.directReports.map(r => typeof r === 'object' ? r.name : r);
    parts.push(`Direct Report Names: ${names.join(', ')}`);
  }
  if (person.teams && person.teams.length > 0) {
    parts.push(`Teams: ${person.teams.join(', ')}`);
  }
  if (person.datasourceToProfileLink) {
    const links = person.datasourceToProfileLink;
    if (links.SLACK) parts.push(`Slack: ${links.SLACK}`);
  }
  if (relationship) parts.push(`Relationship: ${relationship}`);
  return parts.join('\n');
}

// ── Brain storage ───────────────────────────────────────────────────

function storePerson(brain, person, relationship) {
  const email = person.email || `unknown-${person.name.replace(/\s+/g, '-').toLowerCase()}`;
  const sourceId = `glean:person:${email}`;
  const content = buildProfile(person, relationship);

  // Strip circular/large fields from metadata
  const meta = { ...person, relationship };
  // Flatten manager to just name+email
  if (meta.manager && typeof meta.manager === 'object') {
    meta.managerName = meta.manager.name;
    meta.managerEmail = meta.manager.email;
    delete meta.manager;
  }
  // Flatten direct reports to names
  if (meta.directReports) {
    meta.directReportNames = meta.directReports.map(r => typeof r === 'object' ? r.name : r);
    delete meta.directReports;
  }
  const metadata = JSON.stringify(meta);

  // Check if exists and needs update
  const existing = brain.getDb().prepare(
    'SELECT id, content FROM memories WHERE source = ? AND source_id = ?'
  ).get('glean', sourceId);

  if (existing) {
    if (existing.content !== content) {
      brain.getDb().prepare(
        'UPDATE memories SET content = ?, metadata = ?, timestamp = ? WHERE id = ?'
      ).run(content, metadata, new Date().toISOString(), existing.id);
      return 'updated';
    }
    return 'skipped';
  }

  const result = brain.insertMemory({
    source: 'glean',
    source_id: sourceId,
    source_channel: person.department || 'unknown',
    memory_type: 'team_member',
    subject: person.name,
    content,
    metadata,
    importance: 0.5,
    timestamp: new Date().toISOString(),
  });
  return result ? 'inserted' : 'skipped';
}

// ── Recursive crawl ─────────────────────────────────────────────────

async function main() {
  await initGlean();

  const brainPath = path.resolve(SKILL_DIR, '..', '..', '..', 'brain.js');
  const brain = require(brainPath);
  brain.initDb();

  // Resolve owner name from brain if not set via env/args
  if (!ownerName) {
    ownerName = brain.getOwnerName() || '';
  }
  if (!ownerName) {
    throw new Error('[glean-team-sync] No owner name configured (set WALLE_OWNER_NAME or --owner)');
  }

  const stats = { synced: 0, updated: 0, skipped: 0, errors: 0, people: [] };
  const visited = new Set(); // by email

  async function crawlPerson(name, relationship, depth) {
    console.error(`[glean-team-sync] Searching: ${name} (${relationship}, depth=${depth})`);

    try {
      const people = await searchPerson(name);
      await sleep(PAUSE_MS);

      if (people.length === 0) {
        console.error(`[glean-team-sync] No results for: ${name}`);
        stats.errors++;
        return;
      }

      // Find best match
      const normalized = name.trim().toLowerCase();
      const person = people.find(p => p.name && p.name.toLowerCase() === normalized)
        || people.find(p => p.name && p.name.toLowerCase().includes(normalized))
        || people[0];

      if (!person || !person.name) {
        console.error(`[glean-team-sync] No valid profile for: ${name}`);
        stats.errors++;
        return;
      }

      const email = (person.email || '').toLowerCase();
      if (visited.has(email || person.name.toLowerCase())) return;
      visited.add(email || person.name.toLowerCase());

      // Store
      const result = storePerson(brain, person, relationship);
      if (result === 'inserted') stats.synced++;
      else if (result === 'updated') stats.updated++;
      else stats.skipped++;
      stats.people.push(person.name);

      console.error(`[glean-team-sync] ${result}: ${person.name} — ${person.title || 'no title'}`);

      // Recurse into org tree
      if (depth < maxDepth) {
        // Manager
        if (person.manager) {
          const mgrName = typeof person.manager === 'object' ? person.manager.name : person.manager;
          if (mgrName) {
            await crawlPerson(mgrName, `manager of ${person.name}`, depth + 1);
          }
        }

        // Direct reports
        if (person.directReports && person.directReports.length > 0) {
          for (const report of person.directReports) {
            const reportName = typeof report === 'object' ? report.name : report;
            if (reportName) {
              await crawlPerson(reportName, `reports to ${person.name}`, depth + 1);
            }
          }
        }
      }
    } catch (err) {
      console.error(`[glean-team-sync] Error searching ${name}: ${err.message}`);
      stats.errors++;
    }
  }

  await crawlPerson(ownerName, 'self (owner)', 0);

  brain.closeDb();
  mcpClient.disconnectAll();

  console.log(JSON.stringify(stats));
  console.error(`[glean-team-sync] Done: ${stats.synced} new, ${stats.updated} updated, ${stats.skipped} unchanged, ${stats.errors} errors (${stats.people.length} people)`);
}

// Export for skill-planner; also run directly when invoked as CLI script
module.exports = { run: main };

if (require.main === module) {
  main().catch(err => {
    console.error(`[glean-team-sync] Fatal: ${err.message}`);
    process.exit(1);
  });
}
