import assert from 'assert';
import { Op } from 'sequelize';
import db from '@/db/models';
import services from '@/services/file';
import ValidationError from '@/shared/errors/validation';
import type { DbApiOptions, FileInput, FileRelation } from '@/db/api/types';

class FileDBApi {
  static async replaceRelationFiles(
    relation: FileRelation,
    rawFiles: FileInput | FileInput[] | null | undefined,
    options?: DbApiOptions,
  ): Promise<void> {
    assert(relation.belongsTo, 'belongsTo is required');
    assert(relation.belongsToColumn, 'belongsToColumn is required');
    assert(relation.belongsToId, 'belongsToId is required');

    const files: FileInput[] = Array.isArray(rawFiles)
      ? rawFiles
      : rawFiles
        ? [rawFiles]
        : [];

    await this._removeLegacyFiles(relation, files, options);
    await this._addFiles(relation, files, options);
  }

  static async _addFiles(
    relation: FileRelation,
    files: FileInput[],
    options?: DbApiOptions,
  ): Promise<void> {
    const transaction = options?.transaction;
    const currentUser = options?.currentUser ?? { id: null };

    const inexistentFiles = files.filter((file) => !!file.new);

    for (const file of inexistentFiles) {
      if (!file.name || !file.publicUrl) {
        throw new ValidationError('iam.errors.fileNameRequired');
      }

      await db.file.create(
        {
          belongsTo: relation.belongsTo,
          belongsToColumn: relation.belongsToColumn,
          belongsToId: relation.belongsToId,
          name: file.name,
          sizeInBytes: file.sizeInBytes ?? null,
          privateUrl: file.privateUrl ?? null,
          publicUrl: file.publicUrl,
          createdById: currentUser.id,
          updatedById: currentUser.id,
        },
        { transaction },
      );
    }
  }

  static async _removeLegacyFiles(
    relation: FileRelation,
    files: FileInput[],
    options?: DbApiOptions,
  ): Promise<void> {
    const transaction = options?.transaction;

    const filesToDelete = await db.file.findAll({
      where: {
        belongsTo: relation.belongsTo,
        belongsToId: relation.belongsToId,
        belongsToColumn: relation.belongsToColumn,
        id: {
          [Op.notIn]: files
            .filter((file) => !file.new)
            .map((file) => file.id)
            .filter((id): id is string => Boolean(id)),
        },
      },
      transaction,
    });

    for (const file of filesToDelete) {
      if (file.privateUrl) {
        await services.deleteGCloud(file.privateUrl);
      }
      await file.destroy({ transaction });
    }
  }
}

export default FileDBApi;
