const CloudStore = require('./cloudstore');
const QueryUtils = require('./queryutils');
const Model = require('../model');

function createRecordsAPI(modelScope) {
  let _cloudstore;
  const cloudstore = () => {
    // We must create the cloud store just in time so all meta data is loaded
    if (!_cloudstore) _cloudstore = new CloudStore(modelScope);
    return _cloudstore;
  };

  return {
    async query(className, query, options) {
      return new Promise((resolve, reject) => {
        cloudstore().query({
          collection: className,
          where: QueryUtils.convertFilterOp(query || {}, {
            collectionName: className,
            error: (e) => reject(e),
            modelScope
          }),
          limit: options ? options.limit : undefined,
          sort: options ? options.sort : undefined,
          skip: options ? options.skip : undefined,
          include: options ? options.include : undefined,
          select: options ? options.select : undefined,
          count: options ? options.count : undefined,
          success: (results,count) => {
            const _results = results.map((r) => cloudstore()._fromJSON(r, className));
            if(count !== undefined)  resolve({results:_results,count});
            else resolve(_results);
          },
          error: (err) => {
            reject(Error(err || 'Failed to query.'));
          }
        });
      });
    },

    async count(className, query) {
      return new Promise((resolve, reject) => {
        cloudstore().count({
          collection: className,
          where: query
            ? QueryUtils.convertFilterOp(query || {}, {
                collectionName: className,
                error: (e) => reject(e),
                modelScope
              })
            : undefined,
          success: (count) => {
            resolve(count);
          },
          error: (err) => {
            reject(Error(err || 'Failed to query.'));
          }
        });
      });
    },

    async distinct(className, property, query) {
      return new Promise((resolve, reject) => {
        cloudstore().distinct({
          collection: className,
          property,
          where: query
            ? QueryUtils.convertFilterOp(query || {}, {
                collectionName: className,
                error: (e) => reject(e),
                modelScope
              })
            : undefined,
          success: (results) => {
            resolve(results);
          },
          error: (err) => {
            reject(Error(err || 'Failed to query.'));
          }
        });
      });
    },

    async aggregate(className, group, query) {
      return new Promise((resolve, reject) => {
        cloudstore().aggregate({
          collection: className,
          group,
          where: query
            ? QueryUtils.convertFilterOp(query || {}, {
                collectionName: className,
                error: (e) => reject(e),
                modelScope
              })
            : undefined,
          success: (results) => {
            resolve(results);
          },
          error: (err) => {
            reject(Error(err || 'Failed to aggregate.'));
          }
        });
      });
    },

    async fetch(objectOrId, options) {
      if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
      const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;

      return new Promise((resolve, reject) => {
        if (!className) return reject('No class name specified');

        cloudstore().fetch({
          collection: className,
          objectId: objectOrId,
          include: options ? options.include : undefined,
          success: function (response) {
            var record = cloudstore()._fromJSON(response, className);
            resolve(record);
          },
          error: function (err) {
            reject(Error(err || 'Failed to fetch.'));
          }
        });
      });
    },

    async increment(objectOrId, properties, options) {
      if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
      const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;

      return new Promise((resolve, reject) => {
        if (!className) return reject('No class name specified');

        cloudstore().increment({
          collection: className,
          objectId: objectOrId,
          properties,
          success: (response) => {
            cloudstore()._fromJSON(Object.assign({ objectId: objectOrId }, response), className); // Update values

            resolve();
          },
          error: (err) => {
            reject(Error(err || 'Failed to increment.'));
          }
        });
      });
    },

    async save(objectOrId, properties, options) {
      if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
      const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;

      const model = (modelScope || Model).get(objectOrId);
      if (properties) {
        Object.keys(properties).forEach((p) => {
          model.set(p, properties[p]);
        });
      }

      return new Promise((resolve, reject) => {
        if (!className) return reject('No class name specified');

        cloudstore().save({
          collection: className,
          objectId: objectOrId,
          data: properties || model.data,
          acl: options ? options.acl : undefined,
          success: (response) => {
            cloudstore()._fromJSON(Object.assign({ objectId: objectOrId }, response), className); // Assign updated at
            resolve();
          },
          error: (err) => {
            reject(Error(err || 'Failed to save.'));
          }
        });
      });
    },

    async create(className, properties, options) {
      return new Promise((resolve, reject) => {
        cloudstore().create({
          collection: className,
          data: properties,
          acl: options ? options.acl : undefined,
          success: (data) => {
            // Successfully created
            const m = cloudstore()._fromJSON(data, className);
            resolve(m);
          },
          error: (err) => {
            reject(Error(err || 'Failed to insert.'));
          }
        });
      });
    },

    async delete(objectOrId, options) {
      if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
      const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;

      return new Promise((resolve, reject) => {
        if (!className) return reject('No class name specified');

        cloudstore().delete({
          collection: className,
          objectId: objectOrId,
          success: () => {
            (modelScope || Model).get(objectOrId).notify('delete');
            resolve();
          },
          error: (err) => {
            reject(Error(err || 'Failed to delete.'));
          }
        });
      });
    },

    async addRelation(options) {
      const recordId = options.recordId || options.record.getId();
      const className = options.className || (modelScope || Model).get(recordId)._class;

      const targetRecordId = options.targetRecordId || options.targetRecord.getId();
      const targetClassName = options.targetClassName || (modelScope || Model).get(targetRecordId)._class;

      return new Promise((resolve, reject) => {
        if (!className) return reject('No class name specified');
        if (!targetClassName) return reject('No target class name specified');

        cloudstore().addRelation({
          collection: className,
          objectId: recordId,
          key: options.key,
          targetObjectId: targetRecordId,
          targetClass: targetClassName,
          success: (response) => {
            resolve();
          },
          error: (err) => {
            reject(Error(err || 'Failed to add relation.'));
          }
        });
      });
    },

    async removeRelation(options) {
      const recordId = options.recordId || options.record.getId();
      const className = options.className || (modelScope || Model).get(recordId)._class;

      const targetRecordId = options.targetRecordId || options.targetRecord.getId();
      const targetClassName = options.targetClassName || (modelScope || Model).get(targetRecordId)._class;

      return new Promise((resolve, reject) => {
        if (!className) return reject('No class name specified');
        if (!targetClassName) return reject('No target class name specified');

        cloudstore().removeRelation({
          collection: className,
          objectId: recordId,
          key: options.key,
          targetObjectId: targetRecordId,
          targetClass: targetClassName,
          success: (response) => {
            resolve();
          },
          error: (err) => {
            reject(Error(rr || 'Failed to add relation.'));
          }
        });
      });
    }
  };
}

module.exports = createRecordsAPI;
