import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import {
  createWriteStream,
  existsSync,
  mkdirSync,
  readFileSync,
  writeFileSync,
} from 'fs';
import { join } from 'path';
import { PageInfoEntity } from 'src/common/entities/page-info.entity';
import env from 'src/env';
import { Md5 } from 'ts-md5';
import { Repository } from 'typeorm';
import { Resource } from '../entities/resource.entity';
import { User } from 'src/system/entities/user.entity';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const officeToPDF = require('office-to-pdf');

export const ACCEPT_MAP = {
  image: 'image/*',
  audio: 'audio/*',
  video: 'video/*',

  ppt: 'application/vnd.ms-powerpoint',
  pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  xls: 'application/vnd.ms-excel',
  xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  pdf: 'application/pdf',
  doc: 'application/msword',
  docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
};

@Injectable()
// 定义一个资源服务类
export class ResourceService {
  // 设置资源路径和转换任务数组
  RES_PATH = env.staticAssets.resource.path;
  // 设置资源转换任务数组
  CONVER_TASKS = [];

  // 构造函数
  constructor(
    // 注入资源仓库
    @InjectRepository(Resource)
    private resRepository: Repository<Resource>,
  ) {
    // 创建资源路径目录
    mkdirSync(this.RES_PATH, { recursive: true });
  }

  /**
   * 定义一个创建资源的方法，接受一个资源数组作为参数
   * @param resource 资源数组
   * @returns
   */
  async create(resource: Resource[]) {
    // 调用仓库的保存方法，返回结果
    return this.resRepository.save(resource);
  }

  /**
   * 定义一个创建文件夹的方法，接受一个资源对象作为参数
   * @param resource 资源对象
   * @returns
   */
  async createFolder(resource: Resource) {
    // 查询是否存在同名同路径的文件夹，如果存在则抛出异常
    const res = await this.resRepository.findOneBy({
      delFlag: 0,
      type: resource.type,
      name: resource.name,
      path: resource.path,
    });
    if (res) throw '该文件夹已存在';
    // 调用仓库的保存方法，返回结果
    return this.resRepository.save(resource);
  }

  /**
   * 定义一个查询所有资源的方法，接受分页信息和查询条件作为参数
   *
   * @param pageInfo 分页信息
   * @param query 查询条件
   * @returns
   */
  async findAll(pageInfo: PageInfoEntity, query) {
    // 使用查询构建器来构建查询语句，并选择需要的字段
    const [list, total] = await this.resRepository
      .createQueryBuilder('res')

      .leftJoinAndMapOne('res.userinfo', User, 'user', 'res.creator = user.id')
      .select([
        'res.id',
        'res.name',
        'res.ext',
        'res.type',
        'res.mimetype',
        'res.size',
        'res.path',
        'res.diskname',
        'res.preview',
        'res.classifyId',
        'res.classifyIds',
        'res.createTime',
        'res.creator',
        'res.share',
        'user.name',
      ])
      .leftJoinAndMapOne('res.user', User, 'u', 'u.id = res.creator')
      .where(
        () =>
          [
            ' res.path=:path and res.del_flag=0 ',
            query.isManager || query.share == 1
              ? ''
              : ' and res.creator=:creator ',
            query.share == 1 ? ' and res.share=1' : '',
            ` and res.type in ('${['folder', ...Object.keys(ACCEPT_MAP)].join(
              "','",
            )}') `,
            query.likeName ? ' and res.name like :likeName ' : '',
            query.classifyId ? ' and res.classifyId=:classifyId ' : '',
          ].join(''),
        { ...query, likeName: `%${query.likeName}%` },
      )
      .orderBy(
        Object.assign(
          {},
          query.sortBySize && { 'res.size': query.sortBySize },
          query.sortByCreateTime && {
            'res.createTime': query.sortByCreateTime,
          },
          { 'res.id': 'DESC' },
        ) as any,
      )
      .skip(pageInfo.start)
      .take(pageInfo.size)
      .getManyAndCount();

    // 返回列表和总数对象
    return { list, total };
  }

  /**
   * 根据id查找资源
   *
   * @param id 资源ID
   * @returns
   */
  find(id: number) {
    return this.resRepository.findOneBy({ id });
  }

  /**
   * 查找所有文件夹
   *
   * @param creator 创建人
   * @returns
   */
  async findAllFolder(creator) {
    const data = await this.resRepository.find({
      where: { delFlag: 0, type: 'folder', creator },
      select: ['name', 'path'],
    });

    // 如果没有文件夹则抛出异常
    if (!data.length) throw '暂未创建任何文件夹，创建后再试';

    return data;
  }

  /**
   * 根据id更新资源
   *
   * @param id 资源ID
   * @param data 资源
   * @returns
   */
  update(id: number, data) {
    return this.resRepository.update(id, data);
  }

  /**
   * 根据ids批量更新分类
   *
   * @param data
   * @returns
   */
  updateClassify({ ids, ...data }) {
    return this.resRepository.update(ids, data);
  }

  /**
   * 根据ids批量更新路径
   *
   * @param data
   * @returns
   */
  updatePath({ ids, ...data }) {
    return this.resRepository.update(ids, data);
  }

  /**
   * 根据ids批量删除资源
   *
   * @param ids
   * @returns
   */
  async remove(ids: number[]) {
    await this.resRepository.update(ids, { delFlag: 1 });
    return true;
  }

  /**
   * 根据accept获取对应的类型
   *
   * @param accept accept 名
   * @returns
   */
  getAcceptType(accept) {
    if (!accept) return;

    for (const type in ACCEPT_MAP) {
      if (accept.startsWith(ACCEPT_MAP[type].replace(/\*$/, ''))) return type;
    }
  }

  async saveFile(file: Express.Multer.File): Promise<Resource> {
    // 获取文件名和文件后缀
    const [, name, ext] = Buffer.from(file.originalname, 'latin1')
      .toString('utf8')
      .match(/(.+)\.(.+)/);

    // 获取文件 mimetype 类型
    const acceptType = this.getAcceptType(file.mimetype);

    if (!acceptType) throw '暂不支持该文件类型';

    // 生成【资源】数据对象
    const resource = {
      name,
      ext,
      diskname: Md5.hashStr(file.buffer.toString()) + '.' + ext.toLowerCase(),
      type: acceptType,
      mimetype: file.mimetype,
      size: file.size,
    } as Resource;

    // 如果是 ['image', 'audio', 'video', 'pdf'] 的类型文件
    // 预览地址和磁盘名称一致
    // 其他文件需要调用 fileConver 来生成预览地址
    if (['image', 'audio', 'video', 'pdf'].includes(acceptType))
      resource.preview = resource.diskname;

    // 处理完整的磁盘存储路径
    const path = join(this.RES_PATH, resource.diskname);

    // 如果该路径不存在，则创建文件
    if (!existsSync(path)) {
      const ws = createWriteStream(path);
      ws.write(file.buffer);
    }

    return resource;
  }

  /**
   * 获取单个资源信息接口
   *
   * @param id 资源ID
   * @returns
   */
  findOneById(id: number) {
    return this.resRepository.findOneBy({ id });
  }

  /**
   * 用于文件转换的方法
   *
   * @param id 文件id
   * @param converUser 转换操作人
   * @returns
   */
  async fileConver(id: number, converUser: string) {
    const taskIndex = this.CONVER_TASKS.indexOf(id);
    if (taskIndex !== -1) throw '文件正在加载中，请稍后';
    this.CONVER_TASKS.push(id);

    return this.resRepository.manager.transaction(async (entityManager) => {
      const res = await this.findOneById(id);

      const originPath = join(this.RES_PATH, res.diskname);
      const preview = res.diskname + '.pdf';
      const previewPath = join(this.RES_PATH, preview);
      if (res.preview !== preview) {
        if (!existsSync(previewPath))
          try {
            await this.converToPDF(originPath, previewPath);

            await entityManager.update(Resource, id, {
              preview,
              updater: converUser,
            });
          } catch (error) {
            console.error(error);
            throw '文件转换失败';
          } finally {
            this.CONVER_TASKS.splice(taskIndex, 1);
          }
      }

      return preview;
    });
  }

  /**
   * 用于文件转换的方法
   *
   * @param originPath 源地址
   * @param savePath 保存地址
   */
  async converToPDF(originPath: string, savePath: string) {
    const buf = await officeToPDF(readFileSync(originPath));
    writeFileSync(savePath, buf);
  }
}
