import Database = require('better-sqlite3');
import { createHash } from 'node:crypto';

export interface RoomInfo {
  tableName: string;
  username: string;
  roomName: string;
  isGroup: boolean;
}

export interface ContactIdentity {
  username: string;
  remark: string | null;
  nickName: string | null;
  displayName: string;
}

/**
 * Msg_<sha> 表名 ↔ 群名/用户名 映射。
 *
 * 表名算法已通过 PoC 13 确认: tableName = 'Msg_' + md5(username)
 *   - username 以 `@chatroom` 结尾 = 群
 *   - 其余为 DM 或公众号
 *
 * 数据来源(Weixin 4.x, 通过 PoC 13/14 确认):
 *   - session.db 的 SessionTable.username 列出所有活跃会话 (930+ rows)
 *   - contact.db 的 contact 表提供群/人 的显示名 (nick_name / remark)
 *   - 纯 DM 不需要 contact 补全(本项目只关心群)
 */
export class RoomResolver {
  private readonly roomsByTable = new Map<string, RoomInfo>();
  private readonly contactsByUsername = new Map<string, ContactIdentity>();
  private readonly roomOwnersByUsername = new Map<string, string>();

  /**
   * 从明文 session.db 的 SessionTable 加载所有会话 username,
   * 以 username 本身作为 roomName 占位(后续 mergeContactNames 再替换为显示名)。
   */
  loadFromSessionDb(sessionDbPath: string): void {
    const db = new Database(sessionDbPath, { readonly: true, fileMustExist: true });
    try {
      const rows = db.prepare(
        `SELECT username FROM SessionTable`
      ).all() as { username: string | null }[];
      for (const r of rows) {
        const username = String(r.username || '').trim();
        if (!username) continue;
        const tableName = 'Msg_' + createHash('md5').update(username, 'utf8').digest('hex');
        this.roomsByTable.set(tableName, {
          tableName,
          username,
          roomName: username,
          isGroup: username.endsWith('@chatroom'),
        });
      }
    } finally {
      db.close();
    }
  }

  /**
   * 从明文 contact.db 的 contact 表读群/人的显示名,覆盖 loadFromSessionDb 的 username 占位。
   * 优先级: remark > nick_name > username (loaded)。
   */
  mergeContactNames(contactDbPath: string): void {
    const db = new Database(contactDbPath, { readonly: true, fileMustExist: true });
    try {
      const rows = db.prepare(
        `SELECT username, remark, nick_name FROM contact`
      ).all() as { username: string | null; remark: string | null; nick_name: string | null }[];
      for (const r of rows) {
        const username = String(r.username || '').trim();
        if (!username) continue;
        const remark = (r.remark || '').trim() || null;
        const nickName = (r.nick_name || '').trim() || null;
        const displayName = remark || nickName || username;
        this.contactsByUsername.set(username, {
          username,
          remark,
          nickName,
          displayName,
        });
        const tableName = 'Msg_' + createHash('md5').update(username, 'utf8').digest('hex');
        const existing = this.roomsByTable.get(tableName);
        if (existing) {
          existing.roomName = displayName;
        } else {
          this.roomsByTable.set(tableName, {
            tableName,
            username,
            roomName: displayName,
            isGroup: username.endsWith('@chatroom'),
          });
        }
      }
      const tables = db.prepare(
        "SELECT name FROM sqlite_master WHERE type='table' AND name='chat_room'"
      ).all() as { name: string }[];
      if (tables.length > 0) {
        const roomRows = db.prepare(
          `SELECT username, owner FROM chat_room`
        ).all() as { username: string | null; owner: string | null }[];
        for (const rr of roomRows) {
          const username = String(rr.username || '').trim();
          const owner = String(rr.owner || '').trim();
          if (username && owner) this.roomOwnersByUsername.set(username, owner);
        }
      }
    } finally {
      db.close();
    }
  }

  lookup(tableName: string): RoomInfo | undefined {
    return this.roomsByTable.get(tableName);
  }

  resolveContact(username: string): ContactIdentity | undefined {
    return this.contactsByUsername.get(String(username || '').trim());
  }

  resolveRoomSenderIdentity(roomUsername: string, realSenderId: string): ContactIdentity | undefined {
    if (String(realSenderId || '').trim() !== '2') return undefined;
    const owner = this.roomOwnersByUsername.get(String(roomUsername || '').trim());
    return owner ? this.resolveContact(owner) : undefined;
  }

  /**
   * 按 roomName 反查所有匹配的群(仅 isGroup=true)。
   * 同名群可能多个,由调用方决定如何取舍。
   * 比较前 trim 双侧,抗首尾空格 / 用户输入与 contact.db 显示名细微差异。
   */
  findRoomsByName(roomName: string): RoomInfo[] {
    const needle = (roomName ?? '').trim();
    if (!needle) return [];
    const result: RoomInfo[] = [];
    for (const info of this.roomsByTable.values()) {
      if (info.isGroup && (info.roomName ?? '').trim() === needle) result.push(info);
    }
    return result;
  }

  /** 返回 tableName 列表中所有"群 + 用户白名单匹配"的子集。 */
  filterWhitelistedGroupTables(
    tableNames: string[],
    whitelistRoomNames: ReadonlySet<string>,
  ): string[] {
    return tableNames.filter(t => {
      const info = this.roomsByTable.get(t);
      if (!info || !info.isGroup) return false;
      return whitelistRoomNames.has(info.roomName);
    });
  }

  /** 测试钩子: 注入预设映射, 绕过 DB 加载。 */
  _setMap(map: Map<string, RoomInfo>): void {
    this.roomsByTable.clear();
    for (const [k, v] of map) this.roomsByTable.set(k, v);
  }

  /** 调试用:列出所有 isGroup 的 roomName(用于白名单不匹配排查)。 */
  _dumpAllGroupNames(): string[] {
    const out: string[] = [];
    for (const info of this.roomsByTable.values()) {
      if (info.isGroup) out.push(info.roomName);
    }
    return out;
  }
}
