const { driverBase } = global.DBGATE_PACKAGES['dbgate-tools'];
const { convertToMongoCondition, convertToMongoSort } = require('./convertToMongoCondition');
const Dumper = require('./Dumper');
const { mongoSplitterOptions } = require('dbgate-query-splitter/lib/options');
const _pickBy = require('lodash/pickBy');
const _fromPairs = require('lodash/fromPairs');

function mongoReplacer(key, value) {
  if (typeof value === 'bigint') {
    return { $bigint: value.toString() };
  }
  return value;
}

function jsonStringifyWithObjectId(obj) {
  return JSON.stringify(obj, mongoReplacer, 2)
    .replace(/\{\s*\"\$oid\"\s*\:\s*\"([0-9a-f]+)\"\s*\}/g, (m, id) => `ObjectId("${id}")`)
    .replace(/\{\s*\"\$bigint\"\s*\:\s*\"([0-9]+)\"\s*\}/g, (m, num) => `${num}n`);
}

/** @type {import('dbgate-types').SqlDialect} */
const dialect = {
  limitSelect: true,
  rangeSelect: true,
  offsetFetchRangeSyntax: true,
  stringEscapeChar: "'",
  fallbackDataType: 'nvarchar(max)',
  quoteIdentifier(s) {
    return `[${s}]`;
  },
};

/** @type {import('dbgate-types').EngineDriver} */
const driver = {
  ...driverBase,
  dumperClass: Dumper,
  databaseEngineTypes: ['document'],
  dialect,
  engine: 'mongo@dbgate-plugin-mongo',
  title: 'MongoDB',
  editorMode: 'javascript',
  defaultPort: 27017,
  supportsDatabaseUrl: true,
  supportsServerSummary: true,
  // temporatily disable MongoDB profiler support
  supportsDatabaseProfiler: false,
  profilerFormatterFunction: 'formatProfilerEntry@dbgate-plugin-mongo',
  profilerTimestampFunction: 'extractProfileTimestamp@dbgate-plugin-mongo',
  profilerChartAggregateFunction: 'aggregateProfileChartEntry@dbgate-plugin-mongo',
  profilerChartMeasures: [
    { label: 'Req count/s', field: 'countPerSec' },
    { label: 'Avg duration', field: 'avgDuration' },
    { label: 'Max duration', field: 'maxDuration' },
  ],
  databaseUrlPlaceholder: 'e.g. mongodb://username:password@mongodb.mydomain.net/dbname',
  collectionSingularLabel: 'collection',
  collectionPluralLabel: 'collections',

  getQuerySplitterOptions: () => mongoSplitterOptions,

  showConnectionField: (field, values) => {
    if (field == 'useDatabaseUrl') return true;
    if (values.useDatabaseUrl) {
      return ['databaseUrl', 'defaultDatabase', 'singleDatabase', 'isReadOnly'].includes(field);
    }
    return ['server', 'port', 'user', 'password', 'defaultDatabase', 'singleDatabase', 'isReadOnly'].includes(field);
  },

  importExportArgs: [
    {
      type: 'checkbox',
      name: 'createStringId',
      label: 'Create string _id attribute',
      apiName: 'createStringId',
      direction: 'target',
    },
  ],

  newCollectionFormParams: [
    {
      type: 'text',
      label: 'Collection name',
      name: 'name',
      focused: true,
    },
  ],

  getCollectionUpdateScript(changeSet, collectionInfo) {
    let res = '';
    for (const insert of changeSet.inserts) {
      res += `db.${insert.pureName}.insertOne(${jsonStringifyWithObjectId({
        ...insert.document,
        ...insert.fields,
      })});\n`;
    }
    for (const update of changeSet.updates) {
      if (update.document) {
        res += `db.${update.pureName}.replaceOne(${jsonStringifyWithObjectId(
          update.condition
        )}, ${jsonStringifyWithObjectId({
          ...update.document,
          ...update.fields,
        })});\n`;
      } else {
        const set = _pickBy(update.fields, (v, k) => v !== undefined);
        const unset = _fromPairs(
          Object.keys(update.fields)
            .filter((k) => update.fields[k] === undefined)
            .map((k) => [k, ''])
        );
        const updates = {};
        if (!_.isEmpty(set)) updates.$set = set;
        if (!_.isEmpty(unset)) updates.$unset = unset;

        res += `db.${update.pureName}.updateOne(${jsonStringifyWithObjectId(
          update.condition
        )}, ${jsonStringifyWithObjectId(updates)});\n`;
      }
    }
    for (const del of changeSet.deletes) {
      res += `db.${del.pureName}.deleteOne(${jsonStringifyWithObjectId(del.condition)});\n`;
    }
    return res;
  },

  getFilterBehaviour(dataType, standardFilterBehaviours) {
    return standardFilterBehaviours.mongoFilterBehaviour;
  },

  getCollectionExportQueryScript(collection, condition, sort) {
    return `db.collection('${collection}')
  .find(${JSON.stringify(convertToMongoCondition(condition) || {})})
  .sort(${JSON.stringify(convertToMongoSort(sort) || {})})`;
  },
  getCollectionExportQueryJson(collection, condition, sort) {
    return {
      collection,
      condition: convertToMongoCondition(condition) || {},
      sort: convertToMongoSort(sort) || {},
    };
  },

  dataEditorTypesBehaviour: {
    parseJsonNull: true,
    parseJsonBoolean: true,
    parseNumber: true,
    parseJsonArray: true,
    parseJsonObject: true,
    parseObjectIdAsDollar: true,
    parseDateAsDollar: true,

    explicitDataType: true,
    supportNumberType: true,
    supportStringType: true,
    supportBooleanType: true,
    supportDateType: true,
    supportJsonType: true,
    supportObjectIdType: true,
    supportNullType: true,

    supportFieldRemoval: true,
  },

  getScriptTemplates(objectTypeField) {
    switch (objectTypeField) {
      case 'collections':
        return [
          {
            label: 'JS: dropCollection()',
            scriptTemplate: 'dropCollection',
          },
          {
            label: 'JS: find()',
            scriptTemplate: 'findCollection',
          },
        ];
    }

    return [];
  },

  async getScriptTemplateContent(scriptTemplate, props) {
    switch (scriptTemplate) {
      case 'dropCollection':
        return `db.${props.pureName}.drop();`;
      case 'findCollection':
        return `db.${props.pureName}.find();`;
    }
  },
};

module.exports = driver;
