/**
 * Invitation provisioner — creates user workspace + IM channel after
 * the invitee completes WeChat Bot authorization.
 *
 * Flow: bot_connected → provisionUser() → im_channel INSERT →
 *       channel_binding INSERT → status='completed'
 */

import fs from 'fs'
import path from 'path'
import crypto from 'crypto'
import { getDb } from '@/lib/database/client'
import type { Database } from '@/lib/database/client'
import { workspaces, imChannels, channelBindings } from '@/lib/database/schema'
import { makeUserDirName } from '@/lib/invite/user-id'
import { getSetting } from '@/lib/settings-store'
import type { WeixinCredentials } from '@/lib/im/adapters/weixin/weixin-types'

export interface ProvisionResult {
  userId: string
  userDir: string
  channelId: string
  workspaceId: string
  workspacePath: string
}

// Files symlinked from the host's .claude/ (read-only, always in sync).
const SHARED_LINKS: ReadonlyArray<{ name: string; kind: 'dir' | 'file' }> = [
  { name: 'skills', kind: 'dir' },
  { name: 'agents', kind: 'dir' },
  { name: 'rules', kind: 'dir' },
  { name: 'CLAUDE.md', kind: 'file' },
]

// Files copied from the host's .claude/ as an independent per-user snapshot.
const INDEPENDENT_COPIES: ReadonlyArray<string> = [
  'settings.local.json',
  'MEMORY.md', 'IDENTITY.md', 'SOUL.md', 'USER.md', 'HEARTBEAT.md',
]

/**
 * Provision a new user workspace after successful WeChat Bot authorization.
 *
 * @param projectRoot - The host's project root (e.g. /Users/xxx/workspace/forge)
 * @param phoneNumber - The invitee's verified phone number
 * @param credentials - WeChat iLink bot credentials from QR scan
 * @param inviterUserId - The inviting user's id; becomes the owner of all created
 *                        resources (im_channel, workspace). The invitee is NOT a
 *                        user in the system — they are scoped to the channel/chat.
 */
export function provisionUser(
  projectRoot: string,
  phoneNumber: string,
  credentials: WeixinCredentials & { ilinkUserId?: string },
  inviterUserId: string,
): ProvisionResult {
  const db = getDb()

  const userDirName = makeUserDirName(phoneNumber)
  const userDir = path.join(projectRoot, 'users', userDirName)
  const workspacePath = userDir
  const workspaceId = crypto.randomUUID()
  const channelId = crypto.randomUUID()
  // userId is the suffix of the dir name (e.g. "8000-20260621-k7f") — used
  // for chat_id fallback and stored in invitations.created_user_id.
  const userId = userDirName.slice('user-'.length)
  const claudeUserDir = setupUserWorkspace(userDir, projectRoot)
  const credentialsJson = buildCredentialsJson(credentials)
  const ownerUserId = inviterUserId
  const resolvedModel = resolveDefaultModel(ownerUserId)

  registerWorkspaceAndChannel(db, {
    channelId, userDirName, credentialsJson, workspaceId, workspacePath,
    ownerUserId, resolvedModel, userId, credentials,
  })

  return { userId, userDir, channelId, workspaceId, workspacePath }
}

// ── fs layout ─────────────────────────────────────────────────────────

function setupUserWorkspace(userDir: string, projectRoot: string): string {
  const claudeUserDir = path.join(userDir, '.claude')
  // Host resources live under the invite's projectRoot (the workspace being
  // invited into), not process.cwd() — the server cwd is a fixed deploy dir
  // with no workspace skills. invite.project_root comes from ws.path.
  const hostClaudeDir = path.join(projectRoot, '.claude')
  fs.mkdirSync(userDir, { recursive: true })
  fs.mkdirSync(claudeUserDir, { recursive: true })

  for (const { name, kind } of SHARED_LINKS) {
    const src = path.join(hostClaudeDir, name)
    if (fs.existsSync(src)) {
      fs.symlinkSync(src, path.join(claudeUserDir, name), kind)
    }
  }
  for (const name of INDEPENDENT_COPIES) {
    const src = path.join(hostClaudeDir, name)
    if (fs.existsSync(src)) {
      fs.copyFileSync(src, path.join(claudeUserDir, name))
    }
  }
  // .env and .pi are intentionally omitted per user's requirement
  return claudeUserDir
}

// ── credentials JSON ──────────────────────────────────────────────────

function buildCredentialsJson(credentials: WeixinCredentials & { ilinkUserId?: string }): string {
  // bot_token format: "appid:secret". Fall back to ilinkBotId for appid
  // when the separator is missing (some upstream flows).
  const colonIdx = credentials.botToken.indexOf(':')
  const appid = colonIdx !== -1 ? credentials.botToken.substring(0, colonIdx) : credentials.ilinkBotId
  const secret = colonIdx !== -1 ? credentials.botToken.substring(colonIdx + 1) : credentials.botToken
  return JSON.stringify({
    token: credentials.botToken,
    appid,
    secret,
    ilink_bot_id: credentials.ilinkBotId,
    baseurl: credentials.baseUrl || 'https://ilinkai.weixin.qq.com',
    cdn_baseurl: credentials.cdnBaseUrl || 'https://novac2c.cdn.weixin.qq.com/c2c',
    ...(credentials.ilinkUserId ? { ilink_user_id: credentials.ilinkUserId } : {}),
  })
}

// ── default model lookup ──────────────────────────────────────────────

function resolveDefaultModel(ownerUserId: string): string {
  // 用户级覆盖优先，回退全局 settings；与 settingsRepo.getEffective 语义一致。
  return getSetting('bridge_default_model', ownerUserId) ?? ''
}

// ── registration (workspaces, im_channels, channel_bindings) ─────────

interface RegisterArgs {
  channelId: string
  userDirName: string
  credentialsJson: string
  workspaceId: string
  workspacePath: string
  ownerUserId: string
  resolvedModel: string
  userId: string
  credentials: WeixinCredentials & { ilinkUserId?: string }
}

function registerWorkspaceAndChannel(db: Database, a: RegisterArgs): void {
  // chat_id is the invitee's ilink_user_id (per-user identity at the bot),
  // NOT ilinkBotId (the bot's own id). channel-router matches against this.
  const chatId = a.credentials.ilinkUserId || `user-${a.userId}`

  db.transaction((tx) => {
    tx.insert(workspaces)
      .values({
        id: a.workspaceId,
        path: a.workspacePath,
        ownerUserId: a.ownerUserId,
      })
      .run()
    tx.insert(imChannels)
      .values({
        id: a.channelId,
        type: 'weixin',
        name: `Bot ${a.userDirName}`,
        enabled: 1,
        status: 'connected',
        credentials: a.credentialsJson,
        defaultWorkspaceId: a.workspaceId,
        defaultModel: a.resolvedModel,
        dmPolicy: 'open',
        groupPolicy: 'open',
        triggerMode: 'mention',
        groupWhitelist: '[]',
        senderWhitelist: '[]',
        ownerUserId: a.ownerUserId,
      })
      .run()
    tx.insert(channelBindings)
      .values({
        id: crypto.randomUUID(),
        channelId: a.channelId,
        chatId,
        chatName: `User ${a.userDirName}`,
        workspace: a.workspacePath,
        workspaceId: a.workspaceId,
      })
      .run()
  })
}
