const fs = require('fs');
const path = require('path');

/**
 * Manager untuk data test integration.
 * Mengelola dua level data test:
 * - Global: test-data.json di root project
 * - Per-endpoint: tests/data/{endpoint}.test-data.json
 *
 * Prioritas resolusi (4 lapis):
 * 1. Per-endpoint test data (tertinggi)
 * 2. Global fieldDefaults
 * 3. Global typeDefaults
 * 4. Built-in type defaults (terendah) + warning
 */
class TestDataManager {

  /**
   * Built-in type defaults (lapis 4, fallback terakhir)
   */
  static get BUILTIN_TYPE_DEFAULTS() {
    return {
      integer: [1, 0, 100],
      decimal: [100.50, 0.00, 99999.99],
      number: [100.50, 0.00, 99999.99],
      boolean: [true, false],
      string: ['test-value'],
      date: ['2026-01-01'],
      datetime: ['2026-01-01T00:00:00.000Z'],
      timestamp: ['2026-01-01T00:00:00.000Z'],
      uuid: ['a1b2c3d4-e5f6-7890-abcd-ef1234567890'],
      json: [{}],
      array: [[]]
    };
  }

  /**
   * Template global test-data.json untuk --init
   */
  static get GLOBAL_TEMPLATE() {
    return {
      fieldDefaults: {
        qty: { values: [5, 10, 15], type: 'integer' },
        price: { values: [100.50, 0, 99999.99], type: 'decimal' },
        amount: { values: [100000, 0, 500000], type: 'decimal' },
        status: { values: ['active', 'inactive'], type: 'string' },
        is_active: { values: [true, false], type: 'boolean' }
      },
      typeDefaults: {
        integer: [1, 0, 100],
        decimal: [100.50, 0.00, 99999.99],
        boolean: [true, false],
        string: ['test-value'],
        date: ['2026-01-01'],
        datetime: ['2026-01-01T00:00:00.000Z']
      }
    };
  }

  // ─── Load Methods ──────────────────────────────────────────────────────────

  /**
   * Baca test-data.json global dari {cwd}/test-data.json
   * @returns {{ fieldDefaults: Object, typeDefaults: Object }}
   */
  static loadGlobalTestData() {
    const filePath = path.join(process.cwd(), 'test-data.json');

    if (!fs.existsSync(filePath)) {
      return { fieldDefaults: {}, typeDefaults: {} };
    }

    try {
      const content = fs.readFileSync(filePath, 'utf8');
      const data = JSON.parse(content);
      return {
        fieldDefaults: data.fieldDefaults || {},
        typeDefaults: data.typeDefaults || {}
      };
    } catch (error) {
      console.warn(`Warning: Failed to parse test-data.json: ${error.message}`);
      return { fieldDefaults: {}, typeDefaults: {} };
    }
  }

  /**
   * Baca per-endpoint test data dari {cwd}/tests/data/{endpoint}.test-data.json
   * @param {string} endpointName - Nama endpoint
   * @returns {{ create: Object, update: Object } | null} null jika file tidak ada
   */
  static loadEndpointTestData(endpointName) {
    const filePath = path.join(process.cwd(), 'tests', 'data', `${endpointName}.test-data.json`);

    if (!fs.existsSync(filePath)) {
      return null;
    }

    try {
      const content = fs.readFileSync(filePath, 'utf8');
      const data = JSON.parse(content);
      return {
        create: data.create || {},
        update: data.update || {}
      };
    } catch (error) {
      console.warn(`Warning: Failed to parse ${endpointName}.test-data.json: ${error.message}`);
      return null;
    }
  }

  // ─── Init Methods ──────────────────────────────────────────────────────────

  /**
   * Buat test-data.json global dengan template awal
   */
  static initGlobalTestData() {
    const filePath = path.join(process.cwd(), 'test-data.json');

    if (fs.existsSync(filePath)) {
      console.log('test-data.json already exists. Use --force to overwrite.');
      return false;
    }

    fs.writeFileSync(filePath, JSON.stringify(this.GLOBAL_TEMPLATE, null, 2), 'utf8');
    console.log('Created: test-data.json');
    console.log('Edit this file to define global test values for fields shared across endpoints.');
    return true;
  }

  /**
   * Buat per-endpoint test-data template dari payload
   * @param {string} endpointName - Nama endpoint
   * @param {Object} payload - Payload object (fieldName, fieldValidation, primaryKey)
   */
  static initEndpointTestData(endpointName, payload) {
    const dirPath = path.join(process.cwd(), 'tests', 'data');
    const filePath = path.join(dirPath, `${endpointName}.test-data.json`);

    if (fs.existsSync(filePath)) {
      console.log(`${endpointName}.test-data.json already exists. Use --force to overwrite.`);
      return false;
    }

    // Buat directory jika belum ada
    if (!fs.existsSync(dirPath)) {
      fs.mkdirSync(dirPath, { recursive: true });
    }

    // Build template dari payload fields
    const template = this._buildEndpointTemplate(endpointName, payload);

    fs.writeFileSync(filePath, JSON.stringify(template, null, 2), 'utf8');
    console.log(`Created: tests/data/${endpointName}.test-data.json`);
    console.log('Edit this file to define exact test values for this endpoint.');
    return true;
  }

  /**
   * Init dengan force mode (timpa file yang sudah ada)
   * @param {boolean} force - Apakah force overwrite
   */
  static initGlobalTestDataForce(force) {
    const filePath = path.join(process.cwd(), 'test-data.json');

    if (fs.existsSync(filePath) && !force) {
      console.log('test-data.json already exists. Use --force to overwrite.');
      return false;
    }

    fs.writeFileSync(filePath, JSON.stringify(this.GLOBAL_TEMPLATE, null, 2), 'utf8');
    console.log('Created: test-data.json');
    return true;
  }

  /**
   * Init per-endpoint dengan force mode
   * @param {string} endpointName
   * @param {Object} payload
   * @param {boolean} force
   */
  static initEndpointTestDataForce(endpointName, payload, force) {
    const dirPath = path.join(process.cwd(), 'tests', 'data');
    const filePath = path.join(dirPath, `${endpointName}.test-data.json`);

    if (fs.existsSync(filePath) && !force) {
      console.log(`${endpointName}.test-data.json already exists. Use --force to overwrite.`);
      return false;
    }

    if (!fs.existsSync(dirPath)) {
      fs.mkdirSync(dirPath, { recursive: true });
    }

    const template = this._buildEndpointTemplate(endpointName, payload);
    fs.writeFileSync(filePath, JSON.stringify(template, null, 2), 'utf8');
    console.log(`Created: tests/data/${endpointName}.test-data.json`);
    return true;
  }

  // ─── Resolve Methods ───────────────────────────────────────────────────────

  /**
   * Resolve nilai test untuk satu field, cek 4 lapis prioritas
   * @param {string} fieldName - Nama field
   * @param {string|null} fieldType - Tipe field dari fieldValidation (null jika tidak diketahui)
   * @param {Object|null} endpointData - Per-endpoint test data ({ create, update })
   * @param {{ fieldDefaults: Object, typeDefaults: Object }} globalData - Global test data
   * @returns {{ value: *, source: string }} value dan source resolusi
   */
  static resolveFieldValue(fieldName, fieldType, endpointData, globalData) {
    // Lapis 1: Per-endpoint test data
    if (endpointData && endpointData.create && endpointData.create[fieldName] !== undefined) {
      return {
        value: endpointData.create[fieldName],
        source: 'endpoint'
      };
    }

    // Lapis 2: Global fieldDefaults
    if (globalData.fieldDefaults && globalData.fieldDefaults[fieldName]) {
      const fieldDef = globalData.fieldDefaults[fieldName];
      const values = fieldDef.values || [fieldDef];
      return {
        value: Array.isArray(values) ? values[0] : values,
        source: 'fieldDefaults'
      };
    }

    // Lapis 3: Global typeDefaults
    if (fieldType && globalData.typeDefaults && globalData.typeDefaults[fieldType]) {
      const typeValues = globalData.typeDefaults[fieldType];
      return {
        value: Array.isArray(typeValues) ? typeValues[0] : typeValues,
        source: 'typeDefaults'
      };
    }

    // Lapis 4: Built-in type defaults
    if (fieldType && this.BUILTIN_TYPE_DEFAULTS[fieldType]) {
      return {
        value: this.BUILTIN_TYPE_DEFAULTS[fieldType][0],
        source: 'builtin'
      };
    }

    // Fallback absolut: string default
    return {
      value: 'test-value',
      source: 'builtin'
    };
  }

  /**
   * Resolve nilai update untuk satu field
   * @param {string} fieldName
   * @param {string|null} fieldType
   * @param {Object|null} endpointData
   * @param {*} createValue - Nilai yang dipakai di create (untuk variasi)
   * @returns {{ value: *, source: string }}
   */
  static resolveUpdateFieldValue(fieldName, fieldType, endpointData, createValue) {
    // Lapis 1: Per-endpoint update data
    if (endpointData && endpointData.update && endpointData.update[fieldName] !== undefined) {
      return {
        value: endpointData.update[fieldName],
        source: 'endpoint'
      };
    }

    // Fallback: variasi otomatis dari create value
    return {
      value: this._generateUpdateVariant(createValue, fieldType),
      source: 'auto'
    };
  }

  /**
   * Resolve semua field sekaligus untuk create dan update payload
   * @param {string[]} fieldNames - Daftar nama field
   * @param {Array|undefined} fieldValidation - Array fieldValidation dari payload
   * @param {Object|null} endpointData - Per-endpoint test data
   * @param {{ fieldDefaults: Object, typeDefaults: Object }} globalData - Global test data
   * @param {Object} payload - Full payload object (untuk cek autoGenerate, primaryKey)
   * @returns {{ create: Object, update: Object, warnings: string[] }}
   */
  static resolveAllFields(fieldNames, fieldValidation, endpointData, globalData, payload) {
    const create = {};
    const update = {};
    const warnings = [];

    // Build type map dari fieldValidation
    const typeMap = this._buildTypeMap(fieldValidation);

    // Build constraints map
    const constraintsMap = this._buildConstraintsMap(fieldValidation);

    const primaryKey = payload.primaryKey || null;

    for (const fieldName of fieldNames) {
      const constraints = constraintsMap.get(fieldName) || {};
      const fieldType = typeMap.get(fieldName) || null;

      // Skip field autoGenerate (UUID, serial, dll)
      if (constraints.autoGenerate) {
        continue;
      }

      // Skip primaryKey di create (server yang generate)
      if (fieldName === primaryKey && constraints.primaryKey) {
        continue;
      }

      // Resolve create value
      const createResult = this.resolveFieldValue(fieldName, fieldType, endpointData, globalData);
      create[fieldName] = createResult.value;

      // Log source
      if (createResult.source === 'builtin') {
        const typeInfo = fieldType ? `/${fieldType}` : '';
        warnings.push(`${fieldName} → not in fieldDefaults, using ${createResult.source}${typeInfo} defaults`);
      }

      // Resolve update value (skip code field, biasanya unique dan tidak diupdate)
      const isCodeField = fieldName.toLowerCase().endsWith('_code') || fieldName.toLowerCase() === 'code';
      if (!isCodeField) {
        const updateResult = this.resolveUpdateFieldValue(fieldName, fieldType, endpointData, createResult.value);
        update[fieldName] = updateResult.value;
      }
    }

    return { create, update, warnings };
  }

  // ─── Private Helpers ───────────────────────────────────────────────────────

  /**
   * Build type map dari fieldValidation array
   * @param {Array|undefined} fieldValidation
   * @returns {Map<string, string>} fieldName -> type
   */
  static _buildTypeMap(fieldValidation) {
    const map = new Map();
    if (!Array.isArray(fieldValidation)) return map;

    for (const field of fieldValidation) {
      if (field.name && field.type) {
        map.set(field.name, field.type);
      }
    }
    return map;
  }

  /**
   * Build constraints map dari fieldValidation array
   * @param {Array|undefined} fieldValidation
   * @returns {Map<string, Object>} fieldName -> constraints
   */
  static _buildConstraintsMap(fieldValidation) {
    const map = new Map();
    if (!Array.isArray(fieldValidation)) return map;

    for (const field of fieldValidation) {
      if (field.name) {
        map.set(field.name, field.constraints || {});
      }
    }
    return map;
  }

  /**
   * Build per-endpoint template dari payload
   * @param {string} endpointName
   * @param {Object} payload
   * @returns {Object}
   */
  static _buildEndpointTemplate(endpointName, payload) {
    const create = {};
    const update = {};
    const fieldNames = payload.fieldName || [];
    const typeMap = this._buildTypeMap(payload.fieldValidation);
    const constraintsMap = this._buildConstraintsMap(payload.fieldValidation);
    const primaryKey = payload.primaryKey || null;

    for (const fieldName of fieldNames) {
      const constraints = constraintsMap.get(fieldName) || {};

      // Skip autoGenerate / primaryKey
      if (constraints.autoGenerate || (fieldName === primaryKey && constraints.primaryKey)) {
        continue;
      }

      const fieldType = typeMap.get(fieldName) || 'string';

      // Code field: pakai placeholder timestamp
      const isCodeField = fieldName.toLowerCase().endsWith('_code') || fieldName.toLowerCase() === 'code';
      if (isCodeField) {
        create[fieldName] = 'TEST-INT-{TS}';
        continue;
      }

      // Generate template value berdasarkan type
      create[fieldName] = this._getTemplateValue(fieldType, constraints);

      // Update: variasi dari create
      update[fieldName] = this._getTemplateUpdateValue(fieldType, constraints, create[fieldName]);
    }

    return { create, update };
  }

  /**
   * Generate template value untuk per-endpoint init
   * @param {string} fieldType
   * @param {Object} constraints
   * @returns {*}
   */
  static _getTemplateValue(fieldType, constraints) {
    // Enum
    if (Array.isArray(constraints.enum) && constraints.enum.length > 0) {
      return constraints.enum[0];
    }

    // Default value
    if (constraints.default !== undefined) {
      return constraints.default;
    }

    switch (fieldType) {
      case 'boolean': return true;
      case 'integer': return constraints.min !== undefined ? Math.max(constraints.min, 1) : 1;
      case 'decimal':
      case 'number': return constraints.precision ? parseFloat((100.50).toFixed(constraints.precision)) : 100.50;
      case 'date': return '2026-01-01';
      case 'datetime':
      case 'timestamp': return '2026-01-01T00:00:00.000Z';
      case 'uuid': return 'sample-uuid-replace-with-real-id';
      case 'json': return {};
      case 'array': return [];
      default: return 'test-value';
    }
  }

  /**
   * Generate template update value (variasi dari create)
   * @param {string} fieldType
   * @param {Object} constraints
   * @param {*} createValue
   * @returns {*}
   */
  static _getTemplateUpdateValue(fieldType, constraints, createValue) {
    // Enum: ambil index kedua jika ada
    if (Array.isArray(constraints.enum) && constraints.enum.length > 1) {
      return constraints.enum[1];
    }

    return this._generateUpdateVariant(createValue, fieldType);
  }

  /**
   * Generate variasi otomatis dari create value untuk update
   * @param {*} createValue
   * @param {string|null} fieldType
   * @returns {*}
   */
  static _generateUpdateVariant(createValue, fieldType) {
    if (typeof createValue === 'boolean') {
      return !createValue;
    }

    if (typeof createValue === 'number') {
      return createValue + 1;
    }

    if (typeof createValue === 'string') {
      // Date-like string: jangan ubah
      if (/^\d{4}-\d{2}-\d{2}/.test(createValue)) {
        return '2026-06-15';
      }
      return createValue + ' Updated';
    }

    return createValue;
  }
}

module.exports = TestDataManager;
