package cn.lihongjie.coal.file.service;

import cn.hutool.core.io.IoUtil;
import cn.hutool.http.HttpUtil;
import cn.lihongjie.coal.base.dto.CommonQuery;
import cn.lihongjie.coal.base.dto.IdRequest;
import cn.lihongjie.coal.base.service.BaseService;
import cn.lihongjie.coal.common.Ctx;
import cn.lihongjie.coal.exception.BizException;
import cn.lihongjie.coal.file.dto.CreateFileDto;
import cn.lihongjie.coal.file.dto.FileDto;
import cn.lihongjie.coal.file.dto.UpdateFileDto;
import cn.lihongjie.coal.file.entity.FileEntity;
import cn.lihongjie.coal.file.mapper.FileMapper;
import cn.lihongjie.coal.file.repository.FileRepository;
import cn.lihongjie.coal.spring.config.AliyunProperty;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PutObjectResult;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tika.Tika;
import org.apache.tika.config.TikaConfig;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Base64;
import java.util.UUID;

@Service
@Slf4j
@Transactional
public class FileService extends BaseService<FileEntity, FileRepository> {

    @Autowired AliyunProperty aliyunProperty;

    @Autowired OSSClient ossClient;

    @Autowired private FileRepository repository;
    @Autowired private FileMapper mapper;
    @Autowired private ConversionService conversionService;
    private final Tika tika = new Tika();
    TikaConfig config = TikaConfig.getDefaultConfig();

    @Value("${spring.profiles.active}")
    private String activateProfile;

    public FileDto create(CreateFileDto request) {
        FileEntity entity = mapper.toEntity(request);

        this.repository.save(entity);
        return getById(entity.getId());
    }

    public FileDto update(UpdateFileDto request) {
        FileEntity entity = this.repository.get(request.getId());
        this.mapper.updateEntity(entity, request);

        this.repository.save(entity);

        return getById(entity.getId());
    }

    public void delete(IdRequest request) {
        this.repository.deleteAllById(request.getIds());
    }

    public FileDto getById(String id) {
        FileEntity entity = repository.get(id);

        return mapper.toDto(entity);
    }

    public Page<FileDto> list(CommonQuery query) {
        Page<FileEntity> page =
                repository.findAll(
                        query.specification(conversionService),
                        PageRequest.of(
                                query.getPageNo(),
                                query.getPageSize(),
                                Sort.by(query.getOrders())));

        return page.map(this.mapper::toDto);
    }

    @Autowired ObjectMapper objectMapper;

    @NotNull
    private static String getObjectKey(FileEntity fileEntity) {
        return fileEntity.getDirectory() + "/" + fileEntity.getObjectId();
    }

    @SneakyThrows
    public FileDto upload(
            MultipartFile file,
            String name,
            String dir,
            String sortKey,
            String code,
            String remarks,
            String url,
            String base64) {

        long size = 0;
        InputStream fis = null;
        if (file != null) {

            size = file.getSize();
            fis = new ByteArrayInputStream(IoUtil.readBytes(file.getInputStream()));
        } else if (StringUtils.isNotEmpty(url)) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            HttpUtil.download(url, out, true);
            fis = new ByteArrayInputStream(out.toByteArray());
            size = out.size();
        } else if (StringUtils.isNotBlank(base64)) {

            fis =
                    new ByteArrayInputStream(
                            Base64.getDecoder()
                                    .decode(base64.replace("data:image/png;base64,", "")));
            size = fis.available();

        } else {
            throw new BizException("文件为空");
        }

        FileEntity fileEntity = new FileEntity();
        fileEntity.setFileName(name);
        fileEntity.setFileSize(size);
        fileEntity.setCode(code);
        fileEntity.setRemarks(remarks);
        try {

            fileEntity.setSortKey(Integer.parseInt(sortKey));
        } catch (Exception e) {

            fileEntity.setSortKey(1);
        }
        fileEntity.setDirectory(
                "%s/%s/%s"
                        .formatted(
                                activateProfile,
                                Ctx.currentUser().getOrganizationId(),
                                StringUtils.defaultIfBlank(dir, "public")));
        InputStream inputStream = fis;
        String extension = FilenameUtils.getExtension(name.replace(":", "_"));

        try (inputStream) {
            fileEntity.setMimeType(tika.detect(inputStream));
        }

        // if file ext is empty, get from mime type

        try {

            if (StringUtils.isBlank(extension)) {
                extension =
                        config.getMimeRepository()
                                .getRegisteredMimeType(fileEntity.getMimeType())
                                .getExtension().replace(".", "");
            }
        } catch (Exception e) {
            log.warn("获取文件扩展名失败", e);
        }

        fileEntity.setObjectId(UUID.randomUUID() + "." + extension);

        String objectKey = getObjectKey(fileEntity);

        fileEntity.setPublicUrl(uploadToOSS(objectKey, fis));

        this.save(fileEntity);

        return getById(fileEntity.getId());
    }

    private String uploadToOSS(String objectKey, InputStream inputStream) {
        String objectUrl;
        try {

            PutObjectResult objectResult =
                    ossClient.putObject(
                            aliyunProperty.getOSS().getBucketName(), objectKey, inputStream);
        } catch (Exception e) {

            log.warn("上传文件失败", e);
            throw new BizException("上传文件失败 %s".formatted(e.getMessage()));
        }

        AliyunProperty.OSSRegion ossRegion =
                aliyunProperty.getOSS().getRegions().stream()
                        .filter(
                                x ->
                                        x.getCode()
                                                .equalsIgnoreCase(
                                                        aliyunProperty.getOSS().getRegion()))
                        .findFirst()
                        .orElseThrow(() -> new RuntimeException("region not found"));

        objectUrl =
                "https://%s.%s/%s"
                        .formatted(
                                aliyunProperty.getOSS().getBucketName(),
                                ossRegion.getEndpoint(),
                                objectKey);
        return objectUrl;
    }

    @SneakyThrows
    public FileDto upload(
            InputStream file,
            String name,
            String dir) {
        return upload(file, name, dir, null, null, null);
    }

    @SneakyThrows
    public FileDto upload(
            InputStream file,
            String name,
            String dir,
            String sortKey,
            String code,
            String remarks) {
        FileEntity fileEntity = new FileEntity();
        fileEntity.setFileName(name);
        fileEntity.setFileSize(Long.valueOf(file.available()));
        fileEntity.setCode(code);
        fileEntity.setRemarks(remarks);
        try {

            fileEntity.setSortKey(Integer.parseInt(sortKey));
        } catch (Exception e) {

            fileEntity.setSortKey(1);
        }
        fileEntity.setDirectory(
                "%s/%s/%s"
                        .formatted(
                                activateProfile,
                                Ctx.currentUser().getOrganizationId(),
                                StringUtils.defaultIfBlank(dir, "public")));
        InputStream inputStream = file;
        fileEntity.setObjectId(
                UUID.randomUUID() + "." + FilenameUtils.getExtension(name.replace(":", "_")));
        try (inputStream) {
            fileEntity.setMimeType(tika.detect(inputStream));
        }

        String objectKey = getObjectKey(fileEntity);

        fileEntity.setPublicUrl(uploadToOSS(objectKey, file));

        this.save(fileEntity);

        return getById(fileEntity.getId());
    }

    public InputStream download(String fileId) {

        FileEntity fileEntity = repository.get(fileId);
        String objectKey = getObjectKey(fileEntity);
        try {
            OSSObject object =
                    ossClient.getObject(aliyunProperty.getOSS().getBucketName(), objectKey);
            return object.getObjectContent();
        } catch (Exception e) {
            log.warn("下载文件失败", e);
            throw new BizException("下载文件失败 %s".formatted(e.getMessage()));
        }
    }
}
