package com.ruoyi.web.controller.monitor;

import com.ruoyi.common.core.domain.AjaxResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 日志文件查看（HTTPS 轮询读取最新内容，无需 WebSocket）
 *
 * @author system
 */
@RestController
@RequestMapping("/monitor/logfile")
public class LogfileController {

    private static final Logger log = LoggerFactory.getLogger(LogfileController.class);

    /** 允许读取的日志文件名（不含路径），与 logback 及实际日志目录一致 */
    private static final List<String> ALLOWED_FILES = Collections.unmodifiableList(
            Arrays.asList("all.log", "sys-info.log", "sys-error.log", "sys-user.log"));

    /** 默认读取行数 */
    private static final int DEFAULT_LINES = 500;

    /** 最大读取行数 */
    private static final int MAX_LINES = 5000;

    @Value("${ruoyi.logPath:/home/van/project/ruoyi-java/logs}")
    private String logPath;

    /**
     * 获取可选的日志文件列表
     */
    @PreAuthorize("@ss.hasPermi('monitor:server:list')")
    @GetMapping("/list")
    public AjaxResult list() {
        List<String> files = new ArrayList<>(ALLOWED_FILES);
        return AjaxResult.success(files);
    }

    /**
     * 读取日志文件末尾 N 行（tail 语义），通过 HTTPS 返回最新内容
     *
     * @param file  文件名，如 sys-info.log（必须在白名单内）
     * @param lines 行数，默认 500，最大 5000
     */
    @PreAuthorize("@ss.hasPermi('monitor:server:list')")
    @GetMapping("/tail")
    public AjaxResult tail(
            @RequestParam(value = "file", required = true) String file,
            @RequestParam(value = "lines", required = false) Integer lines) {
        if (file == null || file.trim().isEmpty()) {
            return AjaxResult.error("参数 file 不能为空");
        }
        // 只允许白名单内的文件名，防止路径穿越
        String fileName = file.trim();
        if (!ALLOWED_FILES.contains(fileName)) {
            log.warn("拒绝非法日志文件请求: {}", fileName);
            return AjaxResult.error("不允许读取该文件");
        }
        int lineCount = lines != null && lines > 0 ? Math.min(lines, MAX_LINES) : DEFAULT_LINES;

        Path baseDir = Paths.get(logPath).normalize().toAbsolutePath();
        Path target = baseDir.resolve(fileName).normalize();
        if (!target.startsWith(baseDir)) {
            log.warn("路径穿越被拒绝: {}", target);
            return AjaxResult.error("非法路径");
        }
        if (!Files.isRegularFile(target)) {
            return AjaxResult.error("文件不存在或不可读: " + fileName);
        }

        try {
            List<String> allLines = Files.readAllLines(target, StandardCharsets.UTF_8);
            int total = allLines.size();
            int from = Math.max(0, total - lineCount);
            String content = allLines.subList(from, total).stream()
                    .collect(Collectors.joining("\n"));
            return AjaxResult.success(new TailResult(fileName, content, total, from + 1, total));
        } catch (IOException e) {
            log.error("读取日志文件失败: {}", target, e);
            return AjaxResult.error("读取失败: " + e.getMessage());
        }
    }

    /** 返回体中的 data 结构 */
    public static class TailResult {
        private final String file;
        private final String content;
        private final int totalLines;
        private final int fromLine;
        private final int toLine;

        public TailResult(String file, String content, int totalLines, int fromLine, int toLine) {
            this.file = file;
            this.content = content;
            this.totalLines = totalLines;
            this.fromLine = fromLine;
            this.toLine = toLine;
        }

        public String getFile() { return file; }
        public String getContent() { return content; }
        public int getTotalLines() { return totalLines; }
        public int getFromLine() { return fromLine; }
        public int getToLine() { return toLine; }
    }
}
