// oms/src/services/clickhouseService.ts
import { createClient, ClickHouseClient } from "@clickhouse/client";
import dayjs from "dayjs"; // 👈 新增：导入 dayjs 用于日期格式化

// 定义 ClickHouse Event Log 的 TypeScript 接口
export interface IEventLog {
  log_id: string; // 消息唯一id，主键，对应原始日志的_id字段
  uid: string; // 用户id，对应原始日志的uid字段（project_id=1) 或 user_id字段（project_id=6)
  project: number; // 项目id， ,project_id=1表示android填色应用，project_id=6表示ios版本
  os?: string; // 系统版本,取值android/ios， 对应原始日志的library_name字段
  version?: string; // 应用版本，对应原始日志的version_name字段(project_id=1), 或 library_version字段(project_id=6)
  event: string; // 事件类型，对应原始日志的type字段（project_id=1），或 name 字段（project_id=6)
  time: Date; // 事件时间，对应原始日志的t字段（project_id=1), 或 create_at 字段(project_id=6)
  res?: string | null; // 关联的作品，对应原始日志的res字段(project_id=1) 或 sku_id 字段(project_id=6)
  from?: string | null; // 事件来源，如来自daily，gallery之类的，对应原始日志的from字段（project_id=1)或tab source字段（project_id=6)
  position?: number | null; // 点击位置, 对应原始日志的position字段（project_id=1)或click_position字段(project_id=6)
  duration?: number | null; // 填色时长, 对应原始日志的duration字段, 只在color_data 和 color_done事件上报，目前都没有
  ad_type?: string | null; // 广告类型
  ad_src?: string | null; // 广告位
  revenue?: number | null; // 广告收益值
  cc?: string | null; // 国家
  raw_json_data: string; // 原始日志报文
}

class ClickhouseService {
  private client: ClickHouseClient;
  private readonly database: string;

  constructor(host: string, database: string, username?: string, password?: string) {
    this.database = database;
    this.client = createClient({
      url: host,
      database: database,
      username: username, // 传递用户名
      password: password, // 传递密码
    });
    console.log(`ClickHouseService initialized for database: ${database} at ${host}`);
  }

  /**
   * 确保 ClickHouse 表存在。
   * @param tableName - 要检查的表名。
   */
  public async ensureTable(tableName: string) {
    const createTableSql = `
      CREATE TABLE IF NOT EXISTS ${tableName} (
          log_id String, -- 消息唯一id，主键，对应原始日志的_id字段
          uid String, -- 用户id，对应原始日志的uid字段（对应project_id=1 android填色应用)， 或user_id字段（对应project_id=6的ios应用）
          project UInt8, -- 项目id，对应原始日志的project_id字段， project_id=1表示android填色应用，project_id=6表示ios版本
          os Nullable(String), -- 系统版本,取值android/ios， 对应原始日志的library_name字段
          version Nullable(String), -- 应用版本，对应原始日志的version_name字段(project_id=1), 或 library_version字段(project_id=6)
          event String, -- 事件类型，对应原始日志的type字段（project_id=1），或 name 字段（project_id=6)
          time DateTime, -- 事件时间，对应原始日志的t字段（project_id=1), 或 create_at 字段(project_id=6)
          res Nullable(String), -- 关联的作品，对应原始日志的res字段(project_id=1) 或 sku_id 字段(project_id=6)
          from Nullable(String), -- 事件来源，如来自daily，gallery之类的，对应原始日志的from字段（project_id=1)或tab source字段（project_id=6)
          position Nullable(Int32), -- 点击位置, 对应原始日志的position字段（project_id=1)或click_position字段(对应project_id=6)
          duration Nullable(UInt32), -- 填色时长，对于color_done/color_data事件有用，提取出来作为公共字段方便后续统计，对应原始日志的duration字段 目前都没有上报，以后版本会加上
          ad_type Nullable(String), -- 广告类型，对于广告事件才有用，提取出来作为公共字段方便后续统计，对应原始日志的ad_type字段
          ad_src Nullable(String), -- 广告位，对于广告事件才有用，提取出来作为公共字段方便后续统计，对应原始日志的ad_src字段
          revenue Nullable(Float64), -- 广告收益值，对于广告的revenue事件才有用，提取出来作为公共字段方便后续统计，对应原始日志的rev字段(project_id=1)， 或ad revenue字段(project_id=6)
          cc Nullable(String), -- 国家，对应原始日志cc字段（目前project_id=1的android有上报， ios没有上报）
          raw_json_data String -- 原始json数据
      ) ENGINE = MergeTree()
      ORDER BY (uid, time, event) -- 建议的排序键
      PRIMARY KEY (uid, time) -- 主键可用于数据去重和部分查询优化
      -- TTL time + INTERVAL 30 DAY -- 示例：数据保留30天，根据需求调整。 暂时先注释掉
    `;
    try {
      await this.client.exec({ query: createTableSql });
      console.log(`ClickHouse table '${tableName}' ensured.`);
    } catch (error) {
      console.error(`Failed to ensure ClickHouse table '${tableName}':`, error);
      throw error; // Rethrow to handle in calling context
    }
  }

  /**
   * 插入单个或多个事件日志到 ClickHouse。
   * @param tableName - 要插入的表名。
   * @param events - 单个事件日志数据或事件日志数据数组。
   */
  public async insertEvent(tableName: string, events: IEventLog | IEventLog[]): Promise<void> {
    const valuesToInsert = Array.isArray(events) ? events : [events]; // <--- 关键修改：确保 values 始终是一个数组
    if (valuesToInsert.length === 0) {
      return; // No events to insert
    }

    // 👈 关键修改：在发送到 ClickHouse 之前，格式化 Date 对象
    const formattedValues = valuesToInsert.map((event) => ({
      ...event,
      // 将 Date 对象格式化为 ClickHouse 期望的字符串格式
      time: dayjs(event.time).format("YYYY-MM-DD HH:mm:ss"),
    }));

    try {
      await this.client.insert({
        table: tableName,
        values: formattedValues, // <--- 使用处理后的数组
        format: "JSONEachRow", // ClickHouse 客户端支持多种格式
      });
      // console.log(`Inserted ${valuesToInsert.length} events into ${tableName}.`); // 更适合批量插入的日志
    } catch (error) {
      console.error(`Error inserting event(s) into ${tableName}:`, error);
      throw error;
    }
  }

  /**
   * 查询 ClickHouse 事件日志。
   * @param querySql - ClickHouse 查询 SQL 语句。
   * @returns 查询结果数组。
   */
  public async queryEvents<T>(querySql: string): Promise<T[]> {
    try {
      const resultSet = await this.client.query({ query: querySql });
      const response = await resultSet.json<T[]>();
      // 从响应中提取数据数组，通常在data属性中
      const result = response.data as T[];
      return result;
    } catch (error) {
      console.error(`Error querying ClickHouse:`, error);
      throw error;
    }
  }

  /**
   * 获取 ClickHouse 客户端实例 (如果需要更高级的操作)。
   */
  public getClient(): ClickHouseClient {
    return this.client;
  }
}

// 在 app.ts 中实例化并导出此服务
export { ClickhouseService };
