package com.ffii.core.common.file.service;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.validation.Valid;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.tika.Tika;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import com.ffii.core.exception.NotFoundException;
import com.ffii.core.support.AbstractService;
import com.ffii.core.support.JdbcDao;
import com.ffii.core.common.file.dao.FileBlobDao;
import com.ffii.core.common.file.dao.FileDao;
import com.ffii.core.common.file.dao.FileRefDao;
import com.ffii.core.common.file.entity.File;
import com.ffii.core.common.file.entity.FileBlob;
import com.ffii.core.common.file.entity.FileRef;
import com.ffii.core.common.file.web.FileController.UpdateFileInfoReq;

/** @author Fung */
@Service
public class FileService extends AbstractService {
  private FileDao dao;
  private FileRefDao refDao;
  private FileBlobDao blobDao;

  public FileService(JdbcDao jdbcDao, FileDao dao, FileRefDao refDao, FileBlobDao blobDao) {
    super(jdbcDao);
    this.dao = dao;
    this.refDao = refDao;
    this.blobDao = blobDao;
  }

  @Transactional(rollbackFor = Exception.class)
  public Map<String, Object> upload(Integer refId, String refType, String refCode, MultipartFile multipartFile,
      String remarks)
      throws IOException {
    Assert.notNull(refType, "refType cannot null");
    Assert.notNull(refId, "refId cannot null");
    Assert.notNull(multipartFile, "multipartFile cannot null");

    String filename = multipartFile.getOriginalFilename();

    File file = new File();
    file.setSkey(RandomStringUtils.randomAlphanumeric(32));
    file.setFilename(FilenameUtils.getBaseName(filename));
    file.setExtension(FilenameUtils.getExtension(filename));
    file.setMimetype(new Tika().detect(filename));
    file.setFilesize(multipartFile.getSize());
    dao.save(file);

    blobDao.save(new FileBlob(file.getId(), multipartFile.getBytes()));
    refDao.save(new FileRef(refType, refId, refCode, file.getId()));
    return Map.of(
        "id", file.getId(),
        "refId", refId == null ? "" : refId,
        "refType", refType == null ? "" : refType,
        "refCode", refCode == null ? "" : refCode,
        "skey", file.getSkey(),
        "filename", file.getFilename(),
        "extension", file.getExtension(),
        "filesize", file.getFilesize(),
        "mimetype", file.getMimetype());
  }

  @Transactional(rollbackFor = Exception.class)
  public Map<String, Object> upload(Integer refId, String refType, String refCode, MultipartFile multipartFile,
      String filename,
      String remarks)
      throws IOException {
    Assert.notNull(refType, "refType cannot null");
    Assert.notNull(refId, "refId cannot null");
    Assert.notNull(multipartFile, "multipartFile cannot null");

    File file = new File();
    file.setSkey(RandomStringUtils.randomAlphanumeric(32));
    file.setFilename(FilenameUtils.getBaseName(filename));
    file.setExtension(FilenameUtils.getExtension(filename));
    file.setMimetype(new Tika().detect(filename));
    file.setFilesize(multipartFile.getSize());
    dao.save(file);

    blobDao.save(new FileBlob(file.getId(), multipartFile.getBytes()));
    refDao.save(new FileRef(refType, refId, refCode, file.getId()));
    return Map.of(
        "id", file.getId(),
        "refId", refId == null ? "" : refId,
        "refType", refType == null ? "" : refType,
        "refCode", refCode == null ? "" : refCode,
        "skey", file.getSkey(),
        "filename", file.getFilename(),
        "extension", file.getExtension(),
        "filesize", file.getFilesize(),
        "mimetype", file.getMimetype());
  }

  @Transactional(rollbackFor = Exception.class)
  public boolean delete(int id, String skey) {
    if (dao.deleteByIdAndSkey(id, skey) > 0) {
      blobDao.deleteByFileId(id);
      refDao.deleteByFileId(id);
      return true;
    } else {
      return false;
    }
  }

  public Pair<File, FileBlob> loadFile(int id, String skey, String filename) {
    Optional<File> opt = dao.findByIdAndSkeyAndFilenameAndExtension(id, skey,
        FilenameUtils.getBaseName(filename),
        FilenameUtils.getExtension(filename));
    return opt.isPresent() ? ImmutablePair.of(opt.get(), blobDao.findByFileId(id).orElse(null))
        : ImmutablePair.nullPair();
  }

  // #App
  public List<Map<String, Object>> list(String refType, int refId) {
    return jdbcDao.queryForList(
        "SELECT"
            + " ref.fileId,"
            + " ref.refType,"
            + " ref.refId,"
            + " ref.refCode,"
            + " f.skey,"
            + " f.filename,"
            + " f.extension,"
            + " f.mimetype,"
            + " f.filesize,"
            + " f.remarks,"
            + " f.created,"
            + " f.createdBy,"
            + " u.name AS createdByName"
            + " FROM file_ref ref"
            + " INNER JOIN file f ON f.id = ref.fileId"
            + " LEFT JOIN user u ON u.username = f.createdBy"
            + " WHERE ref.refType = :refType"
            + " AND ref.refId = :refId",
        Map.of("refType", refType, "refId", refId));
  }

  @Transactional(rollbackFor = Exception.class)
  public void updateFileInfo(int id, String skey, @Valid UpdateFileInfoReq req) {
    File file = dao.findByIdAndSkey(id, skey).orElseThrow(NotFoundException::new);
    BeanUtils.copyProperties(req, file);
    dao.save(file);
  }

  public Optional<FileRef> findRefByFileId(int fileId) {
    return refDao.findByFileId(fileId);
  }

  @Transactional(rollbackFor = Exception.class)
  public FileRef saveRef(FileRef ref) {
    return refDao.save(ref);
  }

}
