/**
 * IM policy enforcement: DM policy, group policy, trigger mode, whitelists.
 *
 * Phase 1 enhancement:
 *   - Layer 1: Group-level admission (group_policy + group_whitelist)
 *   - Layer 2: Sender-level filtering (sender_whitelist in group context)
 *   - Layer 3: Mention requirement (trigger_mode + respond_to_mention_all)
 *   - DM: full pairing flow with verification codes
 */

import crypto from 'crypto'
import { imChannelRepo } from '@/lib/database/repositories/im-channel.repo'
import { imPairingRequestRepo } from '@/lib/database/repositories/im-pairing-request.repo'
import type { IncomingMessage } from '../types'

export type PolicyResult =
  | { allowed: true }
  | { allowed: false; reason: string }

// drizzle 推断的 ImChannel 行为 camelCase 字段；这里只投影成 policy 需要的 5 个。
// 与原裸 SQL `SELECT dm_policy, group_policy, trigger_mode, group_whitelist,
// sender_whitelist FROM im_channels WHERE id = ?` 字段集等价。
type PolicyChannel = {
  dmPolicy: string
  groupPolicy: string
  triggerMode: string
  groupWhitelist: string
  senderWhitelist: string
}

function readPolicyChannel(id: string): PolicyChannel | null {
  const row = imChannelRepo.findById(id)
  if (!row) return null
  const { dmPolicy, groupPolicy, triggerMode, groupWhitelist, senderWhitelist } = row
  return { dmPolicy, groupPolicy, triggerMode, groupWhitelist, senderWhitelist }
}

// ---------------------------------------------------------------------------
// Entry point
// ---------------------------------------------------------------------------

export function checkPolicy(msg: IncomingMessage): PolicyResult {
  const channel = readPolicyChannel(msg.channelId)

  if (!channel) {
    console.warn(
      `[Policy] Channel not found for id="${msg.channelId}" — ensure channelId is the DB UUID, not the platform type`,
    )
    return { allowed: false, reason: 'Channel not found' }
  }

  console.log(
    `[Policy] Checking: isDm=${msg.isDm}, isGroupMention=${msg.isGroupMention}, dm_policy=${channel.dmPolicy}, group_policy=${channel.groupPolicy}, trigger_mode=${channel.triggerMode}`,
  )

  if (msg.isDm) {
    return checkDmPolicy(channel.dmPolicy, msg, channel.senderWhitelist)
  }
  return checkGroupPolicy(channel, msg)
}

// ---------------------------------------------------------------------------
// DM policy
// ---------------------------------------------------------------------------

function checkDmPolicy(policy: string, msg: IncomingMessage, senderWhitelistStr: string): PolicyResult {
  switch (policy) {
    case 'disabled':
      return { allowed: false, reason: 'DM is disabled' }
    case 'open':
      return { allowed: true }
    case 'allowlist': {
      const whitelist = parseJsonArray(senderWhitelistStr)
      if (whitelist.includes(msg.senderId)) return { allowed: true }
      return { allowed: false, reason: 'Sender not in allowlist' }
    }
    case 'pairing': {
      const whitelist = parseJsonArray(senderWhitelistStr)
      if (whitelist.includes(msg.senderId)) return { allowed: true }

      // Check if there's an approved pairing request
      const approved = imPairingRequestRepo.findApproved(msg.channelId, msg.senderId)
      if (approved) return { allowed: true }

      return { allowed: false, reason: 'pairing_pending' }
    }
    default:
      return { allowed: false, reason: `Unknown DM policy: ${policy}` }
  }
}

// ---------------------------------------------------------------------------
// Group policy (Layer 1 + 2 + 3)
// ---------------------------------------------------------------------------

function checkGroupPolicy(channel: PolicyChannel, msg: IncomingMessage): PolicyResult {
  // ---- Layer 1: Group-level admission ----
  switch (channel.groupPolicy) {
    case 'disabled':
      return { allowed: false, reason: 'Group chat is disabled' }
    case 'allowlist': {
      const groupWhitelist = parseJsonArray(channel.groupWhitelist)
      if (!groupWhitelist.includes(msg.chatId)) {
        return { allowed: false, reason: 'Group not in allowlist' }
      }
      break
    }
    case 'open':
      break
    default:
      return { allowed: false, reason: `Unknown group policy: ${channel.groupPolicy}` }
  }

  // ---- Layer 2: Sender-level filtering ----
  const senderWhitelist = parseJsonArray(channel.senderWhitelist)
  if (senderWhitelist.length > 0 && !senderWhitelist.includes(msg.senderId)) {
    return { allowed: false, reason: 'Sender not allowed in this group' }
  }

  // ---- Layer 3: Trigger mode / mention requirement ----
  if (channel.triggerMode === 'mention' && !msg.isGroupMention) {
    // Check respond_to_mention_all: if the message @all, we may still respond
    // For now, treat @all as a mention if the message contains it.
    // (Full respond_to_mention_all config can be added to credentials JSON later.)
    return { allowed: false, reason: 'Bot not mentioned' }
  }

  return { allowed: true }
}

// ---------------------------------------------------------------------------
// Pairing helpers
// ---------------------------------------------------------------------------

/**
 * Generate a pairing code for a sender and store it in the DB.
 * Returns the code to be sent to the user.
 */
export function createPairingRequest(channelId: string, senderId: string): string {
  const code = generatePairingCode()

  // Expire any existing pending requests for this sender
  imPairingRequestRepo.expirePendingForSender(channelId, senderId)

  imPairingRequestRepo.create({
    id: crypto.randomUUID(),
    channelId,
    senderId,
    code,
  })

  return code
}

/**
 * Verify a pairing code. If valid, mark the request as approved and
 * optionally add the sender to the channel's sender_whitelist.
 */
export function verifyPairingCode(channelId: string, senderId: string, code: string): boolean {
  const request = imPairingRequestRepo.findPendingForCode(channelId, senderId, code)
  if (!request) return false

  // Mark as approved
  imPairingRequestRepo.approve(request.id)

  // Add sender to whitelist
  const channel = readPolicyChannel(channelId)
  if (channel) {
    const whitelist = parseJsonArray(channel.senderWhitelist)
    if (!whitelist.includes(senderId)) {
      whitelist.push(senderId)
      imChannelRepo.updateSenderWhitelist(channelId, JSON.stringify(whitelist))
    }
  }

  return true
}

// ---------------------------------------------------------------------------
// Utilities
// ---------------------------------------------------------------------------

function parseJsonArray(str: string): string[] {
  try {
    const parsed = JSON.parse(str)
    return Array.isArray(parsed) ? parsed : []
  } catch {
    return []
  }
}

function generatePairingCode(): string {
  // 6-digit numeric code
  return String(Math.floor(100000 + Math.random() * 900000))
}
