/* eslint-disable */
const fs = require('fs');
const path = require('path');
const SequelizePkg = require('sequelize');
const { Sequelize } = SequelizePkg;

// Dummy Sequelize instance (no DB connection required to inspect model metadata)
const sequelize = new Sequelize('db', 'user', 'pass', {
  dialect: 'mysql',
  logging: false,
});

const MODELS_DIR = path.join(__dirname, '..', 'models');
const MIGRATIONS_DIR = path.join(__dirname, '..', 'migrations');

function ensureDir(p) {
  if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true });
}

function pad2(n) {
  return String(n).padStart(2, '0');
}

function migrationTimestamp(index) {
  // Create stable pseudo-timestamps for file ordering
  const base = new Date(Date.UTC(2026, 1, 9, 0, 0, 0)); // 2026-02-09
  base.setUTCSeconds(base.getUTCSeconds() + index);
  const y = base.getUTCFullYear();
  const m = pad2(base.getUTCMonth() + 1);
  const d = pad2(base.getUTCDate());
  const hh = pad2(base.getUTCHours());
  const mm = pad2(base.getUTCMinutes());
  const ss = pad2(base.getUTCSeconds());
  return `${y}${m}${d}${hh}${mm}${ss}`;
}

function typeToCode(typeObj) {
  const key = typeObj?.key || typeObj?.constructor?.key || typeObj?.constructor?.name;
  const opts = typeObj?.options || {};

  switch (key) {
    case 'UUID':
      return 'Sequelize.UUID';
    case 'STRING':
      return opts.length ? `Sequelize.STRING(${opts.length})` : 'Sequelize.STRING';
    case 'TEXT':
      return 'Sequelize.TEXT';
    case 'BOOLEAN':
      return 'Sequelize.BOOLEAN';
    case 'INTEGER':
      return 'Sequelize.INTEGER';
    case 'BIGINT':
      return 'Sequelize.BIGINT';
    case 'DATE':
      return 'Sequelize.DATE';
    case 'DATEONLY':
      return 'Sequelize.DATEONLY';
    case 'DECIMAL':
      if (opts.precision && opts.scale != null) return `Sequelize.DECIMAL(${opts.precision}, ${opts.scale})`;
      if (opts.precision) return `Sequelize.DECIMAL(${opts.precision})`;
      return 'Sequelize.DECIMAL';
    case 'FLOAT':
      if (opts.precision && opts.scale != null) return `Sequelize.FLOAT(${opts.precision}, ${opts.scale})`;
      return 'Sequelize.FLOAT';
    case 'DOUBLE':
      return 'Sequelize.DOUBLE';
    case 'ENUM':
      return `Sequelize.ENUM(${(typeObj.values || []).map(v => JSON.stringify(v)).join(', ')})`;
    case 'JSON':
      return 'Sequelize.JSON';
    default:
      return key ? `Sequelize.${key}` : 'Sequelize.STRING';
  }
}

function defaultValueToCode(def) {
  if (def === undefined) return null;

  // Sequelize literals
  if (def && def.key === 'UUIDV4') return 'Sequelize.UUIDV4';
  if (def && def.key === 'NOW') return 'Sequelize.NOW';

  // Some UUIDV4 defaults come through as a function marker
  if (typeof def === 'function') return 'Sequelize.UUIDV4';

  if (def instanceof Date) return `new Date(${JSON.stringify(def.toISOString())})`;
  if (typeof def === 'string') return JSON.stringify(def);
  if (typeof def === 'number' || typeof def === 'boolean') return JSON.stringify(def);
  if (Array.isArray(def) || (def && typeof def === 'object')) return JSON.stringify(def);

  return null;
}

function attrToCode(attr, colName) {
  const parts = [];
  parts.push(`type: ${typeToCode(attr.type)}`);

  if (attr.allowNull === false) parts.push('allowNull: false');
  if (attr.primaryKey) parts.push('primaryKey: true');
  if (attr.unique) parts.push('unique: true');
  if (attr.autoIncrement) parts.push('autoIncrement: true');

  const defCode = defaultValueToCode(attr.defaultValue);
  if (defCode) parts.push(`defaultValue: ${defCode}`);

  // keep field if model uses a different attribute name
  if (attr.field && attr.field !== colName) parts.push(`field: ${JSON.stringify(attr.field)}`);

  if (attr.references && attr.references.model) {
    const ref = {
      model: attr.references.model,
      key: attr.references.key || 'id',
    };
    parts.push(`references: ${JSON.stringify(ref)}`);
    if (attr.onUpdate) parts.push(`onUpdate: ${JSON.stringify(attr.onUpdate)}`);
    if (attr.onDelete) parts.push(`onDelete: ${JSON.stringify(attr.onDelete)}`);
  }

  return `{ ${parts.join(', ')} }`;
}

function buildTableColumns(model) {
  const cols = {};

  // Use actual DB column names
  for (const [attrName, attr] of Object.entries(model.rawAttributes)) {
    const col = attr.field || attrName;
    cols[col] = { ...attr, field: col };
  }

  const opts = model.options || {};

  if (opts.timestamps) {
    const created = opts.underscored ? 'created_at' : 'createdAt';
    const updated = opts.underscored ? 'updated_at' : 'updatedAt';

    if (!cols[created]) {
      cols[created] = {
        type: SequelizePkg.DataTypes.DATE,
        allowNull: false,
        defaultValue: SequelizePkg.DataTypes.NOW,
        field: created,
      };
    }
    if (!cols[updated]) {
      cols[updated] = {
        type: SequelizePkg.DataTypes.DATE,
        allowNull: false,
        defaultValue: SequelizePkg.DataTypes.NOW,
        field: updated,
      };
    }
  }

  if (opts.paranoid) {
    const deleted = opts.underscored ? 'deleted_at' : 'deletedAt';
    if (!cols[deleted]) {
      cols[deleted] = {
        type: SequelizePkg.DataTypes.DATE,
        allowNull: true,
        field: deleted,
      };
    }
  }

  return cols;
}

function getIndexes(model) {
  const indexes = (model.options && model.options.indexes) ? model.options.indexes : [];
  return indexes.map((idx) => {
    const fields = (idx.fields || []).map(f => {
      if (typeof f === 'string') return f;
      return f?.name || f?.attribute || f;
    });
    return {
      fields,
      unique: !!idx.unique,
      name: idx.name || undefined,
    };
  });
}

function writeMigrationFile(index, model) {
  const tableName = model.getTableName();
  const cols = buildTableColumns(model);
  const indexes = getIndexes(model);

  const colsCode = Object.entries(cols).map(([col, attr]) => {
    return `      ${JSON.stringify(col)}: ${attrToCode(attr, col)}`;
  }).join(',\n');

  const addIndexLines = indexes.map((idx) => {
    const opts = {};
    if (idx.unique) opts.unique = true;
    if (idx.name) opts.name = idx.name;
    const optsCode = Object.keys(opts).length ? `, ${JSON.stringify(opts)}` : '';
    return `    await queryInterface.addIndex(${JSON.stringify(tableName)}, ${JSON.stringify(idx.fields)}${optsCode});`;
  }).join('\n');

  const fileName = `${migrationTimestamp(index)}-create-${tableName}.js`;
  const content = `/* eslint-disable */\n'use strict';\n\nmodule.exports = {\n  up: async (queryInterface, Sequelize) => {\n    await queryInterface.createTable(${JSON.stringify(tableName)}, {\n${colsCode}\n    }, {\n      charset: 'utf8mb4',\n      collate: 'utf8mb4_unicode_ci',\n      engine: 'InnoDB',\n    });\n${addIndexLines ? addIndexLines + '\n' : ''}  },\n\n  down: async (queryInterface) => {\n    await queryInterface.dropTable(${JSON.stringify(tableName)});\n  },\n};\n`;

  fs.writeFileSync(path.join(MIGRATIONS_DIR, fileName), content, 'utf8');
}

function loadModels() {
  const files = fs.readdirSync(MODELS_DIR)
    .filter(f => f.endsWith('.js'))
    .sort();

  for (const file of files) {
    const modelFactory = require(path.join(MODELS_DIR, file));
    if (typeof modelFactory === 'function') modelFactory(sequelize);
  }

  return Object.values(sequelize.models);
}

function main() {
  ensureDir(MIGRATIONS_DIR);

  const models = loadModels();
  // deterministic order by table name
  models.sort((a, b) => String(a.getTableName()).localeCompare(String(b.getTableName())));

  models.forEach((m, i) => writeMigrationFile(i + 1, m));

  console.log(`Generated ${models.length} migration files in ${MIGRATIONS_DIR}`);
}

main();
