/**
 * Feishu per-user OAuth — token lifecycle (load / verify / refresh / device flow).
 *
 * 从 bridge-manager 迁入（C-1）：桥管理器不再直接操作 token-store / oauth-flow，
 * 改由 feishu adapter 通过 ensureChannelAuth() 调用本模块。逻辑与迁移前逐行等价，
 * 仅 this→adapter 参数化、OAuth 失败通知由 delivery.deliver 改为 adapter.send。
 */

import type { IncomingMessage } from '../../types'
import type { PlatformContext } from '@/lib/types'
import type { FeishuAdapter } from './adapter'
import { readFeishuToken, writeFeishuToken, deleteFeishuToken, checkTokenStatus } from '@/lib/feishu/token-store'
import {
    requestDeviceAuthorization,
    pollToken,
    refreshUserToken,
    verifyUserToken,
} from '@/lib/feishu/oauth-flow'
import { FORGE_FEISHU_SCOPES } from '@/lib/feishu/scopes'

/** Token 服务器端校验节流间隔（10 分钟）。 */
export const FEISHU_VERIFY_INTERVAL_MS = 10 * 60 * 1000

type FeishuAuth = NonNullable<PlatformContext['feishu']>

/**
 * Compute which required Forge scopes were NOT requested at the token's last
 * authorization. Used to detect tokens granted before a scope was *added to
 * the code*, so they can be transparently re-authorized exactly once.
 *
 * IMPORTANT: compares against REQUESTED scopes, not granted ones. The server
 * may refuse to grant a requested scope permanently; comparing against granted
 * scopes would report it missing forever and re-authorize every message.
 */
function missingFeishuScopes(requestedScope: string | undefined): string[] {
    const requested = new Set((requestedScope ?? '').split(/\s+/).filter(Boolean))
    return FORGE_FEISHU_SCOPES.filter((s) => s !== 'offline_access' && !requested.has(s))
}

/**
 * Ensure the Feishu user has a valid OAuth token before launching the SDK query.
 *
 * Flow: scope coverage check → locally-valid cached token → throttled server
 * verify → refresh → Device Flow. Returns undefined when per-user auth is
 * disabled (FORGE_FEISHU_USER_AUTH=false) or all attempts fail.
 */
export async function ensureFeishuUserAuth(
    adapter: FeishuAdapter,
    msg: IncomingMessage,
): Promise<FeishuAuth | undefined> {
    console.log(`[ensureFeishuAuth] called channelType=${msg.channelType}, adapter=${adapter.constructor.name}, senderId=${msg.senderId.slice(0, 12)}...`)

    // Feature switch FORGE_FEISHU_USER_AUTH (default on). When explicitly
    // set to 'false', skip the entire per-user OAuth flow: no token lookup,
    // no OAuth card sent to IM, no device-code polling. The SDK query runs
    // on without a user access token (same as the failed-auth fallback).
    if (process.env.FORGE_FEISHU_USER_AUTH === 'false') {
        console.log(`[ensureFeishuAuth] disabled by FORGE_FEISHU_USER_AUTH=false; running without user token`)
        return undefined
    }

    const creds = adapter.getCredentials()
    console.log(`[ensureFeishuAuth] credentials appId=${creds.appId.slice(0, 12)}..., platform=${creds.platform}`)
    const toAuth = (token: string): FeishuAuth => ({ appId: creds.appId, token, brand: creds.platform })
    const stored = readFeishuToken(creds.appId, msg.senderId)
    console.log(`[ensureFeishuAuth] stored token exists=${!!stored}, scopesCount=${FORGE_FEISHU_SCOPES.length}`)

    let token: string | undefined

    // ── Scope coverage check ──
    if (stored) {
        const missing = missingFeishuScopes(stored.requestedScope)
        console.log(`[ensureFeishuAuth] missing scopes count=${missing.length}, examples=${missing.slice(0, 5).join(', ')}`)
        if (missing.length > 0) {
            console.warn(`[feishu-auth] token for ${msg.senderId.slice(0, 12)}... is missing ${missing.length} scope(s) (e.g. ${missing.slice(0, 3).join(', ')}); re-authorizing`)
            token = await startFeishuDeviceFlow(adapter, msg)
            return token ? toAuth(token) : undefined
        }
    }

    // ── Token valid locally ──
    if (stored && checkTokenStatus(stored) === 'valid') {
        const lastVerified = stored.lastVerifiedAt ?? stored.grantedAt
        console.log(`[ensureFeishuAuth] token valid locally, lastVerified=${new Date(lastVerified).toISOString()}, interval=${FEISHU_VERIFY_INTERVAL_MS}ms`)
        if (Date.now() - lastVerified < FEISHU_VERIFY_INTERVAL_MS) {
            console.log(`[ensureFeishuAuth] using cached token (within verify interval)`)
            token = stored.accessToken
        } else {
            // Throttled server-side check: catches server-side revocation
            console.log(`[ensureFeishuAuth] verifying token server-side...`)
            try {
                if (await verifyUserToken(stored.accessToken, creds.platform)) {
                    console.log(`[ensureFeishuAuth] server verification passed`)
                    writeFeishuToken(creds.appId, msg.senderId, { ...stored, lastVerifiedAt: Date.now() })
                    token = stored.accessToken
                } else {
                    console.warn(`[feishu-auth] token rejected server-side for ${msg.senderId.slice(0, 12)}..., trying refresh`)
                    deleteFeishuToken(creds.appId, msg.senderId)
                    // Fall through to refresh below (using the in-memory `stored`)
                }
            } catch (err) {
                // Network error — keep using the locally-valid token
                console.warn('[feishu-auth] token verification skipped:', err instanceof Error ? err.message : err)
                token = stored.accessToken
            }
        }
    }

    // ── Token needs refresh (near expiry or rejected server-side) ──
    if (token === undefined && stored && Date.now() < stored.refreshExpiresAt) {
        console.log(`[ensureFeishuAuth] token needs refresh, attempting refresh...`)
        try {
            const refreshed = await refreshUserToken(creds.appId, creds.appSecret, stored.refreshToken, creds.platform)
            const updated = {
                accessToken: refreshed.accessToken,
                refreshToken: refreshed.refreshToken,
                expiresAt: Date.now() + refreshed.expiresIn * 1000,
                refreshExpiresAt: stored.refreshExpiresAt,
                scope: refreshed.scope || FORGE_FEISHU_SCOPES.join(' '),
                requestedScope: stored.requestedScope,
                grantedAt: stored.grantedAt,
                lastVerifiedAt: Date.now(),
            }
            writeFeishuToken(creds.appId, msg.senderId, updated)
            console.log(`[feishu-auth] token refreshed for ${msg.senderId.slice(0, 12)}...`)
            token = updated.accessToken
        } catch (err) {
            console.warn('[feishu-auth] token refresh failed:', err instanceof Error ? err.message : err)
            // Fall through to OAuth
        }
    }

    // ── Token expired / refresh failed → start Device Flow ──
    if (token === undefined) {
        console.log(`[ensureFeishuAuth] no valid token, starting device flow...`)
        token = await startFeishuDeviceFlow(adapter, msg)
    }

    return token ? toAuth(token) : undefined
}

/**
 * Run the Feishu OAuth Device Flow: request a device code, send the OAuth
 * card to the user, poll until they authorise, then persist and return the
 * new access token. Returns undefined if the flow fails.
 *
 * IMPORTANT: In group chats, the OAuth card is sent to the user's DM
 * (via open_id) to avoid leaking the verification code to all members.
 */
async function startFeishuDeviceFlow(adapter: FeishuAdapter, msg: IncomingMessage): Promise<string | undefined> {
    console.log(`[startFeishuDeviceFlow] starting device flow for senderId=${msg.senderId.slice(0, 12)}..., isDm=${msg.isDm}`)
    const creds = adapter.getCredentials()
    try {
        const deviceAuth = await requestDeviceAuthorization(creds.appId, creds.appSecret, creds.platform, FORGE_FEISHU_SCOPES)
        console.log(`[startFeishuDeviceFlow] device code obtained, verificationUri=${deviceAuth.verificationUri.slice(0, 40)}...`)

        // Send OAuth card — use DM in group chats to prevent leaking the
        // verification code to all group members.
        if (msg.isDm) {
            console.log(`[startFeishuDeviceFlow] sending OAuth card to chat ${msg.chatId.slice(0, 12)}...`)
            await adapter.sendOAuthCard(msg.chatId, deviceAuth.verificationUriComplete, deviceAuth.userCode)
        } else {
            console.log(`[startFeishuDeviceFlow] sending OAuth card to user DM ${msg.senderId.slice(0, 12)}...`)
            await adapter.sendOAuthCardToUser(msg.senderId, deviceAuth.verificationUriComplete, deviceAuth.userCode)
        }

        // Poll for user authorisation
        console.log(`[startFeishuDeviceFlow] polling for authorization...`)
        const result = await pollToken(
            creds.appId,
            creds.appSecret,
            deviceAuth.deviceCode,
            deviceAuth.interval,
            undefined,
            creds.platform,
        )
        console.log(`[startFeishuDeviceFlow] authorization completed, accessToken obtained`)

        const newToken = {
            accessToken: result.accessToken,
            refreshToken: result.refreshToken,
            expiresAt: Date.now() + result.expiresIn * 1000,
            refreshExpiresAt: Date.now() + result.refreshExpiresIn * 1000,
            scope: result.scope || FORGE_FEISHU_SCOPES.join(' '),
            requestedScope: FORGE_FEISHU_SCOPES.join(' '),
            grantedAt: Date.now(),
        }
        writeFeishuToken(creds.appId, msg.senderId, newToken)
        console.log(`[feishu-auth] OAuth completed for ${msg.senderId.slice(0, 12)}...`)
        return newToken.accessToken
    } catch (err) {
        const msg_text = err instanceof Error ? err.message : String(err)
        console.warn('[feishu-auth] OAuth failed:', msg_text)
        // Notify user, then return undefined (SDK query runs without token)
        await adapter.send({ chatId: msg.chatId, text: `❌ 飞书授权失败: ${msg_text}` })
        return undefined
    }
}
