import { normalizeStreamToken } from '@/lib/streams/cloudflareStreamUrls'
import {
    isStreamLiveByLifecycleStatus,
    isStreamOfflineByLifecycleStatus,
    normalizeLifecycleStatus,
} from '@/lib/streams/lifecycleStatus'
import { resolveRoomStreamTitle } from '@/lib/streams/streamTitle'
import { pickRowString } from '@/server/pgRow'

const LIVE_INPUT_UID_RE = /^[a-f0-9]{32}$/i
/** Dashboard WebRTC 串流金鑰：32 字元 hex + k + 32 字元 Live Input uid */
export const CLOUDFLARE_LIVE_INPUT_KEY_LENGTH = 65
export const CLOUDFLARE_LIVE_INPUT_KEY_RE = /^[a-f0-9]{32}k[a-f0-9]{32}$/i
/** Dashboard 儲存的 Live Input 識別字串（含 WebRTC path 區段） */
const LIVE_INPUT_IDENTIFIER_RE = /^[a-zA-Z0-9_-]{1,200}$/

export type DashboardLiveInputKeyValidation =
    | { ok: true }
    | { ok: false; reason: 'length' | 'format' }

/** 空白視為有效；有內容時須為 65 字元且符合 Cloudflare 金鑰格式 */
export function validateDashboardLiveInputKey(input: string): DashboardLiveInputKeyValidation {
    const raw = String(input ?? '').trim()
    if (!raw) return { ok: true }
    if (raw.length !== CLOUDFLARE_LIVE_INPUT_KEY_LENGTH) {
        return { ok: false, reason: 'length' }
    }
    if (!CLOUDFLARE_LIVE_INPUT_KEY_RE.test(raw)) {
        return { ok: false, reason: 'format' }
    }
    return { ok: true }
}

export type CloudflareLiveInputStatusEntry = {
    state?: string
    statusEnteredAt?: string
    statusLastSeen?: string
    ingestProtocol?: string
}

export type CloudflareLiveInputStatus = {
    current?: CloudflareLiveInputStatusEntry
    history?: CloudflareLiveInputStatusEntry[]
}

export type CloudflareLiveInputResult = {
    uid: string
    rtmps?: { url?: string; streamKey?: string }
    webRTC?: { url?: string }
    webRTCPlayback?: { url?: string }
    meta?: { name?: string }
    status?: CloudflareLiveInputStatus
    enabled?: boolean
}

export function parseCfStatusEnteredAtSec(enteredAt: string | null | undefined): number | null {
    if (!enteredAt) return null
    const ms = Date.parse(enteredAt)
    return Number.isNaN(ms) ? null : Math.floor(ms / 1000)
}

function collectStatusTimeline(
    status: CloudflareLiveInputStatus | null | undefined
): { state: string; sec: number }[] {
    const entries: CloudflareLiveInputStatusEntry[] = []
    if (status?.current) entries.push(status.current)
    if (Array.isArray(status?.history)) entries.push(...status.history)

    const timeline: { state: string; sec: number }[] = []
    for (const entry of entries) {
        const sec = parseCfStatusEnteredAtSec(entry.statusEnteredAt)
        if (sec == null) continue
        timeline.push({
            state: normalizeLifecycleStatus(entry.state),
            sec,
        })
    }
    timeline.sort((a, b) => a.sec - b.sec)
    return timeline
}

/**
 * 已結束的那一場 connected 開播時間（disconnected 前一個 connected）。
 * 僅供顯示「上一場」參考，勿在下播時寫入 DB。
 */
export function getLastEndedSessionStartedAtSec(
    status: CloudflareLiveInputStatus | null | undefined
): number | null {
    const timeline = collectStatusTimeline(status)
    let pendingConnected: number | null = null
    let lastEndedSessionStart: number | null = null

    for (const { state, sec } of timeline) {
        if (isStreamLiveByLifecycleStatus(state)) {
            pendingConnected = sec
        } else if (isStreamOfflineByLifecycleStatus(state) && pendingConnected != null) {
            lastEndedSessionStart = pendingConnected
            pendingConnected = null
        }
    }
    return lastEndedSessionStart
}

/** CF 曾下播／重連過會出現 history；有 history 即視為本場直播已結束（不看 current.state） */
export function hasCfLiveHistory(
    status: CloudflareLiveInputStatus | null | undefined
): boolean {
    return Array.isArray(status?.history) && status.history.length > 0
}

/** 與 hasCfLiveHistory 相同；語意為「已結束／離線」 */
export function isCfLiveInputEnded(
    status: CloudflareLiveInputStatus | null | undefined
): boolean {
    return hasCfLiveHistory(status)
}

/** 開播中：無 history 且 current 為直播中狀態 */
export function isCfLiveInputBroadcasting(
    status: CloudflareLiveInputStatus | null | undefined
): boolean {
    if (isCfLiveInputEnded(status)) return false
    const currentState = normalizeLifecycleStatus(status?.current?.state ?? 'disconnected')
    return isStreamLiveByLifecycleStatus(currentState)
}

/** 寫入 started_at：僅本場開播中（無 history）取 current.statusEnteredAt */
export function resolveLiveInputStartedAt(
    status: CloudflareLiveInputStatus | null | undefined
): number | null {
    if (!isCfLiveInputBroadcasting(status)) return null
    return parseCfStatusEnteredAtSec(status?.current?.statusEnteredAt)
}

export type RoomStreamFieldsFromLiveInput = {
    stream_token: string
    stream_status: string
    title: string | null
    started_at: number | null
    webrtc_publish_url: string | null
}

/** 寫入 room_streams.stream_status：有 history 一律 disconnected，否則才看 current.state */
export function resolveRoomStreamStatusFromCfStatus(
    status: CloudflareLiveInputStatus | null | undefined
): string {
    if (isCfLiveInputEnded(status)) return 'disconnected'
    return normalizeLifecycleStatus(status?.current?.state ?? 'disconnected')
}

export function getCloudflareAccountId(): string {
    return String(process.env.CLOUDFLARE_ACCOUNT_ID ?? '').trim()
}

export function getCloudflareApiToken(): string {
    return String(process.env.CLOUDFLARE_API_TOKEN ?? '').trim()
}

/** 寫入 room_streams.live_input_identifier */
export function coerceLiveInputIdentifierForStorage(input: string): string | null {
    const raw = String(input ?? '').trim()
    if (!raw) return null

    if (/\/webrtc\/publish\/?$/i.test(raw.split('?')[0] ?? '')) {
        try {
            const parts = new URL(raw).pathname.split('/').filter(Boolean)
            const publishIdx = parts.findIndex((p) => /^webrtc$/i.test(p))
            if (publishIdx > 0) {
                const seg = parts[publishIdx - 1]
                if (seg && LIVE_INPUT_IDENTIFIER_RE.test(seg)) return seg
            }
        } catch {
            // fall through
        }
    }

    if (
        raw.length === CLOUDFLARE_LIVE_INPUT_KEY_LENGTH &&
        CLOUDFLARE_LIVE_INPUT_KEY_RE.test(raw)
    ) {
        return raw
    }

    if (LIVE_INPUT_IDENTIFIER_RE.test(raw)) return raw
    return null
}

/**
 * Cloudflare Live Input API 路徑用的 32 字元 uid。
 * 例：…dcb989k9c4a57785b8a7732a1bf5aee5f556508 → 9c4a57785b8a7732a1bf5aee5f556508
 */
export function parseLiveInputApiId(identifier: string): string | null {
    const raw = String(identifier ?? '').trim()
    if (!raw) return null
    if (LIVE_INPUT_UID_RE.test(raw)) return raw.toLowerCase()

    const kSuffix = raw.match(/k([a-f0-9]{32})$/i)
    if (kSuffix?.[1]) return kSuffix[1].toLowerCase()

    const token = normalizeStreamToken(raw)
    if (token) return token

    return null
}

export function mapLiveInputToRoomStreamFields(
    result: CloudflareLiveInputResult,
    options?: { titleFallback?: string }
): RoomStreamFieldsFromLiveInput {
    const uid = String(result.uid ?? '').trim().toLowerCase()
    const status = result.status
    const broadcasting = isCfLiveInputBroadcasting(status)
    const state = resolveRoomStreamStatusFromCfStatus(status)
    const title = resolveRoomStreamTitle(options?.titleFallback)
    const started_at = broadcasting
        ? resolveLiveInputStartedAt(status)
        : getLastEndedSessionStartedAtSec(status)
    const webrtc_publish_url = readCfWebRtcPublishUrl(result) || null

    return {
        stream_token: uid,
        stream_status: state,
        title,
        started_at,
        webrtc_publish_url,
    }
}

export type ApplyLiveInputSyncResult = {
    streamStatus: string
    broadcasting: boolean
    startedAt: number | null
}

/** 將 CF live_inputs 結果寫入 room_streams（與 WS 輪詢同一套規則） */
export async function applyLiveInputSyncToRoomStream(
    db: {
        get: <T>(sql: string, params?: unknown[]) => Promise<T | undefined>
        run: (sql: string, params?: unknown[]) => Promise<unknown>
    },
    roomId: string,
    result: CloudflareLiveInputResult,
    options?: { titleFallback?: string; nowSec?: number }
): Promise<ApplyLiveInputSyncResult> {
    const cfStatus = result.status
    const streamStatus = resolveRoomStreamStatusFromCfStatus(cfStatus)
    const broadcasting = isCfLiveInputBroadcasting(cfStatus)
    const sync = mapLiveInputToRoomStreamFields(result, options)
    sync.stream_status = streamStatus

    const nowSec = options?.nowSec ?? Math.floor(Date.now() / 1000)

    const dbRow = await db.get<{
        streamStatus: string | null
        startedAt: number | null
    }>(
        `SELECT stream_status AS streamStatus, started_at AS startedAt FROM room_streams WHERE room_id = ?`,
        [roomId]
    )

    let startedAtSec =
        dbRow?.startedAt != null && Number(dbRow.startedAt) > 0 ? Number(dbRow.startedAt) : null

    const startedWrite = broadcasting ? (sync.started_at ?? startedAtSec) : (sync.started_at ?? startedAtSec)

    if (startedWrite != null) {
        await db.run(
            `UPDATE room_streams
             SET stream_token = ?,
                 stream_status = ?,
                 started_at = ?,
                 updated_at = ?
             WHERE room_id = ?`,
            [sync.stream_token, streamStatus, startedWrite, nowSec, roomId]
        )
        startedAtSec = startedWrite
    } else {
        await db.run(
            `UPDATE room_streams
             SET stream_token = ?,
                 stream_status = ?,
                 updated_at = ?
             WHERE room_id = ?`,
            [sync.stream_token, streamStatus, nowSec, roomId]
        )
    }

    return {
        streamStatus,
        broadcasting,
        startedAt: startedAtSec,
    }
}

export async function syncRoomStreamFromCloudflareLiveInput(
    db: {
        get: <T>(sql: string, params?: unknown[]) => Promise<T | undefined>
        run: (sql: string, params?: unknown[]) => Promise<unknown>
    },
    roomId: string,
    options?: { titleFallback?: string }
): Promise<{ ok: true; applied: ApplyLiveInputSyncResult } | { ok: false; error: string }> {
    const row = await db.get<Record<string, unknown>>(
        `SELECT live_input_identifier FROM room_streams WHERE room_id = ?`,
        [roomId]
    )
    const identifier = pickRowString(row, 'live_input_identifier', 'liveInputIdentifier')
    if (!identifier) {
        return { ok: false, error: 'live_input_identifier is not set' }
    }

    const apiId = parseLiveInputApiId(identifier)
    if (!apiId) {
        return { ok: false, error: 'Invalid live input identifier' }
    }

    const fetched = await fetchCloudflareLiveInput(apiId)
    if (!fetched.ok) {
        return { ok: false, error: fetched.error }
    }

    const applied = await applyLiveInputSyncToRoomStream(db, roomId, fetched.result, options)
    return { ok: true, applied }
}

function readCfWebRtcPublishUrl(result: CloudflareLiveInputResult): string {
    const row = result as CloudflareLiveInputResult & {
        webrtc?: { url?: string }
    }
    return String(result.webRTC?.url ?? row.webrtc?.url ?? '').trim()
}

/** 從 CF Live Input 回應解析可寫入 live_input_identifier 的金鑰 */
export function extractLiveInputIdentifierFromCfResult(
    result: CloudflareLiveInputResult
): string | null {
    const candidates = [
        readCfWebRtcPublishUrl(result),
        result.rtmps?.streamKey,
        result.rtmps?.url,
    ]
    for (const candidate of candidates) {
        const raw = String(candidate ?? '').trim()
        if (!raw) continue
        const stored = coerceLiveInputIdentifierForStorage(raw)
        if (stored) return stored
    }
    return null
}

export type CreateCloudflareLiveInputOptions = {
    /** CF meta.name（主播 user_id） */
    name: string
    deleteRecordingAfterDays?: number
}

export async function createCloudflareLiveInput(
    options: CreateCloudflareLiveInputOptions
): Promise<
    | { ok: true; result: CloudflareLiveInputResult; liveInputIdentifier: string }
    | { ok: false; error: string }
> {
    const accountId = getCloudflareAccountId()
    const apiToken = getCloudflareApiToken()
    const name = String(options.name ?? '').trim()

    if (!accountId || !apiToken) {
        return { ok: false, error: 'Cloudflare API credentials are not configured' }
    }
    if (!name) {
        return { ok: false, error: 'Live input name is required' }
    }

    const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/stream/live_inputs`
    const res = await fetch(url, {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${apiToken}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            deleteRecordingAfterDays: options.deleteRecordingAfterDays ?? 45,
            enabled: true,
            meta: { name },
            recording: {
                hideLiveViewerCount: false,
                mode: 'off',
                requireSignedURLs: false,
                timeoutSeconds: 0,
            },
        }),
        cache: 'no-store',
    })

    const json = (await res.json().catch(() => null)) as {
        success?: boolean
        result?: CloudflareLiveInputResult
        errors?: { message?: string }[]
    } | null

    if (!res.ok || !json?.success || !json.result?.uid) {
        const msg =
            json?.errors?.[0]?.message ||
            `Cloudflare API error (${res.status})`
        return { ok: false, error: msg }
    }

    const liveInputIdentifier = extractLiveInputIdentifierFromCfResult(json.result)
    if (!liveInputIdentifier) {
        return { ok: false, error: 'Could not resolve stream key from Cloudflare response' }
    }

    return { ok: true, result: json.result, liveInputIdentifier }
}

export async function fetchCloudflareLiveInput(
    apiId: string
): Promise<{ ok: true; result: CloudflareLiveInputResult } | { ok: false; error: string }> {
    const accountId = getCloudflareAccountId()
    const apiToken = getCloudflareApiToken()
    const uid = parseLiveInputApiId(apiId) ?? apiId.trim().toLowerCase()

    if (!accountId || !apiToken) {
        return { ok: false, error: 'Cloudflare API credentials are not configured' }
    }
    if (!LIVE_INPUT_UID_RE.test(uid)) {
        return { ok: false, error: 'Invalid live input id' }
    }

    const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/stream/live_inputs/${uid}`
    const res = await fetch(url, {
        headers: {
            Authorization: `Bearer ${apiToken}`,
            'Content-Type': 'application/json',
        },
        cache: 'no-store',
    })

    const json = (await res.json().catch(() => null)) as {
        success?: boolean
        result?: CloudflareLiveInputResult
        errors?: { message?: string }[]
    } | null

    if (!res.ok || !json?.success || !json.result?.uid) {
        const msg =
            json?.errors?.[0]?.message ||
            `Cloudflare API error (${res.status})`
        return { ok: false, error: msg }
    }

    return { ok: true, result: json.result }
}
