// ============================================================
// 主进程日志核心 — 文件写入 / 轮转 / 清理
//
// 架构：
//   - 日志写入 {userData}/logs/app-YYYY-MM-DD.log（JSON Lines 格式）
//   - 每日轮转：日期变化时自动创建新文件
//   - 保留策略：7 天前旧文件自动删除
//   - 写入队列批量冲刷（setImmediate 聚合同一 tick 的日志）
//   - 惰性初始化：app.getPath('userData') 在 ready 后才可用,
//     initLogger() 在 whenReady 中显式调用预热
//
// 用法（主进程）：
//   import { logger, initLogger } from './main/logger';
//   logger.info('updater', '检查更新', { version: '0.1.0' });
//
// 渲染进程日志通过 log-ipc.ts IPC 通道写入，调用 writeRendererLog()
// ============================================================

import { app } from 'electron';
import { createWriteStream, mkdirSync, readdirSync, statSync, unlinkSync } from 'node:fs';
import { join } from 'node:path';
import type { WriteStream } from 'node:fs';
import type { LogEntry, LogLevel, LogCategory, LogErrorSnapshot } from '../../shared/types/logging';
import { sanitizeLogEntry } from '../../shared/utils/sanitize';

// ---------- 内部状态 ----------

let writeStream: WriteStream | null = null;
let currentDate = '';
let logDir = '';
let logDirResolver: (() => string) | null = null;
const writeQueue: string[] = [];
let isWriting = false;
const MAX_DAYS = 7;

// ---------- 辅助函数 ----------

function getDateStr(): string {
   return new Date().toISOString().slice(0, 10);
}

function getLogFilePath(date: string): string {
   return join(logDir, `app-${date}.log`);
}

/** 确保 logs/ 目录存在 */
function ensureDir(): void {
   if (logDir) return;
   try {
      logDir = logDirResolver ? logDirResolver() : join(app.getPath('userData'), 'logs');
   } catch {
      // app.getPath 尚不可用（app.ready 之前），静默跳过
      return;
   }
   if (!logDir) return;
   try {
      mkdirSync(logDir, { recursive: true });
   } catch {
      /* 目录创建失败 — 后续写入静默丢弃 */
   }
}

/** 日期轮转：日切时关闭旧流 → 创建新文件 → 清理过期文件 */
function rotateIfNeeded(): void {
   const today = getDateStr();
   if (currentDate === today && writeStream) return;

   if (writeStream) {
      try {
         writeStream.end();
      } catch {
         /* ignore */
      }
      writeStream = null;
   }

   try {
      writeStream = createWriteStream(getLogFilePath(today), { flags: 'a' });
      currentDate = today;
   } catch {
      writeStream = null;
   }

   purgeOldLogs();
}

/** 清理超过 MAX_DAYS 天的旧日志文件 */
function purgeOldLogs(): void {
   if (!logDir) return;
   const now = Date.now();
   const maxAge = MAX_DAYS * 24 * 60 * 60 * 1000;

   try {
      const files = readdirSync(logDir);
      for (const file of files) {
         if (!file.startsWith('app-') || !file.endsWith('.log')) continue;
         try {
            const stat = statSync(join(logDir, file));
            if (now - stat.mtimeMs > maxAge) {
               unlinkSync(join(logDir, file));
            }
         } catch {
            /* skip inaccessible files */
         }
      }
   } catch {
      /* 目录读取失败 — 下次轮转再试 */
   }
}

/** 批量冲刷写入队列 */
function processQueue(): void {
   if (isWriting || writeQueue.length === 0) return;
   isWriting = true;

   ensureDir();
   if (logDir) {
      rotateIfNeeded();
      if (writeStream) {
         const batch = writeQueue.splice(0).join('');
         try {
            writeStream.write(batch);
         } catch {
            /* 写入失败 — 数据丢失，但日志不应阻断业务 */
         }
      } else {
         writeQueue.length = 0;
      }
   } else {
      // app ready 前尚无 userData 路径 — 丢弃（此时距 ready 极近）
      writeQueue.length = 0;
   }

   isWriting = false;
}

/** 错误对象 → 可序列化快照 */
function errorToSnapshot(err: Error): LogErrorSnapshot {
   return {
      name: err.name,
      message: err.message,
      stack: err.stack,
      cause: (err as { cause?: unknown }).cause
         ? String((err as { cause?: unknown }).cause)
         : undefined,
   };
}

/** 构建日志条目 */
function createEntry(
   level: LogLevel,
   category: LogCategory,
   message: string,
   processType: 'main' | 'renderer',
   error?: Error,
   context?: Record<string, unknown>,
): LogEntry {
   const entry: LogEntry = {
      timestamp: new Date().toISOString(),
      level,
      category,
      message,
      processType,
   };
   if (context && Object.keys(context).length > 0) {
      entry.context = context;
   }
   if (error) {
      entry.error = errorToSnapshot(error);
   }
   return entry;
}

/** 内部写入入口 */
function write(
   level: LogLevel,
   category: LogCategory,
   message: string,
   error?: Error,
   context?: Record<string, unknown>,
): void {
   const entry = sanitizeLogEntry(
      createEntry(level, category, message, 'main', error, context),
   );
   writeQueue.push(JSON.stringify(entry) + '\n');
   // 首个入队项触发 setImmediate 批量冲刷
   if (writeQueue.length === 1) {
      setImmediate(processQueue);
   }
}

// ---------- 对外 API ----------

/** 主进程日志器（与渲染进程 API 完全一致） */
export const logger = {
   debug(category: LogCategory, message: string, context?: Record<string, unknown>): void {
      write('DEBUG', category, message, undefined, context);
   },
   info(category: LogCategory, message: string, context?: Record<string, unknown>): void {
      write('INFO', category, message, undefined, context);
   },
   warn(
      category: LogCategory,
      message: string,
      error?: Error,
      context?: Record<string, unknown>,
   ): void {
      write('WARN', category, message, error, context);
   },
   error(
      category: LogCategory,
      message: string,
      error?: Error,
      context?: Record<string, unknown>,
   ): void {
      write('ERROR', category, message, error, context);
   },
};

/** 注入自定义日志目录解析器（在 initLogger() 之前调用） */
export function setLogDirResolver(resolver: () => string): void {
   logDirResolver = resolver;
}

/** 预热日志模块（在 app.whenReady() 中调用） */
export function initLogger(): void {
   ensureDir();
   if (logDir) {
      rotateIfNeeded();
   }
}

/**
 * 接收渲染进程日志并写入文件
 * 由 log-ipc.ts 的 IPC 监听器调用
 */
export function writeRendererLog(entry: LogEntry): void {
   writeQueue.push(JSON.stringify(entry) + '\n');
   if (writeQueue.length === 1) {
      setImmediate(processQueue);
   }
}

/** 冲刷所有未写入日志并关闭写入流（在 app.before-quit 中调用） */
export function flushLogger(): void {
   if (writeQueue.length > 0) {
      processQueue();
   }
   if (writeStream) {
      try {
         writeStream.end();
      } catch {
         /* ignore */
      }
      writeStream = null;
   }
}
