'use strict';

const fs = require('fs');
const path = require('path');
const { createHash } = require('crypto');
const { execSync } = require('child_process');

let claudeDesktopSessions = null;
function getClaudeDesktopSessions() {
  if (claudeDesktopSessions) return claudeDesktopSessions;
  try {
    claudeDesktopSessions = require('../../claude-task-manager/lib/claude-desktop-sessions');
  } catch {
    return null;
  }
  return claudeDesktopSessions;
}

// --- Task type classification ---

function classifyTaskType(content) {
  if (!content) return 'chat';
  const lower = content.toLowerCase();
  // Coding signals
  if (/\b(function|class|const |let |var |import |require\(|def |async |await |return )\b/.test(content)) return 'coding';
  if (/\b(fix|bug|error|implement|refactor|test|deploy|build)\b/i.test(lower)) return 'coding';
  // Planning signals
  if (/\b(plan|design|architect\w*|roadmap|strategy|prioritize|brainstorm|spec\b|tradeoffs?|trade-offs?|discuss|proposal)\b/i.test(lower)) return 'planning';
  // QA signals
  if (/\b(what is|how do|explain|why does|describe|compare)\b/i.test(lower)) return 'qa';
  // Slack signals
  if (/\b(slack|reply|respond|message|dm|channel)\b/i.test(lower)) return 'slack-reply';
  return 'chat';
}

// --- Content hash for dedup ---

function contentHash(source, prompt) {
  return createHash('sha256').update(`${source}:${prompt}`).digest('hex').slice(0, 16);
}

// --- Claude Code Session Harvesting ---

async function harvestClaudeCodeSessions(since) {
  const claudeDir = path.join(process.env.HOME, '.claude', 'projects');
  if (!fs.existsSync(claudeDir)) return [];

  const samples = [];
  const jsonlFiles = findJsonlFiles(claudeDir);

  for (const file of jsonlFiles) {
    const stat = fs.statSync(file);
    if (since && stat.mtime < new Date(since)) continue;

    try {
      const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
      const messages = lines.map(line => {
        try { return JSON.parse(line); } catch { return null; }
      }).filter(Boolean);

      // Pair user->assistant turns
      for (let i = 0; i < messages.length - 1; i++) {
        const msg = messages[i];
        const next = messages[i + 1];

        if (msg.type !== 'human' && msg.role !== 'user') continue;
        if (next.type !== 'assistant' && next.role !== 'assistant') continue;

        const userContent = extractContent(msg);
        const assistantContent = extractContent(next);
        if (!userContent || userContent.length < 20) continue;
        if (!assistantContent || assistantContent.length < 20) continue;

        // Extract tool calls from assistant message
        const toolCalls = extractToolCalls(next);

        const id = contentHash('claude-code', userContent);
        samples.push({
          id,
          source: 'claude-code',
          session_id: path.basename(file, '.jsonl'),
          timestamp: msg.timestamp || stat.mtime.toISOString(),
          task_type: classifyTaskType(userContent),
          prompt: userContent,
          response: assistantContent,
          tool_calls: toolCalls,
          outcome: 'unknown',
          outcome_signal: { git_committed: false, git_diff: null, task_status: null, user_corrected: false },
          model: next.model || 'unknown',
          quality_label: 0.5,
        });
      }
    } catch (err) {
      console.error(`[harvester] Error parsing ${file}:`, err.message);
    }
  }
  return samples;
}

// --- Claude Desktop Session Harvesting ---

async function harvestClaudeDesktopSessions(since) {
  const reader = getClaudeDesktopSessions();
  if (!reader) return [];

  const sessions = reader.listSessions();
  const samples = [];

  for (const session of sessions) {
    if (since && session.updatedAt && session.updatedAt <= since) continue;
    const messages = Array.isArray(session.messages) ? session.messages : [];
    for (let i = 0; i < messages.length - 1; i++) {
      const userMsg = messages[i];
      const assistantMsg = messages[i + 1];
      if (userMsg.role !== 'user' || assistantMsg.role !== 'assistant') continue;
      const userContent = userMsg.text || '';
      const assistantContent = assistantMsg.text || '';
      if (!userContent || userContent.length < 20) continue;
      if (!assistantContent || assistantContent.length < 20) continue;

      samples.push({
        id: contentHash('claude-desktop', `${session.uuid}:${i}:${userContent}`),
        source: 'claude-desktop',
        session_id: session.uuid,
        timestamp: userMsg.timestamp || session.updatedAt || session.createdAt || new Date().toISOString(),
        task_type: classifyTaskType(userContent),
        prompt: userContent,
        response: assistantContent,
        tool_calls: [],
        outcome: 'unknown',
        outcome_signal: { git_committed: false, git_diff: null, task_status: null, user_corrected: false },
        model: session.model || 'unknown',
        quality_label: 0.5,
      });
    }
  }
  return samples;
}

// --- Codex Session Harvesting ---

async function harvestCodexSessions(since) {
  const codexDir = path.join(process.env.HOME, '.codex', 'sessions');
  if (!fs.existsSync(codexDir)) return [];

  const samples = [];
  const jsonlFiles = findJsonlFiles(codexDir);

  for (const file of jsonlFiles) {
    const stat = fs.statSync(file);
    if (since && stat.mtime < new Date(since)) continue;

    try {
      const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
      const messages = lines.map(line => {
        try { return JSON.parse(line); } catch { return null; }
      }).filter(Boolean);

      // Codex format: response_item with role
      const userMsgs = messages.filter(m => m.role === 'user' || (m.type === 'response_item' && m.item?.role === 'user'));
      const assistantMsgs = messages.filter(m => m.role === 'assistant' || (m.type === 'response_item' && m.item?.role === 'assistant'));

      // Pair them up sequentially
      const pairs = Math.min(userMsgs.length, assistantMsgs.length);
      for (let i = 0; i < pairs; i++) {
        const userContent = extractContent(userMsgs[i]);
        const assistantContent = extractContent(assistantMsgs[i]);
        if (!userContent || userContent.length < 20) continue;
        if (!assistantContent || assistantContent.length < 20) continue;

        const toolCalls = extractToolCalls(assistantMsgs[i]);
        const id = contentHash('codex', userContent);

        samples.push({
          id,
          source: 'codex',
          session_id: path.basename(file, '.jsonl'),
          timestamp: userMsgs[i].timestamp || stat.mtime.toISOString(),
          task_type: classifyTaskType(userContent),
          prompt: userContent,
          response: assistantContent,
          tool_calls: toolCalls,
          outcome: 'unknown',
          outcome_signal: { git_committed: false, git_diff: null, task_status: null, user_corrected: false },
          model: assistantMsgs[i].model || 'unknown',
          quality_label: 0.5,
        });
      }
    } catch (err) {
      console.error(`[harvester] Error parsing ${file}:`, err.message);
    }
  }
  return samples;
}

// --- CTM Session Harvesting ---

async function harvestCtmSessions(since, dataDirOverride = null) {
  try {
    const {
      listPromptExecutions,
      pairPromptExecutions,
    } = require('../memory/ctm-prompt-executions-client');
    const executions = await listPromptExecutions({
      since,
      limit: 5000,
      order: 'desc',
      timeoutMs: 1500,
    });
    const rows = pairPromptExecutions(executions, { limit: 500 });

    return rows.map(row => ({
      id: contentHash('ctm', row.prompt),
      source: 'ctm-session',
      session_id: row.session_id,
      timestamp: row.executed_at,
      task_type: classifyTaskType(row.prompt),
      prompt: row.prompt,
      response: row.response,
      tool_calls: (() => { try { return JSON.parse(row.tool_uses || '[]'); } catch { return []; } })(),
      outcome: 'unknown',
      outcome_signal: { git_committed: false, git_diff: null, task_status: null, user_corrected: false },
      model: 'claude',
      quality_label: 0.7,
    }));
  } catch (err) {
    console.error('[harvester] Error reading CTM sessions through API:', err.message);
    return [];
  }
}

// --- Wall-E Chat Harvesting ---

async function harvestWalleChat(brain, since) {
  const messages = brain.listChatMessages ? brain.listChatMessages() : [];
  const samples = [];

  // Group by session
  const sessions = new Map();
  for (const msg of messages) {
    if (since && msg.created_at && msg.created_at < since) continue;
    const sid = msg.session_id || '__default__';
    if (!sessions.has(sid)) sessions.set(sid, []);
    sessions.get(sid).push(msg);
  }

  for (const [sid, msgs] of sessions) {
    for (let i = 0; i < msgs.length - 1; i++) {
      const userMsg = msgs[i];
      const assistantMsg = msgs[i + 1];
      if (userMsg.role !== 'user' || assistantMsg.role !== 'assistant') continue;
      if (!userMsg.content || userMsg.content.length < 20) continue;
      if (!assistantMsg.content || assistantMsg.content.length < 20) continue;
      // Skip tool-use-only messages
      if (isToolUseOnly(assistantMsg.content)) continue;

      const id = contentHash('walle-chat', userMsg.content);
      samples.push({
        id,
        source: 'walle-chat',
        session_id: sid,
        timestamp: userMsg.created_at || new Date().toISOString(),
        task_type: classifyTaskType(userMsg.content),
        prompt: userMsg.content,
        response: assistantMsg.content,
        tool_calls: [],
        outcome: 'unknown',
        outcome_signal: { git_committed: false, git_diff: null, task_status: null, user_corrected: false },
        model: assistantMsg.model_id || 'unknown',
        quality_label: 0.5,
      });
    }
  }
  return samples;
}

// --- Wall-E Task Harvesting ---

async function harvestWalleTasks(brain, since) {
  const tasks = brain.listTasks ? brain.listTasks({ status: 'completed' }) : [];
  const samples = [];

  for (const task of tasks) {
    if (!task.result || !task.description) continue;
    if (task.description.length < 20) continue;
    if (since && task.created_at && task.created_at < since) continue;

    const id = contentHash('walle-task', task.description);
    const isSlack = task.source === 'slack';
    const quality = isSlack ? 0.8 : 0.7;

    samples.push({
      id,
      source: 'walle-task',
      session_id: task.id,
      timestamp: task.created_at || new Date().toISOString(),
      task_type: isSlack ? 'slack-reply' : classifyTaskType(task.description),
      prompt: task.description,
      response: task.result,
      tool_calls: [],
      outcome: 'completed',
      outcome_signal: { git_committed: false, git_diff: null, task_status: task.status, user_corrected: false },
      model: 'unknown',
      quality_label: quality,
    });
  }
  return samples;
}

// --- Git Correlation ---

function findCommitsInWindow(cwd, startTime, endTime) {
  try {
    // Validate ISO timestamps to prevent command injection
    if (!/^\d{4}-\d{2}-\d{2}T[\d:.Z+-]+$/.test(startTime)) return [];
    if (!/^\d{4}-\d{2}-\d{2}T[\d:.Z+-]+$/.test(endTime)) return [];
    const result = execSync(
      `git log --after="${startTime}" --before="${endTime}" --format="%H|%s|%aI" 2>/dev/null`,
      { cwd, encoding: 'utf8', timeout: 10000 }
    ).trim();
    if (!result) return [];
    return result.split('\n').map(line => {
      const [hash, subject, date] = line.split('|');
      return { hash, subject, date };
    });
  } catch {
    return [];
  }
}

function getCommitDiff(cwd, hash) {
  try {
    // Validate git hash to prevent command injection
    if (!/^[0-9a-f]{7,40}$/i.test(hash)) return null;
    const result = execSync(
      `git show --stat --format="" ${hash} 2>/dev/null`,
      { cwd, encoding: 'utf8', timeout: 10000 }
    ).trim();
    return result;
  } catch {
    return null;
  }
}

async function correlateGitOutcomes(samples, cwd) {
  if (!cwd || !fs.existsSync(path.join(cwd, '.git'))) return samples;

  for (const sample of samples) {
    if (sample.source !== 'claude-code' && sample.source !== 'codex') continue;

    const ts = new Date(sample.timestamp);
    const startTime = ts.toISOString();
    const endTime = new Date(ts.getTime() + 3600000).toISOString(); // +1 hour window

    const commits = findCommitsInWindow(cwd, startTime, endTime);
    if (commits.length > 0) {
      sample.outcome = 'committed';
      sample.outcome_signal.git_committed = true;
      sample.outcome_signal.git_diff = getCommitDiff(cwd, commits[0].hash);
      sample.quality_label = 1.0;
    }
  }
  return samples;
}

// --- Helpers ---

function findJsonlFiles(dir) {
  const results = [];
  try {
    const entries = fs.readdirSync(dir, { withFileTypes: true, recursive: true });
    for (const entry of entries) {
      if (entry.isFile() && entry.name.endsWith('.jsonl')) {
        // node 20+ returns parentPath, older returns path property
        const parent = entry.parentPath || entry.path || dir;
        results.push(path.join(parent, entry.name));
      }
    }
  } catch {}
  return results;
}

function extractContent(msg) {
  if (typeof msg.content === 'string') return msg.content;
  if (Array.isArray(msg.content)) {
    return msg.content
      .filter(b => b.type === 'text')
      .map(b => b.text)
      .join('\n');
  }
  if (msg.message?.content) return extractContent(msg.message);
  if (msg.item?.content) {
    if (Array.isArray(msg.item.content)) {
      return msg.item.content
        .filter(b => b.type === 'input_text' || b.type === 'output_text' || b.type === 'text')
        .map(b => b.text)
        .join('\n');
    }
  }
  return '';
}

function extractToolCalls(msg) {
  if (Array.isArray(msg.content)) {
    return msg.content
      .filter(b => b.type === 'tool_use')
      .map(b => ({ name: b.name, input: b.input }));
  }
  return [];
}

function isToolUseOnly(content) {
  if (typeof content !== 'string') return false;
  try {
    const parsed = JSON.parse(content);
    if (Array.isArray(parsed)) return parsed.every(b => b.type === 'tool_use');
  } catch {}
  return false;
}

// --- Main Orchestrator ---

async function runHarvest({ incremental = true, brain, dataDir } = {}) {
  if (!brain) throw new Error('brain is required');
  if (!dataDir) {
    dataDir = process.env.WALL_E_DATA_DIR || path.join(process.env.HOME, '.walle', 'data');
  }

  const outputDir = path.join(dataDir, 'training', 'harvested');
  if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });

  const allSamples = [];
  const stats = { total: 0, bySource: {}, byTaskType: {} };

  // Determine "since" for incremental harvesting
  const getSince = (source) => {
    if (!incremental) return null;
    const state = brain.getHarvestState(source);
    return state?.last_processed_at || null;
  };

  // Harvest from each source
  const claudeSamples = await harvestClaudeCodeSessions(getSince('claude-code'));
  const claudeDesktopSamples = await harvestClaudeDesktopSessions(getSince('claude-desktop'));
  const codexSamples = await harvestCodexSessions(getSince('codex'));
  const chatSamples = await harvestWalleChat(brain, getSince('walle-chat'));
  const taskSamples = await harvestWalleTasks(brain, getSince('walle-task'));
  const ctmSamples = await harvestCtmSessions(getSince('ctm-sessions'), dataDir);

  allSamples.push(...claudeSamples, ...claudeDesktopSamples, ...codexSamples, ...chatSamples, ...taskSamples, ...ctmSamples);

  // Deduplicate by content hash
  const seen = new Set();
  const dedupedSamples = allSamples.filter(s => {
    if (seen.has(s.id)) return false;
    seen.add(s.id);
    return true;
  });

  // Build stats
  for (const s of dedupedSamples) {
    stats.bySource[s.source] = (stats.bySource[s.source] || 0) + 1;
    stats.byTaskType[s.task_type] = (stats.byTaskType[s.task_type] || 0) + 1;
  }
  stats.total = dedupedSamples.length;

  // Write to output file
  if (dedupedSamples.length > 0) {
    const today = new Date().toISOString().slice(0, 10);
    const outputPath = path.join(outputDir, `${today}.jsonl`);
    const jsonl = dedupedSamples.map(s => JSON.stringify(s)).join('\n') + '\n';
    fs.appendFileSync(outputPath, jsonl);
  }

  // Update harvest state for each source
  const now = new Date().toISOString();
  if (claudeSamples.length > 0) {
    brain.updateHarvestState('claude-code', { lastProcessedAt: now, totalHarvested: claudeSamples.length });
  }
  if (claudeDesktopSamples.length > 0) {
    brain.updateHarvestState('claude-desktop', { lastProcessedAt: now, totalHarvested: claudeDesktopSamples.length });
  }
  if (codexSamples.length > 0) {
    brain.updateHarvestState('codex', { lastProcessedAt: now, totalHarvested: codexSamples.length });
  }
  if (chatSamples.length > 0) {
    brain.updateHarvestState('walle-chat', { lastProcessedAt: now, totalHarvested: chatSamples.length });
  }
  if (taskSamples.length > 0) {
    brain.updateHarvestState('walle-task', { lastProcessedAt: now, totalHarvested: taskSamples.length });
  }
  if (ctmSamples.length > 0) {
    brain.updateHarvestState('ctm-sessions', { lastProcessedAt: now, totalHarvested: ctmSamples.length });
  }

  return { samples: dedupedSamples, stats };
}

module.exports = {
  classifyTaskType,
  contentHash,
  harvestClaudeCodeSessions,
  harvestClaudeDesktopSessions,
  harvestCodexSessions,
  harvestCtmSessions,
  harvestWalleChat,
  harvestWalleTasks,
  correlateGitOutcomes,
  findCommitsInWindow,
  getCommitDiff,
  runHarvest,
  // Helpers (exported for testing)
  extractContent,
  extractToolCalls,
  isToolUseOnly,
  findJsonlFiles,
};
