import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Any, FindConditions, IsNull, Repository } from 'typeorm';
import { WorkspaceEntity } from '../entities/workspace.entity';
import { AccessTag, ProjectAccess } from '../utils/project-access';
import {
  ProjectImportArchiveAsset,
  ProjectImportArchiveAssetBlock,
  ProjectImportArchiveWorkspace,
  ProjectImportAssetFromSelector,
} from './dto/project-import.dto';
import { ApiError } from '../common/error/api-error';
import { ApiErrorCodes } from '../common/error/api-error-codes';
import * as AdmZip from 'adm-zip';
import { AssetEntity } from '../entities/asset.entity';
import { decodeBigNumberKey } from '../utils/big-number-key';
import { RefService } from '../asset/ref.service';
import { AssetScopeFromId, AssetScopeToId } from '../constants';
import { AssetBlockEntity } from '../entities/asset-block.entity';
import { AssetSelectorService } from '../asset/asset-selector.service';
import { ExportProjectDTO } from './dto/project-dto';

@Injectable()
export class ProjectExportService {
  constructor(
    @InjectRepository(WorkspaceEntity)
    private readonly workspaceRepo: Repository<WorkspaceEntity>,
    @InjectRepository(AssetEntity)
    private readonly assetRepo: Repository<AssetEntity>,
    @InjectRepository(AssetBlockEntity)
    private readonly assetBlockRepo: Repository<AssetBlockEntity>,
    private readonly refService: RefService,
    private readonly assetSelectorService: AssetSelectorService,
  ) {}

  async exportProject(params: ExportProjectDTO, projectAccess: ProjectAccess) {
    if (!projectAccess.accessTags.has(AccessTag.ROOT)) {
      if (!projectAccess.projectLicense?.features?.exportProject) {
        throw new ApiError(
          "User hasn't PRO license",
          ApiErrorCodes.ACCESS_DENIED,
        );
      }
      if (!projectAccess.userRole || !projectAccess.userRole.isAdmin) {
        throw new ApiError(
          `Access denied. Only leader can export project`,
          ApiErrorCodes.ACCESS_DENIED,
        );
      }
    }
    const zip = new AdmZip();

    const params_where = params.where ? JSON.parse(params.where) : {};

    const db_assets: ProjectImportAssetFromSelector[] =
      (await this.assetSelectorService.getView(
        {
          where: {
            ...params_where,
            isSystem: false,
          },
          select: [
            'id',
            'isabstract',
            'name',
            'parentids',
            'workspaceid',
            'scope',
            'ownicon',
            'owntitle',
            'index',
          ],
        },
        projectAccess,
      )) as any[];
    const db_asset_ids: string[] = db_assets.map((a) => a.id as string);
    const db_blocks = await this.assetBlockRepo
      .createQueryBuilder('b')
      .innerJoin(
        'asset_block_keys',
        'bk',
        'b.block_key = bk.block_key AND bk.project_id = :project_id',
        {
          project_id: decodeBigNumberKey(projectAccess.projectId),
        },
      )
      .where('b.asset_id = ANY(:asset_ids)', {
        asset_ids: db_asset_ids,
      })
      .select([
        'b.block_key as block_key',
        'b.asset_id as asset_id',
        'b.title as title',
        'bk.block_name as name',
        'bk.block_title as block_title',
        'bk.block_type as type',
        'b.props as props',
        'b.index as index',
      ])
      .getRawMany();
    const all_refs = await this.refService.getShortsByAssetIds(
      db_asset_ids,
      projectAccess,
    );

    for (const db_asset of db_assets) {
      const asset: ProjectImportArchiveAsset = {
        id: db_asset.id,
        isAbstract: db_asset.isabstract,
        name: db_asset.name,
        parentIds: db_asset.parentids
          ? db_asset.parentids.map((index) => db_asset['parentids\\' + index])
          : [],
        workspaceId: db_asset.workspaceid,
        scope: db_asset.scope as any,
        ownIcon: db_asset.ownicon,
        ownTitle: db_asset.owntitle,
        index: db_asset.index,
        blocks: db_blocks
          .filter((bl) => bl.asset_id === db_asset.id)
          .map((bl) => {
            const block: ProjectImportArchiveAssetBlock = {
              name: bl.name,
              title: bl.block_title,
              ownTitle: bl.title,
              type: bl.type,
              props: { ...bl.props },
              index: bl.index,
            };
            return block;
          }),
        references: all_refs.get(db_asset.id) ?? [],
      };
      zip.addFile(
        'assets/' + db_asset.id + '.json',
        Buffer.from(JSON.stringify(asset), 'utf8'),
      );
    }
    const db_workspaces_conditions: FindConditions<WorkspaceEntity> = {
      projectId: projectAccess.projectId,
      deletedAt: IsNull(),
    };
    if (params_where.scope) {
      if (Array.isArray(params_where.scope)) {
        db_workspaces_conditions.scopeId = Any(
          params_where.scope.map((scope) => AssetScopeToId.get(scope) ?? 0),
        );
      } else {
        db_workspaces_conditions.scopeId =
          AssetScopeToId.get(params_where.scope) ?? 0;
      }
    }
    const db_workspaces = await this.workspaceRepo.find(
      db_workspaces_conditions,
    );
    for (const db_workspace of db_workspaces) {
      const workspace: ProjectImportArchiveWorkspace = {
        id: db_workspace.id,
        parentId: db_workspace.parentId,
        title: db_workspace.title,
        index: db_workspace.index,
        scope: AssetScopeFromId.get(db_workspace.scopeId),
      };
      zip.addFile(
        'workspaces/' + db_workspace.id + '.json',
        Buffer.from(JSON.stringify(workspace), 'utf8'),
      );
    }

    // add file directly
    zip.addFile(
      'index.json',
      Buffer.from(
        JSON.stringify({
          ims: 'creators',
          version: '1.0.0',
          projectId: projectAccess.projectId,
          exportedAt: new Date(),
        }),
        'utf8',
      ),
    );

    // get everything as a buffer
    const willSendthis = zip.toBuffer();
    return willSendthis as Buffer;
  }
}
