/**
 * Browser 温池：按 profile:sessionKey 复用 Browser，降低冷启动与 CF 触发
 *
 * 同一 key 的 acquire 在 inUse 时排队等待，禁止销毁正在使用的 Browser（修复 A2C 并发误杀）。
 */
const { POOL_IDLE_MS, MAX_POOLED_BROWSERS, POOL_ENABLED, QUEUE_TIMEOUT_MS } = require('./constants');

class BrowserPool {
    constructor() {
        /** @type {Map<string, { browser: any, page: any|null, profile: string, sessionKey: string, lastUsedAt: number, inUse: boolean }>} */
        this.entries = new Map();
        /** @type {Map<string, Array<{ resolve: () => void, reject: (err: Error) => void, timer: NodeJS.Timeout|null }>>} */
        this._waitQueues = new Map();
        this.hits = 0;
        this.misses = 0;
        this.evictions = 0;
        this._sweepTimer = setInterval(() => this.evictIdle(), 30000);
        this._sweepTimer.unref?.();
    }

    buildKey(sessionKey, profile) {
        return `${profile}:${sessionKey}`;
    }

    async isBrowserAlive(browser) {
        if (!browser) return false;
        try {
            if (typeof browser.isConnected === 'function' && !browser.isConnected()) {
                return false;
            }
            await browser.version();
            return true;
        } catch (_) {
            return false;
        }
    }

    async destroyEntry(key) {
        const entry = this.entries.get(key);
        if (!entry) return;
        if (entry.inUse) {
            console.warn(`[BrowserPool] 跳过销毁 inUse 条目 key=${key}`);
            return;
        }
        this.entries.delete(key);
        try {
            await entry.browser.close();
        } catch (_) {
            /* ignore */
        }
        this.evictions++;
    }

    enforceCapacity(excludeKey) {
        if (this.entries.size < MAX_POOLED_BROWSERS) return;
        const candidates = [...this.entries.entries()]
            .filter(([k, e]) => k !== excludeKey && !e.inUse)
            .sort((a, b) => a[1].lastUsedAt - b[1].lastUsedAt);
        if (candidates.length === 0) return;
        const [oldestKey] = candidates[0];
        this.destroyEntry(oldestKey);
    }

    waitForRelease(key, timeoutMs = QUEUE_TIMEOUT_MS) {
        const entry = this.entries.get(key);
        if (!entry || !entry.inUse) {
            return Promise.resolve();
        }
        return new Promise((resolve, reject) => {
            const waiter = {
                resolve: () => {
                    if (waiter.timer) clearTimeout(waiter.timer);
                    resolve();
                },
                reject,
                timer: timeoutMs > 0
                    ? setTimeout(() => {
                        const queue = this._waitQueues.get(key) || [];
                        const idx = queue.indexOf(waiter);
                        if (idx >= 0) queue.splice(idx, 1);
                        reject(new Error('POOL_ACQUIRE_TIMEOUT'));
                    }, timeoutMs)
                    : null,
            };
            if (!this._waitQueues.has(key)) {
                this._waitQueues.set(key, []);
            }
            this._waitQueues.get(key).push(waiter);
            console.log(`[BrowserPool] key=${key} 正在使用，排队等待 (队列 ${this._waitQueues.get(key).length})`);
        });
    }

    _wakeNextWaiter(key) {
        const queue = this._waitQueues.get(key);
        if (!queue || queue.length === 0) return;
        const next = queue.shift();
        if (next?.timer) clearTimeout(next.timer);
        next?.resolve();
    }

    async acquire(sessionKey, profile, launcher) {
        if (!POOL_ENABLED || !sessionKey) {
            const launched = await launcher();
            return {
                browser: launched.browser,
                page: launched.page,
                profile,
                pooled: false,
                reused: false,
                release: async () => {
                    await launched.cleanup(true);
                },
            };
        }

        const key = this.buildKey(sessionKey, profile);

        while (true) {
            const existing = this.entries.get(key);
            if (!existing) {
                break;
            }
            if (existing.inUse) {
                await this.waitForRelease(key);
                continue;
            }
            if (await this.isBrowserAlive(existing.browser)) {
                existing.inUse = true;
                existing.lastUsedAt = Date.now();
                this.hits++;
                return {
                    browser: existing.browser,
                    page: null,
                    profile,
                    pooled: true,
                    reused: true,
                    release: async (destroy = false) => this.release(key, destroy),
                };
            }
            await this.destroyEntry(key);
            break;
        }

        this.enforceCapacity(key);
        this.misses++;
        const launched = await launcher();
        this.entries.set(key, {
            browser: launched.browser,
            page: null,
            profile,
            sessionKey,
            lastUsedAt: Date.now(),
            inUse: true,
        });

        return {
            browser: launched.browser,
            page: launched.page,
            profile,
            pooled: true,
            reused: false,
            release: async (destroy = false) => this.release(key, destroy),
        };
    }

    async release(key, destroy = false) {
        const entry = this.entries.get(key);
        if (!entry) {
            this._wakeNextWaiter(key);
            return;
        }
        entry.inUse = false;
        entry.lastUsedAt = Date.now();
        entry.page = null;
        if (destroy || !(await this.isBrowserAlive(entry.browser))) {
            await this.destroyEntry(key);
        }
        this._wakeNextWaiter(key);
    }

    evictIdle() {
        const now = Date.now();
        for (const [key, entry] of this.entries.entries()) {
            if (!entry.inUse && now - entry.lastUsedAt > POOL_IDLE_MS) {
                this.destroyEntry(key);
            }
        }
    }

    async drain() {
        clearInterval(this._sweepTimer);
        const keys = [...this.entries.keys()];
        for (const key of keys) {
            const entry = this.entries.get(key);
            if (entry && !entry.inUse) {
                await this.destroyEntry(key);
            }
        }
    }

    getStats() {
        let idle = 0;
        let active = 0;
        let waiting = 0;
        for (const entry of this.entries.values()) {
            if (entry.inUse) active++;
            else idle++;
        }
        for (const queue of this._waitQueues.values()) {
            waiting += queue.length;
        }
        return {
            enabled: POOL_ENABLED,
            max: MAX_POOLED_BROWSERS,
            idleMs: POOL_IDLE_MS,
            size: this.entries.size,
            idle,
            active,
            waiting,
            hits: this.hits,
            misses: this.misses,
            evictions: this.evictions,
        };
    }
}

const browserPool = new BrowserPool();

module.exports = {
    BrowserPool,
    browserPool,
};
