const fs = require('fs');
const path = require('path');
const http = require('http');
const https = require('https');
const GraphStore = require('./graph.js');
const { semanticDiff, formatSummary } = require('./semantic-diff.js');

/**
 * Developer Intelligence Pipeline v2 - Phase 6: LLM Doc Generation
 * Uses semantic diff context to generate targeted, intelligent documentation.
 * Supports Ollama and OpenAI-compatible APIs.
 */

const LLM_URL = process.env.LLM_URL || 'http://192.168.86.172:11434';
const LLM_MODEL = process.env.LLM_MODEL || 'qwen2.5:7b';
const LLM_BACKEND = process.env.LLM_BACKEND || 'ollama'; // 'ollama' or 'openai'

/**
 * Call LLM API (Ollama or OpenAI-compatible).
 */
function callLLM(prompt, maxTokens = 1024) {
  return new Promise((resolve, reject) => {
    let url, body, headers;

    if (LLM_BACKEND === 'ollama') {
      url = new URL('/api/generate', LLM_URL);
      body = JSON.stringify({
        model: LLM_MODEL,
        prompt,
        stream: false,
        options: { num_predict: maxTokens, temperature: 0.3 },
      });
      headers = { 'Content-Type': 'application/json' };
    } else {
      url = new URL('/v1/chat/completions', LLM_URL);
      body = JSON.stringify({
        model: LLM_MODEL,
        messages: [{ role: 'user', content: prompt }],
        max_tokens: maxTokens,
        temperature: 0.3,
      });
      headers = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.OPENAI_API_KEY || 'not-needed'}`,
      };
    }

    const client = url.protocol === 'https:' ? https : http;
    const req = client.request(url, { method: 'POST', headers }, (res) => {
      let data = '';
      res.on('data', (chunk) => data += chunk);
      res.on('end', () => {
        try {
          const parsed = JSON.parse(data);
          if (LLM_BACKEND === 'ollama') {
            resolve(parsed.response || '');
          } else {
            resolve(parsed.choices?.[0]?.message?.content || '');
          }
        } catch (e) {
          reject(new Error(`LLM parse error: ${e.message}`));
        }
      });
    });
    req.on('error', reject);
    req.setTimeout(60000, () => { req.destroy(); reject(new Error('LLM timeout')); });
    req.write(body);
    req.end();
  });
}

/**
 * Generate documentation for a single entity using graph context.
 */
async function generateEntityDoc(entityId, graph, sourceCode) {
  const entity = graph.nodes.get(entityId);
  if (!entity) return null;

  // Gather context: callers, callees, container
  const outgoing = graph.edges.filter(e => e.source === entityId);
  const incoming = graph.edges.filter(e => e.target === entityId);

  const calls = outgoing.filter(e => e.type === 'CALLS').map(e => e.target);
  const calledBy = incoming.filter(e => e.type === 'CALLS').map(e => e.source);
  const containedBy = incoming.filter(e => e.type === 'CONTAINS').map(e => e.source);

  // Extract source snippet if available
  let snippet = '';
  if (sourceCode && entity.line_range) {
    const lines = sourceCode.split('\n');
    const [start, end] = entity.line_range;
    snippet = lines.slice(start - 1, Math.min(end, start + 50)).join('\n');
    if (end - start > 50) snippet += '\n// ... truncated';
  }

  const prompt = `You are a senior engineer writing concise documentation.

Describe what this ${entity.type.toLowerCase()} does in 2-3 sentences. Be specific about domain logic, not syntax.

Entity: ${entity.name} (${entity.type}, ${entity.kind})
Visibility: ${entity.visibility}
Location: ${entityId}
${containedBy.length > 0 ? `Part of: ${containedBy.join(', ')}` : ''}
${calls.length > 0 ? `Calls: ${calls.slice(0, 10).join(', ')}${calls.length > 10 ? ` (+${calls.length - 10} more)` : ''}` : ''}
${calledBy.length > 0 ? `Called by: ${calledBy.slice(0, 10).join(', ')}${calledBy.length > 10 ? ` (+${calledBy.length - 10} more)` : ''}` : ''}

${snippet ? `Source:\n\`\`\`\n${snippet}\n\`\`\`` : ''}

Documentation:`;

  return callLLM(prompt);
}

/**
 * Generate a change summary using semantic diff context.
 */
async function generateDiffDoc(diff) {
  const summary = formatSummary(diff);

  const prompt = `You are a senior engineer writing a changelog entry for a code review.

Given this semantic diff, write a concise 3-5 sentence summary suitable for a PR description or release note. Focus on:
- What changed and why it matters
- Breaking changes that need attention
- Impact on downstream consumers

Semantic Diff:
${summary}

Changelog entry:`;

  return callLLM(prompt, 512);
}

/**
 * Generate docs for all public entities in a file.
 */
async function generateFileDocs(filePath, graph, repoRoot) {
  const entityIds = graph.fileIndex.get(filePath);
  if (!entityIds) return [];

  let sourceCode = '';
  try {
    sourceCode = fs.readFileSync(filePath, 'utf8');
  } catch {}

  const docs = [];
  for (const id of entityIds) {
    const entity = graph.nodes.get(id);
    if (!entity || entity.visibility !== 'public' || entity.type === 'Dependency') continue;

    try {
      const doc = await generateEntityDoc(id, graph, sourceCode);
      if (doc) {
        docs.push({ entityId: id, name: entity.name, type: entity.type, doc });
      }
    } catch (err) {
      console.error(`  Failed to generate doc for ${id}: ${err.message}`);
    }
  }

  return docs;
}

/**
 * Batch generate docs for changed entities in a diff.
 */
async function generateDiffDocs(diff, oldGraph, newGraph, repoRoot) {
  const results = { changeSummary: '', entityDocs: [] };

  // Generate overall change summary
  try {
    results.changeSummary = await generateDiffDoc(diff);
  } catch (err) {
    console.error(`Failed to generate change summary: ${err.message}`);
  }

  // Generate docs for new/modified public entities
  const entitiesToDoc = [];
  for (const item of diff.categorized.significant) {
    if (item.entity && item.entity.visibility === 'public') {
      entitiesToDoc.push(item.entity.id);
    }
    if (item.new && item.new.visibility === 'public') {
      entitiesToDoc.push(item.new.id);
    }
  }

  for (const id of entitiesToDoc) {
    const entity = newGraph.nodes.get(id);
    if (!entity || entity.type === 'Dependency') continue;

    let sourceCode = '';
    if (entity._file) {
      try { sourceCode = fs.readFileSync(entity._file, 'utf8'); } catch {}
    }

    try {
      const doc = await generateEntityDoc(id, newGraph, sourceCode);
      if (doc) {
        results.entityDocs.push({ entityId: id, name: entity.name, type: entity.type, doc });
      }
    } catch (err) {
      console.error(`  Failed to generate doc for ${id}: ${err.message}`);
    }
  }

  return results;
}

// --- CLI ---
if (require.main === module) {
  const args = process.argv.slice(2);
  const command = args[0];

  if (command === 'entity') {
    const snapshotPath = args[1];
    const entityId = args[2];
    const filePath = args[3]; // optional source file

    if (!snapshotPath || !entityId) {
      console.error('Usage: node docgen.js entity <snapshot.json> <entityId> [source-file]');
      process.exit(1);
    }

    const graph = GraphStore.loadSnapshot(snapshotPath);
    let source = '';
    if (filePath) {
      try { source = fs.readFileSync(filePath, 'utf8'); } catch {}
    }

    generateEntityDoc(entityId, graph, source).then(doc => {
      console.log(doc);
    }).catch(err => {
      console.error(err.message);
      process.exit(1);
    });

  } else if (command === 'diff') {
    const oldPath = args[1];
    const newPath = args[2];

    if (!oldPath || !newPath) {
      console.error('Usage: node docgen.js diff <old-snapshot.json> <new-snapshot.json>');
      process.exit(1);
    }

    const oldGraph = GraphStore.loadSnapshot(oldPath);
    const newGraph = GraphStore.loadSnapshot(newPath);
    const diff = semanticDiff(oldGraph, newGraph);

    generateDiffDocs(diff, oldGraph, newGraph).then(results => {
      console.log('=== Change Summary ===');
      console.log(results.changeSummary);
      console.log('');
      if (results.entityDocs.length > 0) {
        console.log('=== Entity Documentation ===');
        for (const d of results.entityDocs) {
          console.log(`\n[${d.type}] ${d.name} (${d.entityId})`);
          console.log(d.doc);
        }
      }
    }).catch(err => {
      console.error(err.message);
      process.exit(1);
    });

  } else if (command === 'file') {
    const snapshotPath = args[1];
    const filePath = args[2];

    if (!snapshotPath || !filePath) {
      console.error('Usage: node docgen.js file <snapshot.json> <source-file>');
      process.exit(1);
    }

    const graph = GraphStore.loadSnapshot(snapshotPath);
    generateFileDocs(filePath, graph).then(docs => {
      for (const d of docs) {
        console.log(`\n[${d.type}] ${d.name} (${d.entityId})`);
        console.log(d.doc);
      }
      if (docs.length === 0) console.log('No public entities found in file.');
    }).catch(err => {
      console.error(err.message);
      process.exit(1);
    });

  } else {
    console.error('Unknown command. Available: entity, diff, file');
    process.exit(1);
  }
}

module.exports = { generateEntityDoc, generateDiffDoc, generateFileDocs, generateDiffDocs, callLLM };
