package me.qwq.doghouse.controller.admin;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.yulichang.wrapper.MPJLambdaWrapper;

import jodd.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import me.qwq.doghouse.config.DoghouseProperties;
import me.qwq.doghouse.controller.common.BaseController;
import me.qwq.doghouse.entity.Asset;
import me.qwq.doghouse.entity.config.SiteConfig;
import me.qwq.doghouse.entity.Post;
import me.qwq.doghouse.enums.FileTypeEnum;
import me.qwq.doghouse.exception.RException;
import me.qwq.doghouse.pojo.dto.LayPageReq;
import me.qwq.doghouse.pojo.dto.LayPageResp;
import me.qwq.doghouse.pojo.dto.R;
import me.qwq.doghouse.service.AssetService;
import me.qwq.doghouse.util.DateUtils;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.sqids.Sqids;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import javax.imageio.ImageIO;


/**
 * 资产控制器
 * @author Doghole
 */
@Controller
@RequestMapping("/admin/v2/asset")
@Slf4j
public class AssetController extends BaseController {
    
    final private static SecureRandom SECURE_RANDOM = new SecureRandom();
    
    final private static Sqids SQIDS = Sqids.builder().build();
    
    @Autowired
    DoghouseProperties doghouseProperties;
    
    @Autowired
    AssetService assetService;
    
    @Autowired
    SiteConfig siteConfig;

    /**
     * 主页
     * @return
     */
    @GetMapping({"", "/", "/index"})
    String index() {
        return "admin/asset/index";
    }
    
    @ResponseBody
    @GetMapping("/list")
    public LayPageResp<?> getAssetList(LayPageReq<Asset> pageReq) {
        
        MPJLambdaWrapper<Asset> ew = new MPJLambdaWrapper<Asset>()
                .selectAll(Asset.class)
                .leftJoin(Post.class, Post::getPostId, Asset::getFromPostId)
                .orderByDesc(Asset::getCreateTime);
        
        IPage<Asset> page = assetService.page(pageReq, ew);
        
        LayPageResp<Asset> result = new LayPageResp<>(page);
        return result;
    }
    
    
    /**
     * 上传文件
     */
    @ResponseBody
    @PostMapping("/uploadFile")
    R<?> uploadFile(
            @RequestParam(name = "file") MultipartFile file,
            @RequestParam(name = "postId", required = false) Long postId) {
        
        String msg = "文件上传失败";
        String suffix = getSuffix(file);
        
        if (suffix == null) {
            return R.badRequest(msg);
        }
        
        String prefix = generateFilePrefix();
        String filename = prefix + '.' + suffix;
        String originalFilename = file.getOriginalFilename();
        String originalPrefix = originalFilename.substring(0, originalFilename.length() -
                suffix.length() - 1);
        

        Calendar calendar = Calendar.getInstance();
        String yearAndMonth = String.format("%d/%02d", calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1);
        Path filePlacedDirPath = Paths.get(doghouseProperties.getFileUploadPath(), yearAndMonth);
        
        File dir = filePlacedDirPath.toFile();
        File destFile = filePlacedDirPath.resolve(filename).toFile();
        Asset asset = new Asset();
        try {
            if (!dir.exists() && !dir.mkdirs()) {
                msg = "创建文件夹 " + dir + " 失败";
                log.error(msg);
                return R.internalServerError(msg);
            }

            // file.transferTo(destFile); // 解决 jetty 上传文件问题
            FileUtil.writeBytes(destFile, file.getBytes());
            String fileUrl = 
                    Paths.get(doghouseProperties.getFileSqlPath(), yearAndMonth, filename)
                    .toString().replace('\\', '/');
            
            FileTypeEnum fileType = FileTypeEnum.getFileType(suffix);
            
            asset
                .setFromPostId(postId)
                .setTitle(file.getOriginalFilename())
                .setCreateTime(LocalDateTime.now())
                .setFileType(fileType)
                .setFileSize(destFile.length())
                .setUpdateTime(LocalDateTime.now())
                .setPath(fileUrl);
            
            if (fileType == FileTypeEnum.IMAGE) {
                BufferedImage image = ImageIO.read(destFile);
                if (image != null) {
                    asset.setImageHeight(image.getHeight())
                         .setImageWidth(image.getWidth());
                    
                    if (siteConfig.getImageCompress()) {
                        
                        File avifFile = getAvif(suffix, destFile.toString(), prefix, filePlacedDirPath);
                        File webpFile = getWebp(suffix, destFile.toString(), prefix, filePlacedDirPath);

                        File finalFile = null;
                        String finalSuffix = null;
                        
                        if (webpFile != null && avifFile != null) {
                            if (webpFile.length() >= avifFile.length()) {
                                finalFile = webpFile;
                                finalSuffix = ".webp";
                                avifFile.delete();
                            }
                            else {
                                finalFile = avifFile;
                                finalSuffix = ".avif";
                                webpFile.delete();
                            }
                        }
                        else if (webpFile == null && avifFile != null) {
                            finalFile = avifFile;
                            finalSuffix = ".avif";
                        }
                        else if (webpFile != null && avifFile == null) {
                            finalFile = webpFile;
                            finalSuffix = ".webp";
                        }
                        
                        if (finalFile != null) {
                            
                            fileUrl = Paths.get(doghouseProperties.getFileSqlPath(), yearAndMonth, finalFile.getName())
                                    .toString().replace('\\', '/');
                            
                            asset
                            .setPath(fileUrl)
                            .setFileSize(finalFile.length())
                            .setTitle(originalPrefix + finalSuffix);
                            
                            destFile.delete();
                        }
                    }
                }
                else {
                    log.warn("Unsupported image format {}", suffix);
                }
            }
            if (assetService.save(asset)) {
                return R.ok(asset);
            }
            throw RException.badRequest("文件上传成功但保存失败");
            
        } catch (IOException e) {
            log.error(msg, e);
        }
        return R.internalServerError(msg);
    }

    /**
     * 更新文件基本信息
     * @param asset
     * @return
     */
    @ResponseBody
    @PostMapping("/update")
    R<?> update(Asset asset) {
        if (Objects.isNull(asset) || Objects.isNull(asset.getAssetId())) {
            throw RException.badRequest("提供的资产不能为空！");
        }
        LambdaUpdateWrapper<Asset> uw = new LambdaUpdateWrapper<>();
        uw.eq(Asset::getAssetId, asset.getAssetId())
          .set(Asset::getTitle, asset.getTitle())
          .set(Asset::getDescription, asset.getDescription());
        return R.judge(assetService.update(uw), "更新资产信息失败！");
    }
    
    /**
     * 删除资产
     * @param assetId
     * @return
     */
    @ResponseBody
    @PostMapping("/delete")
    R<?> delete(Long assetId) {
        if (Objects.isNull(assetId)) {
            throw RException.badRequest("提供的资产不能为空！");
        }
        Asset asset = assetService.getById(assetId);
        if (Objects.isNull(asset)) {
            log.info("试图删除已不存在于数据库内的资产");
            return R.ok();
        }
        String uploadPath = doghouseProperties.getFileUploadPath(asset);
        File file = new File(uploadPath);
        if (file.exists()) {
            return R.judge(file.delete() && assetService.removeById(assetId), "删除文件失败");
        }
        return R.judge(assetService.removeById(assetId), "删除文件失败");
    }
    

    /**
     * 解绑资产
     * @param assetId
     * @return
     */
    @ResponseBody
    @PostMapping("/unlink")
    R<?> unlink(Long assetId) {
        if (Objects.isNull(assetId)) {
            throw RException.badRequest("提供的资产不能为空！");
        }
        Asset asset = assetService.getById(assetId);
        if (Objects.isNull(asset)) {
            log.info("试图操作已不存在于数据库内的资产");
            return R.ok();
        }
        if (Objects.isNull(asset.getFromPostId())) {
            return R.ok("该资产并未绑定 Post");
        }

        return R.judge(
                assetService.update(new LambdaUpdateWrapper<Asset>()
                    .eq(Asset::getAssetId, asset.getAssetId())
                    .set(Asset::getFromPostId, null)), "解绑失败");
    }
    
    /**
     * 获取文件后缀，不包含".", 返回小写
     */
    public static String getSuffix(MultipartFile file){
        if (file == null) {
            return null;
        }
        String fileName = file.getOriginalFilename();
        if (StringUtils.isEmpty(fileName)) {
            return null;
        }
        int lastIndexOfDot = fileName.lastIndexOf('.');
        if (lastIndexOfDot == -1 || lastIndexOfDot == fileName.length() - 1) {
            return "";
        }
        return fileName.substring(lastIndexOfDot + 1).toLowerCase();
    }
    
    /**
     * 使用随机 Long + Sqids 作为文件名
     * @param file
     * @return
     */
    public static String generateFilePrefix() {
        String dateStr = DateUtils.format(LocalDateTime.now(), "yyyyMMdd_HHmmssSSS_");
        
        StringBuilder sb = new StringBuilder();
        sb.append(dateStr);
        
        List<Long> list = new ArrayList<>();
        list.add(Math.abs(SECURE_RANDOM.nextLong()));
        
        String id = SQIDS.encode(list);
        sb.append(id);
        return sb.toString();
    }
    
    private static File getAvif(String suffix,
            String originalFilePath, String finalPrefix, Path filePlacedDirPath) {
        
        if (!"jpg".equals(suffix) && !"jpeg".equals(suffix) && !"png".equals(suffix)) {
            return null;
        }
        
        String avifFilename = finalPrefix + ".avif";
        String avifPath = filePlacedDirPath.resolve(avifFilename).toString();
        List<String> avifCommand = List.of(
                "avifenc", 
                "-q", "60", 
                "--qalpha", "80", 
                "-s", "6",
                "-j", "all", 
                originalFilePath, 
                avifPath);
        
        return executeFile(avifCommand, avifPath);
    }
    

    private static File getWebp(String suffix,
            String originalFilePath, String finalPrefix, Path filePlacedDirPath) {


        if (!"jpg".equals(suffix) && !"jpeg".equals(suffix) && !"png".equals(suffix) && !"gif".equals(suffix)) {
            return null;
        }
        
        String webpFilename = finalPrefix + ".webp";
        String webpPath = filePlacedDirPath.resolve(webpFilename).toString();
        List<String> webpCommand = 
            !"gif".equals(suffix) ? 
                    List.of("cwebp", 
                            "-z", "9", 
                            "-q", "100", 
                            "-near_lossless", "60", 
                            "-m", "6", 
                            "-mt", originalFilePath,
                            "-o", webpPath):
                    List.of("gif2webp", 
                            "-q", "90", 
                            "-lossy", "-min_size", 
                            "-m", "0", 
                            "-mt", originalFilePath,
                            "-o", webpPath);
        return executeFile(webpCommand, webpPath);
    }
    
    private static File executeFile(List<String> command, String filePath) {
        ProcessBuilder pb = new ProcessBuilder(command);
        pb.redirectErrorStream(true);
        try {
            Process process = pb.start();
            
            try (var reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.debug("[img-encode] {}", line);
                }
            }

            int exitCode = process.waitFor(15, TimeUnit.SECONDS) ? process.exitValue() : -1;
            
            if (exitCode == 0) {
                File out = FileUtil.file(filePath);
                if (!out.exists()) {
                    log.warn("Command finished with code 0, but output file not found: {}", filePath);
                    return null;
                }
                return out;
            }
            log.warn("Execution failed with exit code: {}, command {}", exitCode, command);
            return null;
        }
        catch (IOException | InterruptedException e) {
            log.warn("Cannot execute command {}, {}", command, e);
            return null;
        }
    }
}
