package com.cclotus.cms.service

import com.cclotus.cms.vo.FileInfo
import com.ruoyi.common.config.RuoYiConfig
import com.ruoyi.common.constant.Constants
import com.ruoyi.common.exception.ServiceException
import org.springframework.stereotype.Service
import java.io.File
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneOffset

/**
 * 文件管理服务
 *
 */
@Service
class FileManagerService {


    // 媒体文件夹位置
    private val MEDIA_DIR = "/media"

    /**
     * 浏览指定文件夹
     *
     * @param path 文件夹路径
     * @return 指定文件夹下的所有内容
     */
    fun browseDirectory(path: String?): List<FileInfo> {
        return browse(path ?: "", 1, false)
    }

    /**
     * 获取目录结构树
     *
     * @param path
     * @return
     */
    fun directoryTree(path: String?): List<FileInfo> {
        return browse(path ?: "", null, true)
    }

    /**
     * 打开指定媒体文件夹
     *
     * @param path 与媒体文件夹的相对路径，以'/'分隔
     * @param maxDepth 最大打开深度
     * @param onlyDirectory 是否只返回目录信息
     * @return 文件及目录信息列表
     */
    fun browse(path: String, maxDepth: Int?, onlyDirectory: Boolean): List<FileInfo> {
        val rootPath = getMediaPath()
        val fileTreeWalk = File("$rootPath$path").let {
            if (!it.exists()) throw ServiceException("指定文件夹不存在")
            if (!it.isDirectory) throw ServiceException("打开的不是文件夹")
            maxDepth?.run {
                it.walkTopDown().maxDepth(this)
            } ?: it.walkTopDown()
        }
        val fileInfos = fileTreeWalk.filter {
            !(onlyDirectory && it.isFile)
        }.map {
            FileInfo().apply {
                this.isDirectory = it.isDirectory
                this.path = path.ifBlank { "/" }
                this.name = it.name
                this.url = if (it.isFile) {
                    if (this.path!!.last() == '/') Constants.RESOURCE_PREFIX + MEDIA_DIR + this.path + it.name
                    Constants.RESOURCE_PREFIX + MEDIA_DIR + this.path + "/" + it.name
                } else null
                this.updateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(it.lastModified()), ZoneOffset.of("+8"))
                this.size = if (it.isFile) it.length() * 1.0 / 1000 else null
                this.isEmpty = it.list()?.isEmpty()
            }
        }.toMutableList()
        // 移除根目录信息
        if (fileInfos.isNotEmpty()) fileInfos.removeAt(0)
        return fileInfos
    }

    /**
     * 创建目录
     *
     * @param path 源目录
     * @param directoryName 创建目录名
     * @return 创建结果
     */
    fun createDirectory(path: String?, directoryName: String) {
        val directory = getFile(path, directoryName)
        if (directory.exists()) throw ServiceException("当前文件夹已存在")
        if (!directory.mkdir()) throw ServiceException("目录创建失败")
    }


    /**
     * 修改文件名
     *
     * @param path 文件所在路径
     * @param originName 文件原始名称
     * @param newName 新文件名
     */
    fun changeFileName(path: String?, originName: String, newName: String) {
        // 如果新旧文件名相同，直接返回
        if (originName == newName) return
        val newFile = getFile(path, newName)
        if (newFile.exists()) throw ServiceException("文件名已存在")
        val originFile = getFile(path, originName)
        val result = originFile.renameTo(newFile)
        if (!result) throw ServiceException("重命名失败")
    }

    /**
     * 删除文件或文件夹
     *
     * @param path 文件路径
     * @param name 文件名
     * @param mandatory 是否强制删除，默认为否，为true则文件夹下有内容也会删除
     */
    fun delete(path: String?, name: String, mandatory: Boolean = false) {
        val file = getFile(path, name)
        if (!file.exists()) throw ServiceException("指定文件不存在")
        if (file.isDirectory) {
            file.list()?.let {
                if (it.isNotEmpty() && !mandatory) throw ServiceException("文件夹下内容不为空，不允许删除")
            }
            // 如果为强制删除，则删除目录下所有文件及本目录
            file.walkBottomUp().forEach {
                it.delete()
            }
            return
        }
        file.delete()
    }

    /**
     * 获取媒体文件根目录
     *
     * @return 媒体文件夹根目录
     */
    fun getMediaPath(): String {
        return RuoYiConfig.getProfile() + MEDIA_DIR
    }

    /**
     * 获取指定路径的文件信息
     *
     * @param path 文件所在路径
     * @param fileName 文件名称
     * @return 文件
     */
    fun getFile(path: String?, fileName: String): File {
        val filePath = if (path.isNullOrBlank()) "/" else path
        return if (filePath.last() == '/') {
            File(getMediaPath() + filePath + fileName)
        } else {
            File(getMediaPath() + filePath + "/" + fileName)
        }
    }

    /**
     * 获取文件名，如果重复就会自动标号，格式：文件名(序号)
     *
     * @param filePath 文件路径
     * @param fileName 文件名
     * @return 自动生成的文件名称
     */
    @Suppress("NAME_SHADOWING")
    fun getFileName(filePath: String, fileName: String?): String {
        val fileName = fileName ?: throw ServiceException("文件名不可为空")
        val fileNameWithExtension = fileName.substring(0, fileName.lastIndexOf("."))
        val extension = fileName.substringAfterLast(".")
        val files = File(filePath).listFiles() ?: emptyArray()
        val regex = "^$fileNameWithExtension(\\([1-9][0-9]*\\))*".toRegex()
        var max = 0
        val sameNameFiles = files.filter {
            it.isFile && regex.matches(it.name.substring(0, it.name.lastIndexOf(".")))
        }
        if (sameNameFiles.none { it.name == fileName }) return fileName
        sameNameFiles.forEach {
            if (it.name.contains("(")) {
                val name = it.name
                val num = name.substring(name.lastIndexOf("(") + 1, name.lastIndexOf(")"))
                try {
                    val n = num.toInt()
                    if (n > max) {
                        max = n
                    }
                } catch (_: NumberFormatException) {

                }
            }
        }
        return "$fileNameWithExtension(${max + 1}).$extension"
    }
}
