/**
 * @fileoverview ConfigGenerator - Renders templates and generates config files
 * @module morph-spec/generator/config-generator
 */

import { readFile, access, copyFile } from 'fs/promises';
import { join, dirname } from 'path';
import Handlebars from 'handlebars';
import { fileURLToPath } from 'url';
import Ajv from 'ajv';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

/**
 * @typedef {import('../types/index.js').ProjectConfig} ProjectConfig
 * @typedef {import('../types/index.js').GeneratedConfigs} GeneratedConfigs
 */

/**
 * Validation Error
 */
export class ValidationError extends Error {
  constructor(message, field, value) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
    this.value = value;
  }
}

/**
 * ConfigGenerator - Generates configuration files from ProjectConfig
 * @class
 */
export class ConfigGenerator {
  constructor() {
    this.projectMdTemplate = null;
    this.configJsonTemplate = null;
    this.ajv = new Ajv({ allErrors: true });
  }

  /**
   * Load templates from filesystem
   * @private
   */
  async loadTemplates() {
    if (this.projectMdTemplate && this.configJsonTemplate) {
      return; // Already loaded
    }

    const templatesDir = join(__dirname, 'templates');

    const [projectMdSource, configJsonSource] = await Promise.all([
      readFile(join(templatesDir, 'project.md.template'), 'utf-8'),
      readFile(join(templatesDir, 'config.json.template'), 'utf-8')
    ]);

    this.projectMdTemplate = Handlebars.compile(projectMdSource);
    this.configJsonTemplate = Handlebars.compile(configJsonSource);
  }

  /**
   * Generate configuration files from project config
   * @param {ProjectConfig} projectConfig - Detected project config
   * @returns {Promise<GeneratedConfigs>}
   */
  async generate(projectConfig) {
    // Load templates if not already loaded
    await this.loadTemplates();

    // Add generation timestamp
    const context = {
      ...projectConfig,
      generatedAt: new Date().toISOString()
    };

    // Render templates
    const projectMd = this.renderProjectMd(context);
    const configJson = this.renderConfigJson(context);

    // Parse config.json to object
    const configObject = JSON.parse(configJson);

    // Validate config.json (optional, but good practice)
    // Note: agent-schema.json validation would happen here if we had the schema
    // For now, we just ensure it's valid JSON

    return {
      projectMd,
      configJson,
      configObject
    };
  }

  /**
   * Render project.md template
   * @param {ProjectConfig} config - Project config
   * @returns {string} Rendered markdown
   */
  renderProjectMd(config) {
    if (!this.projectMdTemplate) {
      throw new Error('Templates not loaded. Call loadTemplates() first.');
    }

    return this.projectMdTemplate(config);
  }

  /**
   * Render config.json template
   * @param {ProjectConfig} config - Project config
   * @returns {string} Rendered JSON string
   */
  renderConfigJson(config) {
    if (!this.configJsonTemplate) {
      throw new Error('Templates not loaded. Call loadTemplates() first.');
    }

    const rendered = this.configJsonTemplate(config);

    // Validate that rendered output is valid JSON
    try {
      JSON.parse(rendered);
      return rendered;
    } catch (error) {
      throw new ValidationError(
        `Rendered config.json is not valid JSON: ${error.message}`,
        'configJson',
        rendered
      );
    }
  }

  /**
   * Validate config.json against agent schema
   * @param {string} configJson - JSON string
   * @returns {boolean} True if valid
   * @throws {ValidationError} If validation fails
   */
  validateConfigJson(configJson) {
    // Parse JSON
    let parsed;
    try {
      parsed = JSON.parse(configJson);
    } catch (error) {
      throw new ValidationError(
        `Invalid JSON: ${error.message}`,
        'configJson',
        configJson
      );
    }

    // Basic validation - ensure required fields exist
    const requiredFields = ['name', 'type', 'description', 'stack', 'architecture'];
    for (const field of requiredFields) {
      if (!parsed[field]) {
        throw new ValidationError(
          `Missing required field: ${field}`,
          field,
          parsed
        );
      }
    }

    // Validate stack.backend is required
    if (!parsed.stack || !parsed.stack.backend) {
      throw new ValidationError(
        'stack.backend is required',
        'stack.backend',
        parsed
      );
    }

    return true;
  }

  /**
   * Backup existing configuration files
   * @param {string} cwd - Current working directory
   * @returns {Promise<void>}
   */
  async backupExisting(cwd) {
    const projectMdPath = join(cwd, '.morph', 'project.md');
    const configJsonPath = join(cwd, '.morph', 'config', 'config.json');

    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');

    // Backup project.md if exists
    try {
      await access(projectMdPath);
      const backupPath = join(cwd, '.morph', `project.md.${timestamp}.backup`);
      await copyFile(projectMdPath, backupPath);
    } catch (error) {
      // File doesn't exist, no need to backup
    }

    // Backup config.json if exists
    try {
      await access(configJsonPath);
      const backupPath = join(cwd, '.morph', 'config', `config.json.${timestamp}.backup`);
      await copyFile(configJsonPath, backupPath);
    } catch (error) {
      // File doesn't exist, no need to backup
    }
  }
}
