import pino, { type Logger as PinoLogger, type LoggerOptions } from "pino";

import { AppEnvUtil } from "./app-env";
import {
  reportSentryLog,
  type RemoteLogLevel,
} from "./logger-sentry-reporter";

type LogArgs = unknown[];
type PinoLogArgs = Parameters<PinoLogger["debug"]>;
type LogLevel = "debug" | "info" | "warn" | "error" | "fatal";

/**
 * 全局 pino root logger（单例，进程级复用）。
 *
 * 配置驱动（基于 `app-env.ts` 的 `AppEnvUtil`）：
 *   - production:  silent（不输出日志）
 *   - test/dev:    debug 级别 + ISO 时间戳
 *   - browser:     保留原始 message 参数，避免多行日志被转义成 `\n`
 */
const rootLogger: PinoLogger = pino(createLoggerOptions());

function createLoggerOptions(): LoggerOptions {
  if (!AppEnvUtil.canOutputLogs()) {
    return { level: "silent" };
  }
  if (AppEnvUtil.isTest()) {
    return {
      level: "debug",
      timestamp: pino.stdTimeFunctions.isoTime,
      browser: { asObjectBindingsOnly: true },
    };
  }
  // development
  return {
    level: "debug",
    timestamp: pino.stdTimeFunctions.isoTime,
    browser: { asObjectBindingsOnly: true },
  };
}

/**
 * Logger 工具类（pino 包装）
 *
 * 设计：
 *   - 构造时绑定 `component` 名（业务模块标识）
 *   - 所有日志自动带 `{ component: "AuthRepository" }` 字段
 *   - 透传 pino 原生 5 个 level：debug / info / warn / error / fatal
 *
 * 用法：
 *   const log = new Logger("AuthRepository");
 *   log.info("login success");
 *   log.warn({ err }, "storage write failed");
 *   log.error(e, "consumer error");
 *   log.debug({ userId: 123 }, "fetched user");
 */
export class Logger {
  private readonly logger: PinoLogger;
  private readonly component: string;

  constructor(component: string) {
    this.component = component;
    this.logger = rootLogger.child({ component });
  }

  debug(...args: LogArgs): void {
    this.write("debug", args);
  }
  info(...args: LogArgs): void {
    this.write("info", args);
  }
  warn(...args: LogArgs): void {
    this.write("warn", args);
  }
  error(...args: LogArgs): void {
    this.write("error", args);
  }
  fatal(...args: LogArgs): void {
    this.write("fatal", args);
  }

  /**
   * 把日志中的任意数据转成人眼友好的多行文本。
   * - object / array: pretty JSON
   * - JSON string: parse 后 pretty JSON
   * - FormData / File: 使用轻量描述，避免直接输出二进制
   */
  static formatValue(value: unknown): string {
    if (value === undefined || value === null) return "";

    if (typeof FormData !== "undefined" && value instanceof FormData) {
      const entries: string[] = [];
      value.forEach((entryValue, key) => {
        if (typeof File !== "undefined" && entryValue instanceof File) {
          entries.push(`${key}=File(${entryValue.name})`);
        } else {
          entries.push(`${key}=${String(entryValue)}`);
        }
      });
      return `FormData{\n  ${entries.join(",\n  ")}\n}`;
    }

    if (typeof value === "string") {
      const trimmed = value.trim();
      if (trimmed.length === 0) return "";
      try {
        return JSON.stringify(JSON.parse(trimmed), null, 2);
      } catch {
        return value;
      }
    }

    try {
      return JSON.stringify(value, null, 2);
    } catch {
      return String(value);
    }
  }

  private static normalizeLogArgs(args: LogArgs): PinoLogArgs {
    const [first, second, ...rest] = args;
    if (typeof first !== "string" || second === undefined) return args as PinoLogArgs;

    if (second instanceof Error) {
      return [{ err: second }, first, ...rest] as PinoLogArgs;
    }

    if (
      typeof second === "object" &&
      second !== null &&
      !Array.isArray(second)
    ) {
      return [second, first, ...rest] as PinoLogArgs;
    }

    return args as PinoLogArgs;
  }

  private write(level: LogLevel, args: LogArgs): void {
    Logger.reportImportantProductionLog(level, this.component, args);

    if (Logger.shouldUseBrowserConsole()) {
      Logger.writeBrowserConsole(level, this.component, args);
      return;
    }

    switch (level) {
      case "debug":
        this.logger.debug(...Logger.normalizeLogArgs(args));
        return;
      case "info":
        this.logger.info(...Logger.normalizeLogArgs(args));
        return;
      case "warn":
        this.logger.warn(...Logger.normalizeLogArgs(args));
        return;
      case "error":
        this.logger.error(...Logger.normalizeLogArgs(args));
        return;
      case "fatal":
        this.logger.fatal(...Logger.normalizeLogArgs(args));
        return;
    }
  }

  private static shouldUseBrowserConsole(): boolean {
    return typeof window !== "undefined" && AppEnvUtil.canOutputLogs();
  }

  private static shouldReportToRemote(level: LogLevel): level is RemoteLogLevel {
    return AppEnvUtil.isProduction() && (
      level === "warn" ||
      level === "error" ||
      level === "fatal"
    );
  }

  private static reportImportantProductionLog(
    level: LogLevel,
    component: string,
    args: LogArgs,
  ): void {
    if (!Logger.shouldReportToRemote(level)) return;

    const { message, data } = Logger.toBrowserConsolePayload(args);
    reportSentryLog({
      level,
      component,
      message,
      data,
      formatValue: Logger.formatValue,
    });
  }

  private static writeBrowserConsole(
    level: LogLevel,
    component: string,
    args: LogArgs,
  ): void {
    const { message, data } = Logger.toBrowserConsolePayload(args);
    const prefix = `${new Date().toISOString()} ${level.toUpperCase()} [${component}]`;
    const method = level === "fatal" ? "error" : level;

    if (data.length === 0) {
      console[method](`${prefix} ${message}`);
      return;
    }

    console[method](`${prefix} ${message}`, ...data);
  }

  private static toBrowserConsolePayload(args: LogArgs): {
    message: string;
    data: unknown[];
  } {
    const [first, second, ...rest] = args;

    if (typeof first === "string") {
      return {
        message: first,
        data: second === undefined ? rest : [second, ...rest],
      };
    }

    if (typeof second === "string") {
      return {
        message: second,
        data: [first, ...rest],
      };
    }

    return {
      message: "",
      data: args,
    };
  }

  /** 透传 raw pino logger（高级用法：自定义 binding / child / serializers） */
  get raw(): PinoLogger {
    return this.logger;
  }
}
