import type { MessageDO, SettingDO } from '../home/types'

import { toRaw } from 'vue'

import { getCurrentUserId } from '#/views/im/utils/auth'

import { ImConversationType } from './constants'

export const DB_SCHEMA_VERSION = 2

export type DbStoreName =
  | 'channels'
  | 'conversationReads'
  | 'conversations'
  | 'friendRequests'
  | 'friends'
  | 'groupMembers'
  | 'groupRequests'
  | 'groups'
  | 'messages'
  | 'settings'

export type DbTransaction = IDBTransaction

/** IM 本地存储 key */
export const StorageKeys = {
  localStorage: {
    /** 侧边栏宽度，三个 Tab 共用一份记忆 */
    asideWidth: 'im:aside',
    /** 会话列表置顶折叠展开态 */
    conversationPinnedExpanded: 'im:conversation:pinnedExpanded'
  },
  settings: {
    /** 私聊消息拉取游标 */
    privateMessageMaxId: 'privateMessageMaxId',
    /** 群聊消息拉取游标 */
    groupMessageMaxId: 'groupMessageMaxId',
    /** 频道消息拉取游标 */
    channelMessageMaxId: 'channelMessageMaxId',
    /** 最近转发会话 key 列表 */
    recentForwardConversationKeys: 'recentForwardConversationKeys',
    // 状态事件补偿增量拉取游标：与上面消息 maxId 游标共用同一 settings keyspace，统一登记在此避免撞 key；
    // 走 update_time + id 复合游标（非单条 maxId），故用 PullCursor 后缀区分语义
    /** 好友关系增量拉取游标 */
    friendPullCursor: 'friendPullCursor',
    /** 好友申请增量拉取游标 */
    friendRequestPullCursor: 'friendRequestPullCursor',
    /** 加群申请增量拉取游标 */
    groupRequestPullCursor: 'groupRequestPullCursor',
    /** 会话读位置增量拉取游标 */
    conversationReadPullCursor: 'conversationReadPullCursor'
  }
} as const

let currentDb: IDBDatabase | null = null
let currentUserId: null | number = null
let currentSession = 0

/** 校验当前 IM IndexedDB session 仍有效 */
export function isCurrentDbSession(session: number): boolean {
  return session === currentSession
}

/** 获取当前 IM IndexedDB session */
export function getDbSession(): number {
  return currentSession
}

/** 拼接当前用户 IM DB 名称 */
function getDbName(userId: number): string {
  return `im:${userId}`
}

/** 包装 IndexedDB request */
function requestToPromise<T = unknown>(request: IDBRequest<T>): Promise<T> {
  return new Promise((resolve, reject) => {
    request.addEventListener('success', () => resolve(request.result))
    request.addEventListener('error', () => reject(request.error))
  })
}

/** 等待事务完成 */
function transactionDone(transaction: DbTransaction): Promise<void> {
  return new Promise((resolve, reject) => {
    transaction.addEventListener('complete', () => resolve())
    transaction.addEventListener('error', () => reject(transaction.error))
    transaction.addEventListener('abort', () => reject(transaction.error))
  })
}

/** 创建索引 */
function createIndex(
  store: IDBObjectStore,
  name: string,
  keyPath: string | string[],
  options?: IDBIndexParameters
) {
  if (!store.indexNames.contains(name)) {
    store.createIndex(name, keyPath, options)
  }
}

/** 初始化 schema */
function upgradeSchema(db: IDBDatabase) {
  if (!db.objectStoreNames.contains('conversations')) {
    const store = db.createObjectStore('conversations', { keyPath: 'clientConversationId' })
    createIndex(store, 'lastSendTime', 'lastSendTime')
  }
  if (!db.objectStoreNames.contains('conversationReads')) {
    const store = db.createObjectStore('conversationReads', { keyPath: 'clientConversationId' })
    createIndex(store, 'conversationType+targetId', ['conversationType', 'targetId'], {
      unique: true
    })
  }
  if (!db.objectStoreNames.contains('messages')) {
    const store = db.createObjectStore('messages', { keyPath: 'messageKey' })
    createIndex(store, 'clientConversationId', 'clientConversationId')
    createIndex(store, 'clientConversationId+sendTime', ['clientConversationId', 'sendTime'])
    createIndex(store, 'clientMessageId', 'clientMessageId', { unique: true })
  }
  if (!db.objectStoreNames.contains('friends')) {
    const store = db.createObjectStore('friends', { keyPath: 'id' })
    createIndex(store, 'friendUserId', 'friendUserId', { unique: true })
    createIndex(store, 'status', 'status')
  }
  if (!db.objectStoreNames.contains('friendRequests')) {
    const store = db.createObjectStore('friendRequests', { keyPath: 'id' })
    createIndex(store, 'status', 'status')
    createIndex(store, 'createTime', 'createTime')
  }
  if (!db.objectStoreNames.contains('groups')) {
    const store = db.createObjectStore('groups', { keyPath: 'id' })
    createIndex(store, 'name', 'name')
    createIndex(store, 'status', 'status')
  }
  if (!db.objectStoreNames.contains('groupMembers')) {
    const store = db.createObjectStore('groupMembers', { keyPath: 'id' })
    createIndex(store, 'groupId', 'groupId')
    createIndex(store, 'groupId+userId', ['groupId', 'userId'], { unique: true })
  }
  if (!db.objectStoreNames.contains('groupRequests')) {
    const store = db.createObjectStore('groupRequests', { keyPath: 'id' })
    createIndex(store, 'status', 'status')
    createIndex(store, 'createTime', 'createTime')
  }
  if (!db.objectStoreNames.contains('channels')) {
    const store = db.createObjectStore('channels', { keyPath: 'id' })
    createIndex(store, 'status', 'status')
    createIndex(store, 'sort', 'sort')
  }
  if (!db.objectStoreNames.contains('settings')) {
    db.createObjectStore('settings', { keyPath: 'key' })
  }
}

/** 打开 IM IndexedDB */
function openDb(name: string): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(name, DB_SCHEMA_VERSION)
    // 创建或升级对象仓库
    request.addEventListener('upgradeneeded', () => upgradeSchema(request.result))
    // 返回可复用连接
    request.addEventListener('success', () => resolve(request.result))
    request.addEventListener('error', () => reject(request.error))
  })
}

/** 初始化当前用户 IM DB */
export async function initDb(): Promise<void> {
  const userId = getCurrentUserId()
  if (!Number.isFinite(userId) || userId <= 0) {
    throw new Error('当前用户不存在，无法初始化 IM DB')
  }
  if (currentDb && currentUserId === userId) {
    return
  }
  currentDb?.close()
  currentSession++
  currentUserId = userId
  currentDb = await openDb(getDbName(userId))
}

/** 关闭当前 IM DB 连接 */
function closeDbConnection() {
  currentDb?.close()
  currentDb = null
  currentUserId = null
}

/** 获取当前 IM DB */
function getRawDb(): IDBDatabase {
  if (!currentDb) {
    throw new Error('IM DB 未初始化')
  }
  return currentDb
}

/** 校验单次写入 session */
function guardSession(session: number) {
  if (!isCurrentDbSession(session)) {
    throw new Error('IM DB session 已失效')
  }
}

/** 克隆可入库对象 */
function toDbValue<T>(value: T): T {
  return cloneDbValue(value) as T
}

/** 转换为 IndexedDB 可克隆对象 */
function cloneDbValue(value: unknown): unknown {
  const raw = toRaw(value)
  if (Array.isArray(raw)) {
    return raw.map((item) => cloneDbValue(item))
  }
  if (!raw || typeof raw !== 'object') {
    return raw
  }
  const prototype = Object.getPrototypeOf(raw)
  if (prototype !== Object.prototype && prototype !== null) {
    return raw
  }
  return Object.fromEntries(
    Object.entries(raw as Record<string, unknown>).map(([key, item]) => [key, cloneDbValue(item)])
  )
}

class DbClient {
  /** 清空 store 记录 */
  async clearStore(storeName: DbStoreName, tx?: DbTransaction): Promise<void> {
    if (tx) {
      await requestToPromise(tx.objectStore(storeName).clear())
      return
    }
    await this.transaction([storeName], 'readwrite', (tx) => this.clearStore(storeName, tx))
  }

  /** 删除记录 */
  async delete(storeName: DbStoreName, key: IDBValidKey, tx?: DbTransaction): Promise<void> {
    if (tx) {
      await requestToPromise(tx.objectStore(storeName).delete(key))
      return
    }
    await this.transaction([storeName], 'readwrite', (tx) => this.delete(storeName, key, tx))
  }

  /** 按索引删除记录 */
  async deleteByIndex(
    storeName: DbStoreName,
    indexName: string,
    query: IDBKeyRange | IDBValidKey,
    tx?: DbTransaction
  ): Promise<void> {
    if (!tx) {
      await this.transaction([storeName], 'readwrite', (tx) =>
        this.deleteByIndex(storeName, indexName, query, tx)
      )
      return
    }
    const index = tx.objectStore(storeName).index(indexName)
    await new Promise<void>((resolve, reject) => {
      const request = index.openCursor(query)
      request.addEventListener('error', () => reject(request.error))
      request.addEventListener('success', () => {
        const cursor = request.result
        if (!cursor) {
          resolve()
          return
        }
        cursor.delete()
        cursor.continue()
      })
    })
  }

  /** 获取单条记录 */
  async get<T>(
    storeName: DbStoreName,
    key: IDBValidKey,
    tx?: DbTransaction
  ): Promise<T | undefined> {
    if (tx) {
      return requestToPromise<T | undefined>(tx.objectStore(storeName).get(key))
    }
    return this.transaction<T | undefined>([storeName], 'readonly', (tx) =>
      this.get<T>(storeName, key, tx)
    )
  }

  /** 获取 store 全量记录 */
  async getAll<T>(storeName: DbStoreName, tx?: DbTransaction): Promise<T[]> {
    if (tx) {
      return requestToPromise<T[]>(tx.objectStore(storeName).getAll())
    }
    return this.transaction<T[]>([storeName], 'readonly', (tx) => this.getAll<T>(storeName, tx))
  }

  /** 按索引获取记录列表 */
  async getAllByIndex<T>(
    storeName: DbStoreName,
    indexName: string,
    query?: IDBKeyRange | IDBValidKey,
    tx?: DbTransaction
  ): Promise<T[]> {
    if (tx) {
      return requestToPromise<T[]>(tx.objectStore(storeName).index(indexName).getAll(query))
    }
    return this.transaction<T[]>([storeName], 'readonly', (tx) =>
      this.getAllByIndex<T>(storeName, indexName, query, tx)
    )
  }

  /** 按唯一索引获取单条记录 */
  async getByIndex<T>(
    storeName: DbStoreName,
    indexName: string,
    query: IDBKeyRange | IDBValidKey,
    tx?: DbTransaction
  ): Promise<T | undefined> {
    if (tx) {
      return requestToPromise<T | undefined>(tx.objectStore(storeName).index(indexName).get(query))
    }
    return this.transaction<T | undefined>([storeName], 'readonly', (tx) =>
      this.getByIndex<T>(storeName, indexName, query, tx)
    )
  }

  /** 按会话分页获取消息 */
  async getMessageListByConversation(
    clientConversationId: string,
    options?: { beforeSendTime?: number; limit?: number },
    tx?: DbTransaction
  ): Promise<MessageDO[]> {
    const limit = options?.limit ?? 50
    const upper = options?.beforeSendTime ?? Number.MAX_SAFE_INTEGER
    const range = IDBKeyRange.bound(
      [clientConversationId, 0],
      [clientConversationId, upper],
      false,
      true
    )
    const read = async (tx: DbTransaction): Promise<MessageDO[]> => {
      const index = tx.objectStore('messages').index('clientConversationId+sendTime')
      const out: MessageDO[] = []
      await new Promise<void>((resolve, reject) => {
        // 从新到旧读取一页
        const request = index.openCursor(range, 'prev')
        request.addEventListener('error', () => reject(request.error))
        request.addEventListener('success', () => {
          const cursor = request.result
          if (!cursor || out.length >= limit) {
            resolve()
            return
          }
          out.push(cursor.value as MessageDO)
          cursor.continue()
        })
      })
      // 气泡渲染需要按时间升序
      return out.toReversed()
    }
    if (tx) {
      return read(tx)
    }
    return this.transaction<MessageDO[]>(['messages'], 'readonly', read)
  }

  /** 读取设置 */
  async getSetting<T>(key: string, tx?: DbTransaction): Promise<T | undefined> {
    const item = await this.get<SettingDO<T>>('settings', key, tx)
    return item?.value
  }

  /** 写入记录 */
  async put<T>(storeName: DbStoreName, value: T, tx?: DbTransaction): Promise<void> {
    if (tx) {
      await requestToPromise(tx.objectStore(storeName).put(toDbValue(value)))
      return
    }
    await this.transaction([storeName], 'readwrite', (tx) => this.put(storeName, value, tx))
  }

  /** 写入设置 */
  async setSetting<T>(key: string, value: T, tx?: DbTransaction): Promise<void> {
    await this.put<SettingDO<T>>('settings', { key, value, updateTime: Date.now() }, tx)
  }

  /** 执行事务 */
  async transaction<T>(
    storeNames: DbStoreName[],
    mode: IDBTransactionMode,
    runner: (tx: DbTransaction) => Promise<T>
  ): Promise<T> {
    // 开启事务前校验 session
    const session = getDbSession()
    guardSession(session)
    const tx = getRawDb().transaction(storeNames, mode)
    const done = transactionDone(tx)
    let result: T
    try {
      // 事务内只执行 IndexedDB request 链
      result = await runner(tx)
    } catch (error) {
      try {
        tx.abort()
      } catch {}
      await done.catch(() => undefined)
      throw error
    }
    // commit 后再次校验 session
    await done
    guardSession(session)
    return result
  }
}

const dbClient = new DbClient()

/** 获取当前 IM DB client */
export function getDb(): DbClient {
  return dbClient
}

/** 当前用户会话主键 */
export function getClientConversationId(type: number, targetId: number): string {
  return `${type}:${targetId}`
}

/** 解析当前用户会话主键 */
export function parseClientConversationId(
  clientConversationId: string
): null | { targetId: number; type: number; } {
  const [typeText, targetIdText] = clientConversationId.split(':')
  const type = Number(typeText)
  const targetId = Number(targetIdText)
  if (!Number.isFinite(type) || !Number.isFinite(targetId) || targetId <= 0) {
    return null
  }
  return { type, targetId }
}

/** 服务端消息主键 */
export function getServerMessageKey(conversationType: number, id: number): string {
  return `${conversationType}:${id}`
}

/** 客户端临时消息主键 */
export function getClientMessageKey(clientMessageId: string): string {
  return `client:${clientMessageId}`
}

/** 解析本地消息主键 */
export function parseMessageKey(
  messageKey: string
):
  | null
  | { clientMessageId: string; kind: 'client'; }
  | { conversationType: number; id: number; kind: 'server'; } {
  if (!messageKey) {
    return null
  }
  if (messageKey.startsWith('client:')) {
    const clientMessageId = messageKey.slice('client:'.length)
    return clientMessageId ? { kind: 'client', clientMessageId } : null
  }
  const [conversationTypeText, idText] = messageKey.split(':')
  const conversationType = Number(conversationTypeText)
  const id = Number(idText)
  if (!Number.isFinite(conversationType) || !Number.isFinite(id) || id <= 0) {
    return null
  }
  return { kind: 'server', conversationType, id }
}

/** 更新消息拉取游标 */
export async function setMessageMaxId(
  conversationType: number,
  maxId: number | undefined,
  tx?: DbTransaction
): Promise<void> {
  if (!maxId) {
    return
  }
  let key: string
  switch (conversationType) {
    case ImConversationType.CHANNEL: {
      key = StorageKeys.settings.channelMessageMaxId
      break
    }
    case ImConversationType.GROUP: {
      key = StorageKeys.settings.groupMessageMaxId
      break
    }
    case ImConversationType.PRIVATE: {
      key = StorageKeys.settings.privateMessageMaxId
      break
    }
    default: {
      throw new Error(`未知 IM 会话类型：${conversationType}`)
    }
  }
  const db = getDb()
  const current = (await db.getSetting<number>(key, tx)) || 0
  if (maxId > current) {
    await db.setSetting(key, maxId, tx)
  }
}

/** 停止当前 IM DB session */
export async function stopRequests(): Promise<void> {
  currentSession++
  const [
    { useMessageStoreWithOut },
    { useConversationStoreWithOut },
    { useFriendStoreWithOut },
    { useGroupStoreWithOut },
    { useChannelStoreWithOut },
    { useGroupRequestStoreWithOut },
    { useFaceStoreWithOut },
    { useRtcStore }
  ] = await Promise.all([
    import('../home/store/messageStore'),
    import('../home/store/conversationStore'),
    import('../home/store/friendStore'),
    import('../home/store/groupStore'),
    import('../home/store/channelStore'),
    import('../home/store/groupRequestStore'),
    import('../home/store/faceStore'),
    import('../home/store/rtcStore')
  ])
  useMessageStoreWithOut().clear()
  useConversationStoreWithOut().clear()
  useFriendStoreWithOut().clear()
  useGroupStoreWithOut().clear()
  useChannelStoreWithOut().clear()
  useGroupRequestStoreWithOut().clear()
  useFaceStoreWithOut().clear()
  useRtcStore().reset()
  useRtcStore().clearGroupCallCache()
  closeDbConnection()
}
