/**
 * Consumer Generator
 *
 * Generator untuk Kafka Consumer files dari payload JSON.
 * Generate consumer code, handlers, dan control routes.
 *
 * @module generators/consumer-generator
 * @version 1.1.0
 * @date 2026-01-13
 */

const fs = require('fs');
const path = require('path');
const FileUtils = require('../utils/file-utils');
const ConfigReader = require('../config/config-reader');

/**
 * Generator untuk Kafka Consumer
 */
class ConsumerGenerator {
  /**
   * Generate consumer files dari payload
   * @param {string} consumerName - Nama consumer
   * @param {Object} payload - Consumer payload configuration
   * @param {Object|boolean} options - Options object atau forceOverwrite (backward compatible)
   * @param {boolean} options.force - Force overwrite existing files
   * @param {string} options.projectName - Nama project
   * @param {string} options.outputDir - Custom output directory
   * @returns {Object} Result { success, files, errors }
   */
  static async generate(consumerName, payload, options = {}) {
    const result = {
      success: true,
      files: [],
      errors: []
    };

    // Backward compatibility: jika options adalah boolean, berarti forceOverwrite
    let forceOverwrite = false;
    let projectName = null;
    let outputDir = null;

    if (typeof options === 'boolean') {
      forceOverwrite = options;
    } else if (typeof options === 'object') {
      forceOverwrite = options.force || false;
      projectName = options.projectName || null;
      outputDir = options.outputDir || null;
    }

    try {
      console.log('');
      console.log(`Generating Kafka Consumer: ${consumerName}`);
      console.log('='.repeat(50));

      // Validate payload
      const validationErrors = this.validatePayload(payload);
      if (validationErrors.length > 0) {
        result.success = false;
        result.errors = validationErrors;
        return result;
      }

      // Get working directory
      const workingDir = ConfigReader.getWorkingDirectory();

      // Determine consumer directory
      // Jika outputDir diberikan, gunakan itu
      // Jika projectName diberikan, gunakan src/consumers/{project}/{consumer}
      // Default: consumers/{consumer} (backward compatible)
      let consumerDir;
      if (outputDir) {
        consumerDir = outputDir;
      } else if (projectName) {
        consumerDir = path.join(workingDir, 'src', 'consumers', projectName, consumerName);
      } else {
        consumerDir = path.join(workingDir, 'consumers', consumerName);
      }

      const hooksDir = path.join(workingDir, 'hooks');
      const demoDir = path.join(workingDir, 'demo');
      const metadataDir = path.join(workingDir, 'metadata', 'consumers');
      const transformsDir = path.join(workingDir, 'transforms');

      // Ensure directories exist
      FileUtils.ensureDirectoryExists(consumerDir);
      FileUtils.ensureDirectoryExists(hooksDir);
      FileUtils.ensureDirectoryExists(demoDir);
      FileUtils.ensureDirectoryExists(metadataDir);

      // Generate files
      const files = [
        { path: path.join(consumerDir, 'consumer.js'), content: this.generateConsumerFile(consumerName, payload, projectName) },
        { path: path.join(consumerDir, 'handler.js'), content: this.generateHandlerFile(consumerName, payload, projectName) },
        { path: path.join(consumerDir, 'index.js'), content: this.generateIndexFile(consumerName, payload) },
        { path: path.join(metadataDir, `${consumerName}.json`), content: JSON.stringify(payload, null, 2) },
        { path: path.join(demoDir, `consumer-${consumerName}-test.json`), content: this.generateDemoFile(consumerName, payload) }
      ];

      // Generate transformer template if external source
      if (payload.message?.sourceType === 'external' && payload.message?.transformer) {
        FileUtils.ensureDirectoryExists(transformsDir);
        const transformerPath = path.join(workingDir, payload.message.transformer);
        if (!fs.existsSync(transformerPath)) {
          files.push({
            path: transformerPath,
            content: this.generateTransformerTemplate(consumerName, payload)
          });
        }
      }

      // Write files
      for (const file of files) {
        try {
          const fileExists = fs.existsSync(file.path);
          if (fileExists && !forceOverwrite) {
            console.log(`  [SKIP] ${path.relative(workingDir, file.path)} (exists)`);
            continue;
          }

          // Ensure parent directory exists
          FileUtils.ensureDirectoryExists(path.dirname(file.path));

          // Write file
          fs.writeFileSync(file.path, file.content);
          console.log(`  [CREATE] ${path.relative(workingDir, file.path)}`);
          result.files.push(file.path);
        } catch (error) {
          result.errors.push(`Failed to write ${file.path}: ${error.message}`);
        }
      }

      console.log('');
      console.log(`Consumer "${consumerName}" generated successfully!`);
      console.log(`Files created: ${result.files.length}`);

      return result;

    } catch (error) {
      result.success = false;
      result.errors.push(error.message);
      console.error(`Error generating consumer: ${error.message}`);
      return result;
    }
  }

  /**
   * Validate consumer payload
   * @param {Object} payload - Payload to validate
   * @returns {string[]} Array of validation errors
   */
  static validatePayload(payload) {
    const errors = [];

    if (!payload.consumerName) {
      errors.push('Missing required field: consumerName');
    }

    if (!payload.kafka?.topic) {
      errors.push('Missing required field: kafka.topic');
    }

    if (!payload.target?.type) {
      errors.push('Missing required field: target.type');
    }

    const validTargetTypes = ['database', 'api', 'kafka', 'console'];
    if (payload.target?.type && !validTargetTypes.includes(payload.target.type)) {
      errors.push(`Invalid target.type: ${payload.target.type}. Must be one of: ${validTargetTypes.join(', ')}`);
    }

    // Target-specific validation
    if (payload.target?.type === 'database') {
      if (!payload.target.database?.tableName) {
        errors.push('Missing required field for database target: target.database.tableName');
      }
    }

    if (payload.target?.type === 'api') {
      if (!payload.target.api?.url) {
        errors.push('Missing required field for api target: target.api.url');
      }
    }

    if (payload.target?.type === 'kafka') {
      if (!payload.target.kafka?.topic) {
        errors.push('Missing required field for kafka target: target.kafka.topic');
      }
    }

    return errors;
  }

  /**
   * Generate main consumer.js file
   * @param {string} consumerName - Consumer name
   * @param {Object} payload - Consumer payload
   * @param {string} projectName - Project name (untuk menentukan path relatif)
   * @returns {string} Generated code
   */
  static generateConsumerFile(consumerName, payload, projectName = null) {
    const kafkaConfig = payload.kafka || {};
    const messageConfig = payload.message || {};
    const processingConfig = payload.processing || {};

    return `/**
 * Kafka Consumer: ${consumerName}
 * ${payload.description || 'Auto-generated by RESTForge Consumer Generator'}
 *
 * Topic: ${kafkaConfig.topic}
 * Group ID: ${kafkaConfig.groupId || consumerName + '-group'}
 *
 * @generated ${new Date().toISOString()}
 */

const { createInstance } = require('@restforgejs/platform/src/services/kafkaConsumerService');
const { createValidator } = require('@restforgejs/platform/src/consumers/validator');
const { createTransformer } = require('@restforgejs/platform/src/consumers/messageTransformer');
const Handler = require('./handler');

class ${this.toPascalCase(consumerName)}Consumer {
  constructor() {
    this.name = '${consumerName}';
    this.consumer = null;
    this.handler = null;
    this.validator = null;
    this.transformer = null;
    this.isRunning = false;

    // Configuration
    this.config = ${JSON.stringify({
      kafka: kafkaConfig,
      message: messageConfig,
      processing: processingConfig,
      target: payload.target
    }, null, 6).replace(/\n/g, '\n    ')};
  }

  /**
   * Initialize consumer
   * @returns {Promise<boolean>}
   */
  async initialize() {
    try {
      // Create consumer instance
      this.consumer = createInstance();

      // Initialize connection
      const connected = await this.consumer.initialize({
        groupId: this.config.kafka.groupId || '${kafkaConfig.groupId || consumerName + '-group'}',
        clientId: 'restforge-consumer-${consumerName}'
      });

      if (!connected) {
        console.error(\`[${consumerName}] Failed to connect to Kafka\`);
        return false;
      }

      // Subscribe to topic
      await this.consumer.subscribe(
        this.config.kafka.topic,
        this.config.kafka.fromBeginning || false
      );

      // Initialize transformer
      this.transformer = createTransformer({
        sourceType: this.config.message.sourceType || 'internal',
        transformer: this.config.message.transformer,
        basePath: __dirname
      });

      // Initialize validator
      if (this.config.message.schema) {
        this.validator = createValidator({ coerceTypes: true });
      }

      // Initialize handler
      this.handler = new Handler(this.config);
      this.handler.setValidator(this.validator);
      this.handler.setTransformer(this.transformer);

      console.log(\`[${consumerName}] Consumer initialized\`);
      return true;

    } catch (error) {
      console.error(\`[${consumerName}] Initialize error: \${error.message}\`);
      return false;
    }
  }

  /**
   * Start consuming messages
   * @returns {Promise<boolean>}
   */
  async start() {
    if (this.isRunning) {
      console.log(\`[${consumerName}] Consumer already running\`);
      return true;
    }

    try {
      await this.consumer.startConsuming(async (messageContext) => {
        await this.handler.process(messageContext);
      });

      this.isRunning = true;
      console.log(\`[${consumerName}] Consumer started\`);
      return true;

    } catch (error) {
      console.error(\`[${consumerName}] Start error: \${error.message}\`);
      return false;
    }
  }

  /**
   * Stop consumer
   * @returns {Promise<boolean>}
   */
  async stop() {
    try {
      if (this.handler?.cleanup) {
        await this.handler.cleanup();
      }

      if (this.consumer) {
        await this.consumer.stop();
      }

      this.isRunning = false;
      console.log(\`[${consumerName}] Consumer stopped\`);
      return true;

    } catch (error) {
      console.error(\`[${consumerName}] Stop error: \${error.message}\`);
      return false;
    }
  }

  /**
   * Pause consumer
   * @returns {Promise<boolean>}
   */
  async pause() {
    if (!this.consumer) return false;
    return this.consumer.pause();
  }

  /**
   * Resume consumer
   * @returns {Promise<boolean>}
   */
  async resume() {
    if (!this.consumer) return false;
    return this.consumer.resume();
  }

  /**
   * Get status
   * @returns {Object}
   */
  getStatus() {
    return {
      name: this.name,
      running: this.isRunning,
      consumer: this.consumer?.getStatus() || null,
      handler: this.handler?.getStats() || null
    };
  }

  /**
   * Get metrics
   * @returns {Object}
   */
  getMetrics() {
    return {
      name: this.name,
      consumer: this.consumer?.getMetrics() || null,
      handler: this.handler?.getStats() || null
    };
  }
}

module.exports = ${this.toPascalCase(consumerName)}Consumer;
`;
  }

  /**
   * Generate handler.js file based on target type
   * @param {string} consumerName - Consumer name
   * @param {Object} payload - Consumer payload
   * @param {string} projectName - Project name (untuk menentukan path relatif)
   * @returns {string} Generated code
   */
  static generateHandlerFile(consumerName, payload, projectName = null) {
    const targetType = payload.target?.type || 'console';

    const handlerMap = {
      database: 'DatabaseHandler',
      api: 'ApiHandler',
      kafka: 'KafkaHandler',
      console: 'ConsoleHandler'
    };

    const handlerClass = handlerMap[targetType];
    const handlerPath = `@restforgejs/platform/src/consumers/handlers/${targetType}Handler`;

    return `/**
 * Handler for ${consumerName} consumer
 * Target Type: ${targetType}
 *
 * @generated ${new Date().toISOString()}
 */

const ${handlerClass} = require('${handlerPath}');

/**
 * Custom handler extending base ${handlerClass}
 * Override methods as needed for custom logic
 */
class ${this.toPascalCase(consumerName)}Handler extends ${handlerClass} {
  constructor(config) {
    super(config);
    this.consumerName = '${consumerName}';
  }

  /**
   * Pre-process hook - called before handling
   * Override untuk custom pre-processing
   * @param {Object} message - Message to process
   * @param {Object} context - Message context
   * @returns {Promise<Object>} Modified message
   */
  async preProcess(message, context) {
    // TODO: Add custom pre-processing logic if needed
    return message;
  }

  /**
   * Post-process hook - called after successful handling
   * Override untuk custom post-processing
   * @param {Object} result - Handler result
   * @param {Object} message - Original message
   * @param {Object} context - Message context
   */
  async postProcess(result, message, context) {
    // TODO: Add custom post-processing logic if needed
  }

  /**
   * Override handle untuk add pre/post processing
   * @param {Object} message - Message to handle
   * @param {Object} messageContext - Message context
   * @returns {Promise<Object>} Result
   */
  async handle(message, messageContext) {
    // Pre-process
    const processedMessage = await this.preProcess(message, messageContext);

    // Call parent handler
    const result = await super.handle(processedMessage, messageContext);

    // Post-process
    await this.postProcess(result, message, messageContext);

    return result;
  }

  /**
   * Override error handler untuk custom error handling
   * @param {Error} error - The error
   * @param {Object} message - The message
   * @param {Object} context - Message context
   */
  async onError(error, message, context) {
    console.error(\`[${consumerName}] Error processing message: \${error.message}\`);

    // Call parent error handler
    await super.onError(error, message, context);

    // TODO: Add custom error handling (alerting, logging, etc.)
  }
}

module.exports = ${this.toPascalCase(consumerName)}Handler;
`;
  }

  /**
   * Generate index.js entry point
   * @param {string} consumerName - Consumer name
   * @param {Object} payload - Consumer payload
   * @returns {string} Generated code
   */
  static generateIndexFile(consumerName, payload) {
    const className = this.toPascalCase(consumerName);

    return `/**
 * Entry point for ${consumerName} consumer
 *
 * @generated ${new Date().toISOString()}
 */

const ${className}Consumer = require('./consumer');

// Export consumer class
module.exports = ${className}Consumer;

// Standalone execution
if (require.main === module) {
  const consumer = new ${className}Consumer();

  // Graceful shutdown
  const shutdown = async (signal) => {
    console.log(\`\\nReceived \${signal}, shutting down...\`);
    await consumer.stop();
    process.exit(0);
  };

  process.on('SIGINT', () => shutdown('SIGINT'));
  process.on('SIGTERM', () => shutdown('SIGTERM'));

  // Start consumer
  (async () => {
    const initialized = await consumer.initialize();
    if (!initialized) {
      console.error('Failed to initialize consumer');
      process.exit(1);
    }

    const started = await consumer.start();
    if (!started) {
      console.error('Failed to start consumer');
      process.exit(1);
    }

    console.log('Consumer running. Press Ctrl+C to stop.');
  })();
}
`;
  }

  /**
   * Generate demo/test file
   * @param {string} consumerName - Consumer name
   * @param {Object} payload - Consumer payload
   * @returns {string} Demo JSON content
   */
  static generateDemoFile(consumerName, payload) {
    const demoMessage = {
      eventId: 'evt-demo-' + Date.now(),
      eventType: 'ENTITY_CREATED',
      eventTime: new Date().toISOString(),
      eventVersion: '1.0',
      source: `restforge-${consumerName}`,
      correlationId: 'corr-demo-' + Date.now(),
      newData: {
        id: 1,
        name: 'Demo Item',
        description: 'This is a demo message for testing',
        created_at: new Date().toISOString()
      },
      oldData: null,
      metadata: {
        createdBy: 'DEMO',
        createdAt: new Date().toISOString(),
        systemId: 'RESTFORGE-DEMO',
        tenantId: 'default'
      }
    };

    return JSON.stringify({
      description: `Demo/test message for ${consumerName} consumer`,
      topic: payload.kafka?.topic,
      key: 'demo-key-1',
      message: demoMessage
    }, null, 2);
  }

  /**
   * Generate transformer template for external sources
   * @param {string} consumerName - Consumer name
   * @param {Object} payload - Consumer payload
   * @returns {string} Transformer template code
   */
  static generateTransformerTemplate(consumerName, payload) {
    return `/**
 * Message Transformer for ${consumerName}
 *
 * Transform external messages ke format RESTForge standard.
 *
 * @generated ${new Date().toISOString()}
 */

/**
 * Transform external message
 * @param {Object} rawMessage - Raw message from Kafka
 * @returns {Promise<Object>} Transformed message
 */
module.exports = async function(rawMessage) {
  // TODO: Map fields dari external source ke internal format

  // Extract header dan payload dari raw message
  // Sesuaikan dengan format message dari source system
  const header = rawMessage.header || {};
  const payload = rawMessage.payload || rawMessage.data || rawMessage;

  // Event type mapping
  const eventTypeMap = {
    'CREATE': 'ENTITY_CREATED',
    'INSERT': 'ENTITY_CREATED',
    'UPDATE': 'ENTITY_UPDATED',
    'MODIFY': 'ENTITY_UPDATED',
    'DELETE': 'ENTITY_DELETED',
    'REMOVE': 'ENTITY_DELETED'
  };

  return {
    eventId: header.messageId || header.id || \`ext-\${Date.now()}\`,
    eventType: eventTypeMap[header.action?.toUpperCase()] || 'ENTITY_CREATED',
    eventTime: header.timestamp || new Date().toISOString(),
    eventVersion: '1.0',
    source: 'external-${consumerName}',
    correlationId: header.correlationId || \`ext-corr-\${Date.now()}\`,

    // TODO: Map payload fields ke newData
    newData: {
      // field1: payload.externalField1,
      // field2: payload.externalField2,
    },
    oldData: null,

    metadata: {
      originalSource: 'EXTERNAL',
      originalMessageId: header.messageId || header.id,
      transformedAt: new Date().toISOString()
    }
  };
};
`;
  }

  /**
   * Convert string to PascalCase
   * @param {string} str - Input string
   * @returns {string} PascalCase string
   */
  static toPascalCase(str) {
    return str
      .split(/[-_]/)
      .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
      .join('');
  }
}

module.exports = ConsumerGenerator;
