'use strict';

/**
 * Fabius Core — Main entry point
 * 
 * Wires together all core components into a single facade.
 */

const path = require('path');
const os = require('os');
const fs = require('fs');
const { randomUUID } = require('crypto');

const Config = require('./config/config');
const FabiusDB = require('./registry/db');
const AIProvider = require('./ai/provider');
const ScannerEngine = require('./scanner/engine');
const IntelPipeline = require('./intel/pipeline');
const WorkflowManager = require('./workflow/manager');
const RemediationEngine = require('./remediation/engine');

class Fabius {
  /**
   * @param {object} options
   * @param {string} options.configDir - Override config/data directory
   */
  constructor(options = {}) {
    // 1. Config
    this.config = new Config(options.configDir);

    // 2. Database
    this.db = new FabiusDB(this.config.getDbPath());

    // 3. Machine registration
    this._registerMachine();

    // 4. AI Provider
    const aiConfig = this.config.get('ai', {});
    this.ai = null;
    if (aiConfig.apiKey) {
      this.ai = new AIProvider(aiConfig);
    }

    // 5. Scanner
    this.scanner = new ScannerEngine({
      config: this.config.get('scanning', {}),
      db: this.db,
    });

    // 6. Intel Pipeline
    this.intel = new IntelPipeline({
      db: this.db,
      ai: this.ai,
      config: this.config,
      onThreatDetected: (threat) => this._onThreatDetected(threat),
    });

    // 7. Workflow Manager
    this.workflows = new WorkflowManager({
      db: this.db,
      ai: this.ai,
    });

    // 8. Remediation Engine
    this.remediation = new RemediationEngine({
      db: this.db,
      ai: this.ai,
      config: this.config,
    });

    // Event callbacks
    this._listeners = {
      threatDetected: [],
      scanComplete: [],
      remediationProposed: [],
    };
  }

  // ─── Event System ─────────────────────────────────────────────────────

  on(event, callback) {
    if (this._listeners[event]) {
      this._listeners[event].push(callback);
    }
    return this;
  }

  _emit(event, data) {
    if (this._listeners[event]) {
      for (const cb of this._listeners[event]) {
        try { cb(data); } catch { /* ok */ }
      }
    }
  }

  // ─── High-Level Operations ────────────────────────────────────────────

  /**
   * Scan a specific repository
   * @param {string} repoId - Repository ID
   * @param {string} threatId - Optional: scan for specific threat only
   * @param {string} triggerType - 'manual', 'auto', or 'schedule'
   * @returns {Promise<object>} Scan results
   */
  async scanRepo(repoId, threatId = null, triggerType = 'manual') {
    const repo = this.db.getRepo(repoId);
    if (!repo) throw new Error(`Repository not found: ${repoId}`);

    const scanId = this.db.startScan(repoId, threatId, triggerType);

    try {
      const results = await this.scanner.scan(repo.path, { threatId });

      this.db.completeScan(scanId, results.findings, 
        `${results.alerts} alerts, ${results.warnings} warnings, ${results.filesScanned} files scanned`
      );

      const status = results.alerts > 0 ? 'infected' : results.warnings > 0 ? 'warnings' : 'clean';
      this.db.updateRepoScan(repoId, status);

      // Auto-propose remediations if findings exist
      if (results.alerts > 0 && this.ai) {
        try {
          const proposals = await this.remediation.proposeForScan(scanId);

          // Auto-apply if configured
          const autoApply = this.config.get('remediation.autoApply', false);
          if (autoApply && proposals.length > 0) {
            for (const proposal of proposals) {
              const trustedOnly = this.config.get('remediation.autoApplyTrustedOnly', true);
              // Only auto-apply if trusted source or not restricted
              if (!trustedOnly) {
                await this.remediation.apply(proposal.id, 'auto');
              }
            }
          }

          this._emit('remediationProposed', { scanId, proposals });
        } catch {
          // Remediation proposal failure is not critical
        }
      }

      this._emit('scanComplete', { repoId, scanId, results });
      return { scanId, results };

    } catch (err) {
      this.db.failScan(scanId, err.message);
      throw err;
    }
  }

  /**
   * Scan all managed repositories
   */
  async scanAllRepos(triggerType = 'manual') {
    const repos = this.db.listRepos();
    const results = [];
    for (const repo of repos) {
      try {
        const result = await this.scanRepo(repo.id, null, triggerType);
        results.push({ repoId: repo.id, repoName: repo.name, ...result });
      } catch (err) {
        results.push({ repoId: repo.id, repoName: repo.name, error: err.message });
      }
    }
    return results;
  }

  /**
   * Scan a path directly (without registering as a repo)
   */
  async scanPath(scanPath) {
    return this.scanner.scan(scanPath);
  }

  /**
   * Add a repository to management
   */
  addRepo(repoPath, name = null) {
    const resolved = path.resolve(repoPath);
    if (!fs.existsSync(resolved)) {
      throw new Error(`Path does not exist: ${resolved}`);
    }

    const id = randomUUID();
    const repoName = name || path.basename(resolved);

    this.db.addRepo({
      id,
      name: repoName,
      path: resolved,
      machineId: this.machineId,
      ecosystem: this._detectEcosystem(resolved),
    });

    return { id, name: repoName, path: resolved };
  }

  /**
   * Remove a repository from management
   */
  removeRepo(repoId) {
    this.db.removeRepo(repoId);
  }

  /**
   * Process a new threat: generate detection + workflows + distribute
   */
  async processThreat(threatId) {
    if (!this.ai) throw new Error('AI provider not configured');

    const threat = this.db.getThreat(threatId);
    if (!threat) throw new Error(`Threat not found: ${threatId}`);

    const results = {};

    // 1. Generate detection code
    try {
      results.detection = await this.workflows.generateDetection(threatId);
    } catch (err) {
      results.detectionError = err.message;
    }

    // 2. Generate workflows
    try {
      results.workflows = await this.workflows.generateForThreat(threatId);
    } catch (err) {
      results.workflowError = err.message;
    }

    // 3. Distribute workflows
    try {
      results.distribution = this.workflows.distributeToAll();
    } catch (err) {
      results.distributionError = err.message;
    }

    return results;
  }

  /**
   * Get dashboard stats
   */
  getStats() {
    return this.db.getStats();
  }

  // ─── Lifecycle ────────────────────────────────────────────────────────

  /**
   * Start background operations (intel polling, scheduled scans)
   */
  start() {
    this.intel.initDefaultSources();

    if (this.ai && this.config.get('scheduler.enabled', true)) {
      this.intel.start();

      // Scheduled full scan
      const scanInterval = this.config.get('scheduler.fullScanInterval', 86400000);
      this._scanTimer = setInterval(() => {
        this.scanAllRepos('schedule').catch(err => {
          console.warn(`[Fabius] Scheduled scan failed: ${err.message}`);
        });
      }, scanInterval);
    }
  }

  /**
   * Stop all background operations
   */
  stop() {
    this.intel.stop();
    if (this._scanTimer) {
      clearInterval(this._scanTimer);
      this._scanTimer = null;
    }
  }

  /**
   * Shutdown cleanly
   */
  shutdown() {
    this.stop();
    this.db.close();
  }

  // ─── Internal ─────────────────────────────────────────────────────────

  _registerMachine() {
    const machinePath = this.config.getMachinePath();
    let machineData;

    try {
      if (fs.existsSync(machinePath)) {
        machineData = JSON.parse(fs.readFileSync(machinePath, 'utf-8'));
      }
    } catch { /* ok */ }

    if (!machineData) {
      machineData = {
        id: randomUUID(),
        name: os.hostname(),
        hostname: os.hostname(),
        os: process.platform,
        registeredAt: new Date().toISOString(),
      };
      const dir = path.dirname(machinePath);
      if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
      fs.writeFileSync(machinePath, JSON.stringify(machineData, null, 2), 'utf-8');
    }

    this.machineId = machineData.id;
    this.db.registerMachine(machineData);
  }

  _onThreatDetected(threat) {
    console.log(`[Fabius] New threat: ${threat.name} (${threat.severity})`);
    this._emit('threatDetected', threat);

    // Auto-process if from trusted source
    if (threat.sourceTrusted && this.ai) {
      this.processThreat(threat.id).catch(err => {
        console.warn(`[Fabius] Auto-process failed for ${threat.id}: ${err.message}`);
      });

      // Auto-scan all repos
      this.scanAllRepos('auto').catch(err => {
        console.warn(`[Fabius] Auto-scan failed: ${err.message}`);
      });
    }
  }

  _detectEcosystem(repoPath) {
    if (fs.existsSync(path.join(repoPath, 'package.json'))) return 'npm';
    if (fs.existsSync(path.join(repoPath, 'requirements.txt'))) return 'pip';
    if (fs.existsSync(path.join(repoPath, 'Cargo.toml'))) return 'cargo';
    if (fs.existsSync(path.join(repoPath, 'go.mod'))) return 'go';
    return 'unknown';
  }
}

module.exports = Fabius;
