/**
 * 日志写入服务
 * 负责将日志异步写入数据库
 * 
 * 特点：
 * 1. 异步写入，不阻塞请求
 * 2. 批量写入，提高性能
 * 3. 写入失败时降级到文件日志
 */

import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { winstonLogger } from '../config/winston.config';

// 使用字符串类型，因为 LogLevel 枚举在 platform_logging schema 中
type LogLevelType = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';

export interface LogEntry {
  // 追踪信息
  traceId: string;
  spanId?: string;
  parentSpanId?: string;
  requestId: string;

  // 基本信息
  level: LogLevelType;
  message?: string;

  // HTTP 信息
  method?: string;
  url?: string;
  statusCode?: number;
  durationMs?: number;

  // 用户信息
  userId?: string;
  userName?: string;

  // 客户端信息
  clientIp?: string;
  userAgent?: string;

  // 服务信息
  service?: string;
  region?: string;
  instance?: string;

  // 错误信息
  errorType?: string;
  errorMessage?: string;
  errorCode?: string;
  errorStack?: string;

  // 请求/响应体
  requestBody?: any;
  responseBody?: any;

  // 元数据
  metadata?: any;
}

@Injectable()
export class LogWriterService implements OnModuleInit, OnModuleDestroy {
  private buffer: LogEntry[] = [];
  private flushInterval: NodeJS.Timeout | null = null;
  private isShuttingDown = false;

  // 配置
  private readonly BATCH_SIZE = 100;        // 批量写入大小
  private readonly FLUSH_INTERVAL_MS = 5000; // 刷新间隔（毫秒）
  private readonly MAX_BUFFER_SIZE = 1000;   // 最大缓冲区大小

  constructor(private readonly prisma: PrismaService) {}

  onModuleInit() {
    // 启动定时刷新
    this.flushInterval = setInterval(() => {
      this.flush().catch(err => {
        winstonLogger.error('Failed to flush logs', { error: err.message });
      });
    }, this.FLUSH_INTERVAL_MS);

    winstonLogger.info('LogWriterService initialized', {
      batchSize: this.BATCH_SIZE,
      flushIntervalMs: this.FLUSH_INTERVAL_MS,
    });
  }

  async onModuleDestroy() {
    this.isShuttingDown = true;
    
    // 清除定时器
    if (this.flushInterval) {
      clearInterval(this.flushInterval);
      this.flushInterval = null;
    }

    // 最后一次刷新
    await this.flush();
    winstonLogger.info('LogWriterService destroyed, flushed remaining logs');
  }

  /**
   * 添加日志到缓冲区
   */
  write(entry: LogEntry): void {
    if (this.isShuttingDown) {
      // 关闭时直接写入文件
      this.writeToFile(entry);
      return;
    }

    this.buffer.push(entry);

    // 如果缓冲区满了，立即刷新
    if (this.buffer.length >= this.BATCH_SIZE) {
      this.flush().catch(err => {
        winstonLogger.error('Failed to flush logs on buffer full', { error: err.message });
      });
    }

    // 如果缓冲区过大，按级别优先级丢弃（防止内存溢出）
    if (this.buffer.length > this.MAX_BUFFER_SIZE) {
      this.dropOverflow();
    }
  }

  /**
   * 缓冲区溢出处理：按级别优先级丢弃，DEBUG/INFO 优先丢，ERROR/WARN 兜底落文件
   * 避免 DB 长时间不可用时关键日志被静默吞掉
   */
  private dropOverflow(): void {
    let excess = this.buffer.length - this.MAX_BUFFER_SIZE;
    if (excess <= 0) return;
    const total = excess;
    const counts: Record<LogLevelType, number> = { ERROR: 0, WARN: 0, INFO: 0, DEBUG: 0 };

    for (const targetLevel of ['DEBUG', 'INFO', 'WARN', 'ERROR'] as LogLevelType[]) {
      if (excess === 0) break;
      for (let i = 0; i < this.buffer.length && excess > 0; ) {
        if (this.buffer[i].level !== targetLevel) {
          i++;
          continue;
        }
        const [dropped] = this.buffer.splice(i, 1);
        counts[targetLevel]++;
        excess--;
        // ERROR/WARN 即便被丢也要落文件，避免日志静默丢失
        if (targetLevel === 'ERROR' || targetLevel === 'WARN') {
          this.writeToFile(dropped);
        }
      }
    }

    winstonLogger.warn(`Dropped ${total} logs due to buffer overflow`, counts);
  }

  /**
   * 刷新缓冲区到数据库
   */
  async flush(): Promise<void> {
    if (this.buffer.length === 0) {
      return;
    }

    // 取出当前缓冲区的日志
    const logsToWrite = this.buffer.splice(0, this.BATCH_SIZE);

    try {
      // 批量写入数据库
      await this.prisma.systemLog.createMany({
        data: logsToWrite.map(entry => this.toSystemLogCreateInput(entry)),
        skipDuplicates: true,
      });
    } catch (error) {
      // 写入失败，降级到文件日志
      winstonLogger.error('Failed to write logs to database, falling back to file', {
        errorName: (error as Error).name,
        error: (error as Error).message,
        count: logsToWrite.length,
      });
      await this.flushOneByOne(logsToWrite);
    }
  }

  /**
   * 降级：写入文件日志
   */
  private writeToFile(entry: LogEntry): void {
    const logMethod = entry.level === 'ERROR' ? 'error' 
      : entry.level === 'WARN' ? 'warn' 
      : entry.level === 'DEBUG' ? 'debug' 
      : 'info';

    winstonLogger[logMethod](entry.message || 'HTTP Request', {
      ...entry,
      _fallback: true, // 标记为降级日志
    });
  }

  /**
   * 当批量写入失败时，逐条尝试写入，避免单条脏数据拖垮整批日志
   */
  private async flushOneByOne(entries: LogEntry[]): Promise<void> {
    for (const entry of entries) {
      try {
        await this.prisma.systemLog.create({
          data: this.toSystemLogCreateInput(entry),
        });
      } catch (singleError) {
        winstonLogger.error('Failed to write single log entry to database, falling back to file', {
          errorName: (singleError as Error).name,
          error: (singleError as Error).message,
          traceId: entry.traceId,
          requestId: entry.requestId,
          level: entry.level,
        });
        this.writeToFile(entry);
      }
    }
  }

  private toSystemLogCreateInput(entry: LogEntry) {
    return {
      traceId: entry.traceId,
      spanId: entry.spanId,
      parentSpanId: entry.parentSpanId,
      requestId: entry.requestId,
      level: entry.level,
      message: entry.message,
      method: entry.method,
      url: entry.url,
      statusCode: entry.statusCode,
      durationMs: entry.durationMs,
      userId: entry.userId,
      userName: entry.userName,
      clientIp: entry.clientIp,
      userAgent: entry.userAgent,
      service: entry.service || 'Backend',
      region: entry.region || 'CN',
      instance: entry.instance,
      errorType: entry.errorType,
      errorMessage: entry.errorMessage,
      errorCode: entry.errorCode,
      errorStack: entry.errorStack,
      requestBody: this.toSafeJsonValue(entry.requestBody),
      responseBody: this.toSafeJsonValue(entry.responseBody),
      metadata: this.toSafeJsonValue(entry.metadata),
    };
  }

  private toSafeJsonValue(value: unknown): any {
    if (value === undefined) {
      return undefined;
    }
    try {
      return JSON.parse(
        JSON.stringify(value, (_key, current) => {
          if (typeof current === 'bigint') {
            return current.toString();
          }
          return current;
        }),
      );
    } catch {
      return {
        _serializationError: true,
        type: typeof value,
        preview: String(value),
      };
    }
  }

  /**
   * 获取缓冲区状态（用于监控）
   */
  getBufferStatus(): { size: number; maxSize: number } {
    return {
      size: this.buffer.length,
      maxSize: this.MAX_BUFFER_SIZE,
    };
  }
}
