/**
 * MemoryProxy Service
 *
 * Bridges Tractatus governance framework with Anthropic's memory tool for persistent rule storage.
 *
 * Architecture:
 * - Application Layer (BoundaryEnforcer, etc.) → MemoryProxy → Memory Backend → Claude API
 * - Provides abstraction over memory storage (filesystem, MongoDB, encrypted, etc.)
 * - Handles rule persistence, retrieval, validation, and audit logging
 *
 * Phase 5 PoC - Week 2/3 Implementation
 */

const fs = require('fs').promises;
const path = require('path');
const logger = require('../utils/logger.util');

class MemoryProxyService {
  constructor(options = {}) {
    this.memoryBasePath = options.memoryBasePath || path.join(__dirname, '../../.memory');
    this.cacheEnabled = options.cacheEnabled !== false;
    this.cacheTTL = options.cacheTTL || 300000; // 5 minutes default
    this.cache = new Map();
    this.cacheTimestamps = new Map();

    logger.info('MemoryProxyService initialized', {
      basePath: this.memoryBasePath,
      cacheEnabled: this.cacheEnabled,
      cacheTTL: this.cacheTTL
    });
  }

  /**
   * Initialize memory directory structure
   */
  async initialize() {
    try {
      await fs.mkdir(path.join(this.memoryBasePath, 'governance'), { recursive: true });
      await fs.mkdir(path.join(this.memoryBasePath, 'sessions'), { recursive: true });
      await fs.mkdir(path.join(this.memoryBasePath, 'audit'), { recursive: true });

      logger.info('Memory directory structure initialized');
      return true;
    } catch (error) {
      logger.error('Failed to initialize memory directories', { error: error.message });
      throw error;
    }
  }

  /**
   * Persist governance rules to memory
   *
   * @param {Array} rules - Array of governance rule objects
   * @returns {Promise<Object>} - Result with success status and metadata
   */
  async persistGovernanceRules(rules) {
    const startTime = Date.now();

    try {
      logger.info('Persisting governance rules', { count: rules.length });

      // Validate rules
      if (!Array.isArray(rules)) {
        throw new Error('Rules must be an array');
      }

      if (rules.length === 0) {
        throw new Error('Cannot persist empty rules array');
      }

      // Ensure all rules have required fields
      for (const rule of rules) {
        if (!rule.id || !rule.text || !rule.quadrant || !rule.persistence) {
          throw new Error(`Invalid rule format: ${JSON.stringify(rule)}`);
        }
      }

      // Prepare storage object
      const rulesData = {
        version: '1.0',
        updated_at: new Date().toISOString(),
        total_rules: rules.length,
        rules: rules,
        stats: {
          by_quadrant: this._countByField(rules, 'quadrant'),
          by_persistence: this._countByField(rules, 'persistence')
        }
      };

      // Store to filesystem
      const filePath = path.join(this.memoryBasePath, 'governance/tractatus-rules-v1.json');
      await fs.writeFile(filePath, JSON.stringify(rulesData, null, 2), 'utf8');

      // Update cache
      if (this.cacheEnabled) {
        this.cache.set('governance-rules', rulesData);
        this.cacheTimestamps.set('governance-rules', Date.now());
      }

      const duration = Date.now() - startTime;

      logger.info('Governance rules persisted successfully', {
        count: rules.length,
        duration: `${duration}ms`,
        path: filePath
      });

      return {
        success: true,
        rulesStored: rules.length,
        duration,
        path: filePath,
        stats: rulesData.stats
      };

    } catch (error) {
      logger.error('Failed to persist governance rules', {
        error: error.message,
        count: rules.length
      });
      throw error;
    }
  }

  /**
   * Load governance rules from memory
   *
   * @param {Object} options - Loading options
   * @returns {Promise<Array>} - Array of governance rule objects
   */
  async loadGovernanceRules(options = {}) {
    const startTime = Date.now();

    try {
      // Check cache first
      if (this.cacheEnabled && !options.skipCache) {
        const cached = this._getCachedRules();
        if (cached) {
          logger.debug('Governance rules loaded from cache');
          return cached;
        }
      }

      // Load from filesystem
      const filePath = path.join(this.memoryBasePath, 'governance/tractatus-rules-v1.json');
      const data = await fs.readFile(filePath, 'utf8');
      const parsed = JSON.parse(data);

      // Update cache
      if (this.cacheEnabled) {
        this.cache.set('governance-rules', parsed);
        this.cacheTimestamps.set('governance-rules', Date.now());
      }

      const duration = Date.now() - startTime;

      logger.info('Governance rules loaded successfully', {
        count: parsed.rules.length,
        duration: `${duration}ms`,
        fromCache: false
      });

      return parsed.rules;

    } catch (error) {
      if (error.code === 'ENOENT') {
        logger.warn('Governance rules file not found - returning empty array');
        return [];
      }

      logger.error('Failed to load governance rules', { error: error.message });
      throw error;
    }
  }

  /**
   * Get specific rule by ID
   *
   * @param {string} ruleId - Rule identifier (e.g., 'inst_016')
   * @returns {Promise<Object|null>} - Rule object or null if not found
   */
  async getRule(ruleId) {
    try {
      const rules = await this.loadGovernanceRules();
      const rule = rules.find(r => r.id === ruleId);

      if (rule) {
        logger.debug('Rule retrieved', { ruleId });
      } else {
        logger.warn('Rule not found', { ruleId });
      }

      return rule || null;
    } catch (error) {
      logger.error('Failed to get rule', { ruleId, error: error.message });
      throw error;
    }
  }

  /**
   * Get rules by quadrant
   *
   * @param {string} quadrant - Quadrant name (STRATEGIC, OPERATIONAL, etc.)
   * @returns {Promise<Array>} - Array of rules in the specified quadrant
   */
  async getRulesByQuadrant(quadrant) {
    try {
      const rules = await this.loadGovernanceRules();
      const filtered = rules.filter(r => r.quadrant === quadrant);

      logger.debug('Rules filtered by quadrant', {
        quadrant,
        count: filtered.length
      });

      return filtered;
    } catch (error) {
      logger.error('Failed to get rules by quadrant', {
        quadrant,
        error: error.message
      });
      throw error;
    }
  }

  /**
   * Get rules by persistence level
   *
   * @param {string} persistence - Persistence level (HIGH, MEDIUM, LOW)
   * @returns {Promise<Array>} - Array of rules with specified persistence
   */
  async getRulesByPersistence(persistence) {
    try {
      const rules = await this.loadGovernanceRules();
      const filtered = rules.filter(r => r.persistence === persistence);

      logger.debug('Rules filtered by persistence', {
        persistence,
        count: filtered.length
      });

      return filtered;
    } catch (error) {
      logger.error('Failed to get rules by persistence', {
        persistence,
        error: error.message
      });
      throw error;
    }
  }

  /**
   * Audit a decision/action
   *
   * @param {Object} decision - Decision object to audit
   * @returns {Promise<Object>} - Audit result
   */
  async auditDecision(decision) {
    const startTime = Date.now();

    try {
      // Validate decision object
      if (!decision.sessionId || !decision.action) {
        throw new Error('Decision must include sessionId and action');
      }

      // Prepare audit entry
      const auditEntry = {
        timestamp: new Date().toISOString(),
        sessionId: decision.sessionId,
        action: decision.action,
        rulesChecked: decision.rulesChecked || [],
        violations: decision.violations || [],
        allowed: decision.allowed !== false,
        metadata: decision.metadata || {}
      };

      // Append to today's audit log (JSONL format)
      const today = new Date().toISOString().split('T')[0];
      const auditPath = path.join(this.memoryBasePath, `audit/decisions-${today}.jsonl`);

      await fs.appendFile(
        auditPath,
        JSON.stringify(auditEntry) + '\n',
        'utf8'
      );

      const duration = Date.now() - startTime;

      logger.info('Decision audited', {
        sessionId: decision.sessionId,
        allowed: auditEntry.allowed,
        violations: auditEntry.violations.length,
        duration: `${duration}ms`
      });

      return {
        success: true,
        audited: true,
        path: auditPath,
        duration
      };

    } catch (error) {
      logger.error('Failed to audit decision', {
        error: error.message,
        sessionId: decision.sessionId
      });
      throw error;
    }
  }

  /**
   * Clear cache (useful for testing or after rule updates)
   */
  clearCache() {
    this.cache.clear();
    this.cacheTimestamps.clear();
    logger.debug('Memory cache cleared');
  }

  /**
   * Get cache statistics
   */
  getCacheStats() {
    return {
      enabled: this.cacheEnabled,
      ttl: this.cacheTTL,
      entries: this.cache.size,
      keys: Array.from(this.cache.keys())
    };
  }

  // Private helper methods

  _getCachedRules() {
    const cacheKey = 'governance-rules';

    if (!this.cache.has(cacheKey)) {
      return null;
    }

    const timestamp = this.cacheTimestamps.get(cacheKey);
    const age = Date.now() - timestamp;

    if (age > this.cacheTTL) {
      // Cache expired
      this.cache.delete(cacheKey);
      this.cacheTimestamps.delete(cacheKey);
      return null;
    }

    const cached = this.cache.get(cacheKey);
    return cached.rules;
  }

  _countByField(rules, field) {
    const counts = {};
    rules.forEach(rule => {
      const value = rule[field];
      counts[value] = (counts[value] || 0) + 1;
    });
    return counts;
  }
}

// Export singleton instance
let instance = null;

function getMemoryProxy(options = {}) {
  if (!instance) {
    instance = new MemoryProxyService(options);
  }
  return instance;
}

module.exports = {
  MemoryProxyService,
  getMemoryProxy
};
