/**
 * This file is part of the NocoBase (R) project.
 * Copyright (c) 2020-2024 NocoBase Co., Ltd.
 * Authors: NocoBase Team.
 *
 * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
 * For more information, please refer to: https://www.nocobase.com/agreement.
 */

import { Context, Next } from '@nocobase/actions';
import { SQLCollection, SQLModel } from '../sql-collection';

const updateCollection = async (ctx: Context, transaction: any) => {
  const { filterByTk, values } = ctx.action.params;
  const repo = ctx.db.getRepository('collections');
  const collection = await repo.findOne({
    filter: {
      name: filterByTk,
    },
    transaction,
  });
  const existFields = await collection.getFields({ transaction });
  const deletedFields = existFields.filter((field: any) => !values.fields?.find((f: any) => f.name === field.name));
  for (const field of deletedFields) {
    await field.destroy({ transaction });
  }
  const upRes = await repo.update({
    filterByTk,
    values,
    updateAssociationValues: ['fields'],
    transaction,
  });

  return { collection, upRes };
};

export default {
  name: 'sqlCollection',
  actions: {
    execute: async (ctx: Context, next: Next) => {
      let { sql } = ctx.action.params.values || {};
      if (!sql) {
        ctx.throw(400, ctx.t('Please enter a SQL statement'));
      }
      sql = sql.trim().split(';').shift();
      if (!/^select/i.test(sql) && !/^with([\s\S]+)select([\s\S]+)/i.test(sql)) {
        ctx.throw(400, ctx.t('Only supports SELECT statements or WITH clauses'));
      }
      const tmpCollection = new SQLCollection({ name: 'tmp', sql }, { database: ctx.db });
      const model = tmpCollection.model as typeof SQLModel;
      // The result is for preview only, add limit clause to avoid too many results
      const data = await model.findAll({ attributes: ['*'], limit: 5, raw: true });
      let fields: {
        [field: string]: {
          type: string;
          source: string;
          collection: string;
          interface: string;
        };
      } = {};
      try {
        fields = model.inferFields();
      } catch (err) {
        ctx.logger.warn(`resource: sql-collection, action: execute, error: ${err}`);
        fields = {};
      }
      const sources = Array.from(
        new Set(
          Object.values(fields)
            .map((field) => field.collection)
            .filter((c) => c),
        ),
      );
      ctx.body = { data, fields, sources };
      await next();
    },
    setFields: async (ctx: Context, next: Next) => {
      const transaction = await ctx.app.db.sequelize.transaction();
      try {
        const {
          upRes: [collection],
        } = await updateCollection(ctx, transaction);
        await collection.loadFields({
          transaction,
        });
        await transaction.commit();
      } catch (e) {
        await transaction.rollback();
        throw e;
      }
      await next();
    },
    update: async (ctx: Context, next: Next) => {
      const transaction = await ctx.app.db.sequelize.transaction();
      try {
        const { upRes } = await updateCollection(ctx, transaction);
        const [collection] = upRes;
        await collection.load({ transaction, resetFields: true });
        await transaction.commit();
        ctx.body = upRes;
      } catch (e) {
        await transaction.rollback();
        throw e;
      }
      await next();
    },
  },
};
