const fs = require('fs');
const path = require('path');
const GraphStore = require('./graph.js');

/**
 * Developer Intelligence Pipeline v2 - Cross-Repo Namespace Registry
 * Resolves cross-repo references using 3-tier matching.
 * No external dependencies.
 */

const SCRIPT_DIR = __dirname;

/**
 * Classify an entity into an artifact type for infrastructure-level matching.
 * Supports: rest-api, grpc-service, helm-chart, terraform-resource, config, code-module
 */
function classifyArtifact(entity) {
  if (entity.type === 'Config') {
    if (entity.kind === 'terraform' || entity.kind === 'hcl-block') return 'terraform-resource';
    if (entity.kind === 'yaml-config' || entity.kind === 'yaml-key') return 'config';
    return 'config';
  }
  if (entity.type === 'Class' && entity.kind === 'interface') return 'interface';
  if (entity.type === 'Class') return 'class';
  if (entity.type === 'Function') return 'code-module';
  if (entity.type === 'Module') return 'code-module';
  return 'code-module';
}

class NamespaceRegistry {
  constructor() {
    this.byShortName = new Map();  // shortName -> [{repoId, entityId, type, kind}]
    this.byEntityId = new Map();   // entityId -> {repoId, shortName}
    this.overrides = new Map();    // localName -> {repoId, entityId}
  }

  /**
   * Build registry from multiple graph snapshots.
   * Collects public entities and indexes them for cross-repo resolution.
   * @param {Array<{repoId: string, snapshot: GraphStore}>} repos
   * @returns {NamespaceRegistry}
   */
  static build(repos) {
    const reg = new NamespaceRegistry();

    for (const { repoId, snapshot } of repos) {
      for (const [id, entity] of snapshot.nodes.entries()) {
        if (entity.visibility !== 'public') continue;
        if (entity.type === 'Dependency') continue;

        const shortName = entity.name;
        const entry = {
          repoId,
          entityId: id,
          type: entity.type,
          kind: entity.kind,
          // Artifact classification for infrastructure matching
          artifact: classifyArtifact(entity),
        };

        // byShortName
        if (!reg.byShortName.has(shortName)) {
          reg.byShortName.set(shortName, []);
        }
        reg.byShortName.get(shortName).push(entry);

        // byEntityId (prefix with repoId for cross-repo uniqueness)
        reg.byEntityId.set(`${repoId}:${id}`, { repoId, shortName, artifact: entry.artifact });
      }
    }

    return reg;
  }

  /**
   * Load overrides from a JSON file.
   * @param {string} overridePath
   */
  loadOverrides(overridePath) {
    if (!fs.existsSync(overridePath)) return;
    const data = JSON.parse(fs.readFileSync(overridePath, 'utf8'));
    for (const [localName, target] of Object.entries(data)) {
      const colonIdx = target.indexOf(':');
      if (colonIdx > 0) {
        this.overrides.set(localName, {
          repoId: target.slice(0, colonIdx),
          entityId: target.slice(colonIdx + 1),
        });
      }
    }
  }

  /**
   * Resolve a name using 3-tier matching.
   * @param {string} name - The unresolved target name
   * @param {string} [sourceRepoId] - The repo making the call (excluded from results)
   * @returns {{resolvedTo: {repoId, entityId}, tier: number, confidence: number} | null}
   */
  resolve(name, sourceRepoId) {
    // Override always wins
    if (this.overrides.has(name)) {
      const target = this.overrides.get(name);
      return { resolvedTo: target, tier: 0, confidence: 1.0 };
    }

    // Tier 1: Exact entity ID match
    for (const [key, val] of this.byEntityId.entries()) {
      const entityId = key.slice(key.indexOf(':') + 1);
      if (entityId === name && val.repoId !== sourceRepoId) {
        return { resolvedTo: { repoId: val.repoId, entityId }, tier: 1, confidence: 1.0 };
      }
    }

    // Tier 2: Normalized match (strip extensions, normalize paths)
    const normalized = name.replace(/\.(ts|js|tsx|jsx|py|java|go|sh)$/, '').replace(/\\/g, '/');
    for (const [key, val] of this.byEntityId.entries()) {
      const entityId = key.slice(key.indexOf(':') + 1);
      const normId = entityId.replace(/\.(ts|js|tsx|jsx|py|java|go|sh)/, '').replace(/\\/g, '/');
      if (normId === normalized && val.repoId !== sourceRepoId) {
        return { resolvedTo: { repoId: val.repoId, entityId }, tier: 2, confidence: 0.9 };
      }
    }

    // Tier 3: Name-only match
    const matches = (this.byShortName.get(name) || []).filter(e => e.repoId !== sourceRepoId);
    if (matches.length === 1) {
      return { resolvedTo: { repoId: matches[0].repoId, entityId: matches[0].entityId }, tier: 3, confidence: 0.7 };
    }
    if (matches.length > 1) {
      // Ambiguous — return first match with lower confidence
      return { resolvedTo: { repoId: matches[0].repoId, entityId: matches[0].entityId }, tier: 3, confidence: 0.5 };
    }

    return null;
  }

  /**
   * Resolve all unresolved CALLS edges in a graph.
   * @param {GraphStore} graph
   * @param {NamespaceRegistry} registry
   * @param {string} sourceRepoId
   * @returns {Array<{source, target, resolvedTo, tier, confidence}>}
   */
  static resolveExternalCalls(graph, registry, sourceRepoId) {
    const results = [];

    for (const edge of graph.edges) {
      if (edge.type !== 'CALLS') continue;
      // If target exists as a node, it's internal — skip
      if (graph.nodes.has(edge.target)) continue;

      const resolution = registry.resolve(edge.target, sourceRepoId);
      if (resolution) {
        results.push({
          source: edge.source,
          target: edge.target,
          resolvedTo: resolution.resolvedTo,
          tier: resolution.tier,
          confidence: resolution.confidence,
        });
      }
    }

    return results;
  }

  /**
   * Serialize registry to JSON.
   */
  toJSON() {
    return {
      byShortName: Object.fromEntries(this.byShortName),
      byEntityId: Object.fromEntries(this.byEntityId),
      overrides: Object.fromEntries(this.overrides),
    };
  }

  /**
   * Deserialize registry from JSON.
   */
  static fromJSON(data) {
    const reg = new NamespaceRegistry();
    for (const [k, v] of Object.entries(data.byShortName || {})) {
      reg.byShortName.set(k, v);
    }
    for (const [k, v] of Object.entries(data.byEntityId || {})) {
      reg.byEntityId.set(k, v);
    }
    for (const [k, v] of Object.entries(data.overrides || {})) {
      reg.overrides.set(k, v);
    }
    return reg;
  }

  /**
   * Lookup a name in the registry.
   */
  lookup(name) {
    const exact = this.byShortName.get(name) || [];
    // Also check entity IDs containing the name
    const byId = [];
    for (const [key, val] of this.byEntityId.entries()) {
      const entityId = key.slice(key.indexOf(':') + 1);
      if (entityId.includes(name)) {
        byId.push({ ...val, entityId });
      }
    }
    return { byName: exact, byId };
  }
}

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

  if (command === 'build') {
    const outputIdx = args.indexOf('--output');
    const outputPath = outputIdx >= 0 ? args[outputIdx + 1] : null;
    const snapshotPaths = args.slice(1).filter((_, i) => {
      const argIdx = i + 1;
      return argIdx !== outputIdx && argIdx !== outputIdx + 1;
    });

    if (snapshotPaths.length === 0 || !outputPath) {
      console.error('Usage: node namespace.js build <snapshot1.json> [snapshot2.json ...] --output <registry.json>');
      process.exit(1);
    }

    const repos = snapshotPaths.map((p, i) => {
      const snapshot = GraphStore.loadSnapshot(p);
      const repoId = path.basename(p, '.json');
      return { repoId, snapshot };
    });

    const registry = NamespaceRegistry.build(repos);

    // Load overrides if present
    const overridePath = path.join(path.dirname(outputPath), 'namespace-overrides.json');
    registry.loadOverrides(overridePath);

    fs.writeFileSync(outputPath, JSON.stringify(registry.toJSON(), null, 2), 'utf8');
    console.log(`Registry built: ${registry.byShortName.size} names, ${registry.byEntityId.size} entities from ${repos.length} repos. Saved to ${outputPath}`);

  } else if (command === 'resolve') {
    const graphPath = args[1];
    const registryPath = args[2];

    if (!graphPath || !registryPath) {
      console.error('Usage: node namespace.js resolve <graph-snapshot.json> <registry.json>');
      process.exit(1);
    }

    const graph = GraphStore.loadSnapshot(graphPath);
    const regData = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
    const registry = NamespaceRegistry.fromJSON(regData);
    const sourceRepoId = path.basename(graphPath, '.json');

    const results = NamespaceRegistry.resolveExternalCalls(graph, registry, sourceRepoId);

    if (results.length === 0) {
      console.log('No external calls resolved.');
    } else {
      console.log(`Resolved ${results.length} external call(s):`);
      for (const r of results) {
        console.log(`  ${r.source} -> ${r.target} => ${r.resolvedTo.repoId}:${r.resolvedTo.entityId} (tier ${r.tier}, confidence ${r.confidence})`);
      }
    }

  } else if (command === 'lookup') {
    const registryPath = args[1];
    const name = args[2];

    if (!registryPath || !name) {
      console.error('Usage: node namespace.js lookup <registry.json> <name>');
      process.exit(1);
    }

    const regData = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
    const registry = NamespaceRegistry.fromJSON(regData);
    const result = registry.lookup(name);

    console.log(JSON.stringify(result, null, 2));

  } else {
    console.error('Unknown command. Available: build, resolve, lookup');
    process.exit(1);
  }
}

module.exports = NamespaceRegistry;
