import { execFile } from 'child_process';
import { readFile, writeFile } from 'fs/promises';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { spawnInteractiveLLM } from '../llm.mjs';


import { getConfig } from '../config.mjs';
import { getWorkspaceRoot } from '../workspace.mjs';

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');

function getActiveWorkspace() {
  try { return getWorkspaceRoot(); } catch { return join(ROOT, 'workspaces', 'default'); }
}
const META_FLAG = join(ROOT, 'workspaces', '.meta-mode');

export const name = 'meta-develop';
export const description = 'Self-modify the agent codebase with git safety. Activates meta mode to edit source files.';

function run(cmd, opts = {}) {
  return new Promise((resolve, reject) => {
    execFile('/bin/bash', ['-c', cmd], {
      cwd: ROOT,
      timeout: 30_000,
      maxBuffer: 1024 * 1024,
      ...opts
    }, (err, stdout, stderr) => {
      if (err) reject(new Error(`${cmd}: ${err.message}\n${stderr}`));
      else resolve(stdout.trim());
    });
  });
}

async function gitCommit(message) {
  try {
    await run('git add -A');
    const status = await run('git status --porcelain');
    if (status) {
      await run(`git commit -m "${message.replace(/"/g, '\\"')}"`);
      return true;
    }
    return false;
  } catch (e) {
    console.error(`[meta-develop] git commit failed: ${e.message}`);
    return false;
  }
}

async function enableMetaMode() {
  await writeFile(META_FLAG, 'enabled\n', 'utf-8');
}

async function disableMetaMode() {
  await writeFile(META_FLAG, 'disabled\n', 'utf-8');
}

export async function execute(ctx) {
  const task = ctx.args?.task || ctx.args?.topic;
  if (!task) {
    return 'meta-develop requires a task description. Tell me what to change about the agent.';
  }

  const dbName = 'agent.db';

  ctx.addEvent('system', 'agent', `Meta-develop starting: ${task}`, { skill: 'meta-develop' });

  // Step 1: Safety commit before changes
  const committed = await gitCommit(`[pre-meta] checkpoint before: ${task.slice(0, 60)}`);
  if (committed) {
    ctx.addEvent('system', 'agent', 'Created safety checkpoint commit');
  }

  // Step 2: Get current git hash for rollback reference
  const preHash = await run('git rev-parse --short HEAD');

  // Step 3: Enable meta mode
  await enableMetaMode();
  ctx.addEvent('system', 'agent', 'Meta mode enabled — sandbox relaxed for source files');

  try {
    // Step 4: Build the prompt with full codebase context
    let fileMap = '';
    const sourceFiles = [
      'agent.mjs', 'server.mjs', 'db.mjs', 'llm.mjs', 'config.mjs',
      'memory.mjs', 'skills.mjs', 'inspiration.mjs', 'json-utils.mjs',
      'personality.md', 'package.json', 'config.json'
    ];
    for (const f of sourceFiles) {
      try {
        const content = await readFile(join(ROOT, f), 'utf-8');
        fileMap += `--- ${f} ---\n${content}\n\n`;
      } catch {}
    }

    const { readdirSync } = await import('fs');

    // Read adapters
    try {
      const adapterFiles = readdirSync(join(ROOT, 'adapters')).filter(f => f.endsWith('.mjs'));
      for (const f of adapterFiles) {
        try {
          const content = await readFile(join(ROOT, 'adapters', f), 'utf-8');
          fileMap += `--- adapters/${f} ---\n${content}\n\n`;
        } catch {}
      }
    } catch {}

    // Read skills
    const skillFiles = readdirSync(join(ROOT, 'skills')).filter(f => f.endsWith('.mjs'));
    for (const f of skillFiles) {
      try {
        const content = await readFile(join(ROOT, 'skills', f), 'utf-8');
        fileMap += `--- skills/${f} ---\n${content}\n\n`;
      } catch {}
    }

    // Read public/index.html
    try {
      const html = await readFile(join(ROOT, 'public', 'index.html'), 'utf-8');
      fileMap += `--- public/index.html ---\n${html}\n\n`;
    } catch {}

    const prompt = `You are working on improving an autonomous AI agent's codebase. You are running inside the agent's workspace directory, but meta mode is enabled — you can read and edit the agent's source files.

## The Agent's Source Code
${fileMap}

## Your Task
${task}

## Rules
1. Read the relevant source files first to understand the current code.
2. Make targeted, minimal changes. Don't rewrite entire files unless necessary.
3. Test your changes mentally — will the agent still boot? Will the loop still work?
4. The agent root is at: ${ROOT}
5. You can edit files like: ${ROOT}/agent.mjs, ${ROOT}/server.mjs, ${ROOT}/skills/*.mjs, ${ROOT}/public/index.html, etc.
6. You CANNOT modify: ${dbName}, .heartbeat, node_modules/, .git/
7. After making changes, run: git diff to verify your changes look correct.
8. Keep the existing code style (ESM imports, async/await, minimal dependencies).

## Important
- The agent is currently running. Your changes will take effect on the next restart.
- If you break something, git revert can undo it. The pre-change commit hash is: ${preHash}
- Be thoughtful. This is recursive self-improvement — make changes that genuinely improve the agent.

Do the task now. Read files, make edits, verify with git diff.`;

    // Step 5: Run LLM in interactive mode with tool access
    const result = await new Promise((resolve, reject) => {
      const proc = spawnInteractiveLLM({
        cwd: getActiveWorkspace(),
        model: ctx.pickModel('self-improve')
      });

      let fullResult = '';
      let buffer = '';
      let killed = false;
      let stderr = '';

      const timer = setTimeout(() => {
        killed = true;
        proc.kill('SIGTERM');
        setTimeout(() => proc.kill('SIGKILL'), 5000);
      }, 1_800_000); // 30 min timeout for self-improvement

      proc.stdout.on('data', (chunk) => {
        buffer += chunk.toString();
        const lines = buffer.split('\n');
        buffer = lines.pop();
        for (const line of lines) {
          if (!line.trim()) continue;
          try {
            const event = JSON.parse(line);
            if (event.type === 'result') {
              fullResult = event.result || '';
            }
          } catch {}
        }
      });

      proc.stderr.on('data', (chunk) => { stderr += chunk.toString(); });

      proc.on('close', (code) => {
        clearTimeout(timer);
        if (killed) reject(new Error('Meta-develop timed out (30 min limit)'));
        else if (code !== 0 && !fullResult) reject(new Error(`Meta-develop error: exit ${code}\n${stderr}`));
        else resolve(fullResult.trim());
      });

      proc.on('error', (err) => {
        clearTimeout(timer);
        reject(new Error(`Meta-develop spawn error: ${err.message}`));
      });

      // Send the prompt via stdin and close to signal completion
      proc.stdin.write(prompt);
      proc.stdin.end();
    });

    // Step 6: Commit the changes
    const diff = await run('git diff --stat').catch(() => '');
    const postCommitted = await gitCommit(`[meta] ${task.slice(0, 60)}`);

    if (postCommitted) {
      const newHash = await run('git rev-parse --short HEAD');
      ctx.addEvent('system', 'agent', `Meta-develop complete. Changes committed: ${newHash}\n\nDiff:\n\`\`\`\n${diff}\n\`\`\``, {
        skill: 'meta-develop',
        pre_hash: preHash,
        post_hash: newHash
      });
    } else {
      ctx.addEvent('system', 'agent', 'Meta-develop complete but no files were changed.');
    }

    return result;
  } catch (err) {
    ctx.addEvent('error', 'system', `Meta-develop failed: ${err.message}. Rollback to ${preHash} with: git revert HEAD`);
    throw err;
  } finally {
    // Step 7: Always disable meta mode when done
    await disableMetaMode();
    ctx.addEvent('system', 'agent', 'Meta mode disabled — sandbox restored');
  }
}

export default { name, description, execute };
