import electron from "electron";
import * as date from "date-and-time";
import path from "node:path";
import {AppEnv} from "../env";
import fs from "node:fs";
import dayjs from "dayjs";
import FileIndex from "../file";

let fileName = null;
let fileStream = null;
let appFileNames = {};
let appFileStreams = {};

const stringDatetime = () => {
    return date.format(new Date(), "YYYYMMDD");
};

const jsonStringifyLogData = (data: any) => {
    return JSON.stringify(data, (key, value) => {
        if (typeof value === "string" && value.length > 200) {
            if (value.startsWith("data:") || value.substring(0, 190).match(/^[a-zA-Z0-9+/=]+\s*$/)) {
                return value.substring(0, 100) + "...(length=" + value.length + ")";
            }
        }
        return value;
    })
}

const logsDir = () => {
    return path.join(AppEnv.userData, "logs");
};

const appLogsDir = () => {
    return path.join(AppEnv.dataRoot, "logs");
};

const root = () => {
    return logsDir();
};

const file = () => {
    return path.join(logsDir(), "log_" + stringDatetime() + ".log");
};

const appFile = (name: string) => {
    return path.join(appLogsDir(), name + "_" + stringDatetime() + ".log");
};

const cleanOldLogs = (keepDays: number) => {
    const logDirs = [
        // 系统日志
        logsDir(),
        // 应用日志
        appLogsDir(),
    ];
    for (const logDir of logDirs) {
        if (!fs.existsSync(logDir)) {
            return;
        }
        const files = fs.readdirSync(logDir);
        const now = new Date();
        // console.log('cleanOldLogs', logDir, files)
        for (let file of files) {
            const filePath = path.join(logDir, file);
            let date = null;
            for (let s of file.split(/[_\\.]/)) {
                // 匹配 YYYYMMDD
                if (s.match(/^\d{8}$/)) {
                    date = s;
                    break;
                }
            }
            if (!date) {
                continue;
            }
            const fileDate = new Date(
                parseInt(date.substring(0, 4)),
                parseInt(date.substring(4, 6)) - 1,
                parseInt(date.substring(6, 8))
            );
            const diff = Math.abs(now.getTime() - fileDate.getTime());
            const diffDays = Math.ceil(diff / (1000 * 3600 * 24));
            // console.log('fileDate', file, fileDate, diffDays)
            if (diffDays > keepDays) {
                fs.unlinkSync(filePath);
            }
        }
    }
};

const log = (level: "INFO" | "ERROR", label: string, data: any = null) => {
    if (fileName !== file()) {
        fileName = file();
        const logDir = logsDir();
        if (!fs.existsSync(logDir)) {
            fs.mkdirSync(logDir);
        }
        if (fileStream) {
            fileStream.end();
        }
        fileStream = fs.createWriteStream(fileName, {flags: "a"});
        cleanOldLogs(14);
    }
    let line = [];
    line.push(date.format(new Date(), "YYYY-MM-DD HH:mm:ss"));
    line.push(level);
    line.push(label);
    if (data) {
        if (!["number", "string"].includes(typeof data)) {
            data = jsonStringifyLogData(data);
        }
        line.push(data);
    }
    console.log(line.join(" - "));
    fileStream.write(line.join(" - ") + "\n");
};

const info = (label: string, data: any = null) => {
    return log("INFO", label, data);
};
const error = (label: string, data: any = null) => {
    return log("ERROR", label, data);
};
const debug = (label: string, data: any = null) => {
    // debug 级别等同于 info，但在开发环境中可能显示更多细节
    return log("INFO", label, data);
};

const appLog = (name: string, level: "INFO" | "ERROR", label: string, data: any = null) => {
    let fileChanged = false;
    if (appFileNames[name] !== appFile(name)) {
        appFileNames[name] = appFile(name);
        fileChanged = true;
    }
    if (fileChanged || !appFileStreams[name]) {
        if (appFileStreams[name]) {
            appFileStreams[name].end();
        }
        const logDir = appLogsDir();
        if (!fs.existsSync(logDir)) {
            fs.mkdirSync(logDir);
        }
        appFileStreams[name] = fs.createWriteStream(appFileNames[name], {flags: "a"});
    }
    let line = [];
    line.push(date.format(new Date(), "YYYY-MM-DD HH:mm:ss"));
    line.push(level);
    line.push(label);
    if (data) {
        if (!["number", "string"].includes(typeof data)) {
            data = JSON.stringify(data);
        }
        line.push(data);
    }
    console.log(`[APP:${name}] - ` + line.join(" - "));
    appFileStreams[name].write(line.join(" - ") + "\n");
};

const appPath = (name: string) => {
    if (!appFileNames[name]) {
        appFileNames[name] = appFile(name);
    }
    return appFileNames[name];
};

const appInfo = (name: string, label: string, data: any = null) => {
    return appLog(name, "INFO", label, data);
};
const appError = (name: string, label: string, data: any = null) => {
    return appLog(name, "ERROR", label, data);
};

const infoRenderOrMain = (label: string, data: any = null) => {
    if (electron.ipcRenderer) {
        console.log("Log.info", label, data);
        return electron.ipcRenderer.invoke("log:info", label, data);
    } else {
        return info(label, data);
    }
};
const errorRenderOrMain = (label: string, data: any = null) => {
    if (electron.ipcRenderer) {
        console.error("Log.error", label, data);
        return electron.ipcRenderer.invoke("log:error", label, data);
    } else {
        return error(label, data);
    }
};
const debugRenderOrMain = (label: string, data: any = null) => {
    if (electron.ipcRenderer) {
        console.log("Log.debug", label, data);
        return electron.ipcRenderer.invoke("log:debug", label, data);
    } else {
        return debug(label, data);
    }
};

const appInfoRenderOrMain = (name: string, label: string, data: any = null) => {
    if (electron.ipcRenderer) {
        console.log("Log.appInfo", name, label, data);
        return electron.ipcRenderer.invoke("log:appInfo", name, label, data);
    } else {
        return appInfo(name, label, data);
    }
};

const appErrorRenderOrMain = (name: string, label: string, data: any = null) => {
    if (electron.ipcRenderer) {
        console.error("Log.appError", name, label, data);
        return electron.ipcRenderer.invoke("log:appError", name, label, data);
    } else {
        return appError(name, label, data);
    }
};

const collectRenderOrMain = async (option?: { startTime?: string; endTime?: string; limit?: number }) => {
    option = Object.assign(
        {
            startTime: dayjs().subtract(1, "day").format("YYYY-MM-DD HH:mm:ss"),
            endTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
            limit: 10 * 10000,
        },
        option
    );
    let startMs = dayjs(option.startTime).valueOf();
    let endMs = dayjs(option.endTime).valueOf();
    let startDayMs = dayjs(option.startTime).startOf("day").valueOf();
    let endDayMs = dayjs(option.endTime).endOf("day").valueOf();
    let resultLines = [];
    let logFiles = [];
    logFiles = logFiles.concat(await FileIndex.list(logsDir(), {isDataPath: false}));
    logFiles = logFiles.concat(await FileIndex.list(appLogsDir(), {isDataPath: false}));
    // console.log('logFiles', logFiles)
    logFiles = logFiles.filter(logFile => {
        if (logFile.isDirectory) {
            return false;
        }
        let date = null;
        for (let s of logFile.name.split(/[_\\.]/)) {
            // 匹配 YYYYMMDD
            if (s.match(/^\d{8}$/)) {
                date = s;
                break;
            }
        }
        if (!date) {
            return false;
        }
        const fileDate = new Date(
            parseInt(date.substring(0, 4)),
            parseInt(date.substring(4, 6)) - 1,
            parseInt(date.substring(6, 8))
        );
        if (fileDate.getTime() < startDayMs || fileDate.getTime() > endDayMs) {
            return false;
        }
        return true;
    });
    // console.log('collectRenderOrMain', {
    //     ...option,
    //     logFiles, startMs, endMs, startDayMs, endDayMs
    // })
    for (const logFile of logFiles) {
        await FileIndex.readLine(
            logFile.pathname,
            line => {
                const lineParts = line.split(" - ");
                const lineTime = dayjs(lineParts[0]);
                // console.log('lineTime', lineParts[0], lineTime.isBefore(startMs) || lineTime.isAfter(endMs))
                if (lineTime.isBefore(startMs) || lineTime.isAfter(endMs)) {
                    return;
                }
                resultLines.push(line);
            },
            {isDataPath: false}
        );
    }
    return {
        startTime: option.startTime,
        endTime: option.endTime,
        logs: resultLines.join("\n"),
    };
};

export default {
    root,
    info,
    error,
    debug,
    infoRenderOrMain,
    errorRenderOrMain,
    debugRenderOrMain,
    appPath,
    appInfo,
    appError,
    appInfoRenderOrMain,
    appErrorRenderOrMain,
    collectRenderOrMain,
    jsonStringifyLogData,
};

export const Log = {
    jsonStringifyLogData,
    info: infoRenderOrMain,
    error: errorRenderOrMain,
    debug: debugRenderOrMain,
    warn: infoRenderOrMain,  // warn 使用 info 实现
    appPath,
    appInfo: appInfoRenderOrMain,
    appError: appErrorRenderOrMain,
};
