// src/modules/x-browser/x-browser.service.ts
import {
    Injectable,
    Logger,
    OnModuleInit,
    OnModuleDestroy, HttpException,
} from '@nestjs/common';
import {chromium, Browser, BrowserContext, Page} from 'playwright';
import {pick, rand} from "../../shared/helper";
import {buildXCookies} from "./utils/x-headers.util";

export interface BrowserAccount {
    accountId: string;
    cookies: Array<{
        name: string;
        value: string;
        domain?: string;
        path?: string;
    }>;
    proxy?: string;
    userAgent?: string;
}

export interface BrowserTweetResult {
    success: boolean;
    tweetId?: string;
    error?: string;
    needsRelogin?: boolean;
}

@Injectable()
export class XBrowserService implements OnModuleInit, OnModuleDestroy {
    private readonly logger = new Logger(XBrowserService.name);
    private browser: Browser | null = null;
    private contextPool = new Map<
        string,
        { ctx: BrowserContext; lastUsed: number }
    >();
    private readonly MAX_CONTEXTS = 5;
    private readonly CONTEXT_TTL_MS = 15 * 60 * 1000; // 15 phút

    async onModuleInit() {
        // Lazy launch – chỉ mở khi cần
        setInterval(() => this.cleanupStaleContexts(), 60_000);
    }

    private async ensureBrowser(): Promise<Browser> {
        if (this.browser && this.browser.isConnected()) return this.browser;
        this.logger.log('Launching Chromium...');
        this.browser = await chromium.launch({
            headless: true,
            args: [
                '--disable-blink-features=AutomationControlled',
                '--no-sandbox',
                '--disable-dev-shm-usage',
            ],
        });
        return this.browser;
    }

    private async getOrCreateContext(
        account: BrowserAccount,
        useCache = true
    ): Promise<BrowserContext> {
        console.log('getOrCreateContext:1')
        // console.log({account});
        const cached = this.contextPool.get(account.accountId);
        if (useCache && cached) {
            console.log('getOrCreateContext:cached');
            cached.lastUsed = Date.now();
            return cached.ctx;
        }
        console.log('getOrCreateContext:2')

        // LRU eviction
        if (this.contextPool.size >= this.MAX_CONTEXTS) {
            const oldest = [...this.contextPool.entries()].sort(
                (a, b) => a[1].lastUsed - b[1].lastUsed,
            )[0];
            await oldest[1].ctx.close().catch(() => null);
            this.contextPool.delete(oldest[0]);
        }
        console.log('getOrCreateContext:3')

        const browser = await this.ensureBrowser();
        console.log('getOrCreateContext:4')

        const ctx = await browser.newContext({
            userAgent:
                account.userAgent ??
                'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
                '(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
            viewport: {width: 1366, height: 768},
            locale: 'en-US',
            proxy: account.proxy ? {server: account.proxy} : undefined,
        });
        console.log('getOrCreateContext:5')

        // Anti-detection: ẩn webdriver flag
        await ctx.addInitScript(() => {
            Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
        });
        console.log('getOrCreateContext:6')

        await ctx.addCookies(
            account.cookies.map((c) => ({
                ...c,
                domain: c.domain || '.x.com',
                path: c.path || '/',
            })),
        );

        this.contextPool.set(account.accountId, {ctx, lastUsed: Date.now()});
        console.log('getOrCreateContext:7')

        // console.log({
        //     ctx
        // })
        return ctx;
    }

    async postTweet(
        account: BrowserAccount,
        text: string,
    ): Promise<BrowserTweetResult> {
        let page: Page | null = null;
        try {
            const ctx = await this.getOrCreateContext(account);
            const cookies = account.cookies.map((c) => ({
                ...c,
                domain: c.domain || '.x.com',
                path: c.path || '/',
            }));
            await ctx.addCookies(cookies);
            page = await ctx.newPage();

            // Intercept để lấy tweet id từ response
            let capturedTweetId: string | undefined;
            page.on('response', async (resp) => {
                if (resp.url().includes('/CreateTweet')) {
                    try {
                        const json = await resp.json();
                        capturedTweetId =
                            json?.data?.create_tweet?.tweet_results?.result?.rest_id;
                    } catch {
                    }
                }
            });
            // await page.keyboard.press('Mở trang ...');
            await page.goto('https://x.com/home', {
                waitUntil: 'domcontentloaded',
                timeout: 30_000,
            });
            await page.waitForTimeout(2000 + (Math.random() + Math.random()) * 3000);
            await page.mouse.wheel(0, rand(300, 500));
            await page.waitForTimeout(rand(1000, 4000));

            // Detect login/challenge screen
            if (page.url().includes('/login') || page.url().includes('/flow')) {
                return {
                    success: false,
                    error: 'Redirected to login',
                    needsRelogin: true,
                };
            }
            await page.mouse.wheel(200, rand(300, 800));
            await page.waitForTimeout(rand(2000, 5000));
            await page.evaluate(() => window.scrollTo({top: 0, behavior: 'smooth'}));
            await page.waitForTimeout(rand(1000, 2000));
            // page.mouse.move()
            // Mở composer
            // const composer = page.locator('a[href="/compose/post"]').first();
            // await page.waitForTimeout(2000 + (Math.random()+Math.random()) * 3000);
            // await composer.click();

            const textarea = page.locator('div[data-testid="tweetTextarea_0"]');
            await page.waitForTimeout(2000 + (Math.random() + Math.random()) * 3000);
            await textarea.fill(text);
            console.log(' Nhập tweet xong ...');
            await page.waitForTimeout(2000 + (Math.random() + Math.random()) * 3000);
            await page.waitForTimeout(5000);

            // Chờ nút enable
            // const postBtn = page.locator('button[data-testid="tweetButtonInline"]');
            // await postBtn.waitFor({state: 'visible', timeout: 5_000});
            // await postBtn.click();
            // await page.locator('button[data-testid="tweetButtonInline"]').click({ force: true });
            const btn = page.locator('button[data-testid="tweetButtonInline"]');
            const btnBox = await btn.boundingBox();
            console.log(btnBox);
            console.log('Nhấn Control+Enter ...');

            // @ts-ignore
            // await page.mouse.click(btnBox?.x + btnBox.width / 2, btnBox.y + btnBox.height / 2);
            await page.keyboard.press('Control+Enter');
            console.log('Nhấn Control+Enter done ...');
            await page.waitForTimeout(5000);

            // Chờ request CreateTweet hoàn tất
            // await page.waitForResponse(
            //     (r) => r.url().includes('/CreateTweet') && r.status() === 200,
            //     {timeout: 15_000},
            // );

            return {success: true, tweetId: capturedTweetId};
        } catch (err: any) {
            this.logger.error(`Browser post failed: ${err.message}`);
            // console.error(err);
            return {success: false, error: err.message};
        } finally {
            await page?.close().catch(() => null);
        }
    }

    async quoteTweet(
        account: BrowserAccount,
        tweetUrl: string,
        quoteText: string,
    ) {

        let ctx = await this.getOrCreateContext(account);
        if (ctx.isClosed()) {
            console.log('browser is closeed, reopen');
            ctx = await this.getOrCreateContext(account, false);
        }
        const cookies = account.cookies.map((c) => ({
            ...c,
            domain: c.domain || '.x.com',
            path: c.path || '/',
        }));
        console.log('cookies:', cookies);
        await ctx.addCookies(cookies);
        const page = await ctx.newPage();
        try {
            // ===== SAFE GOTO =====
            try {
                await page.goto(tweetUrl, {waitUntil: 'domcontentloaded', timeout: 30000});
            } catch (e) {
                console.log('❌ Load fail');
                throw e;
            }

            await page.waitForTimeout(rand(2000, 4000));

            // ===== CHECK LOGIN =====
            if (await page.locator('input[name="text"]').count()) {
                console.log('❌ Cookie die → bị redirect login');

                throw new HttpException('❌ Không thấy nút retweet (tweet private?)', 500);

            }

            // ===== SCROLL (giống người thật) =====
            await page.mouse.wheel(0, rand(300, 800));
            await page.waitForTimeout(rand(1000, 5000));
            await page.mouse.wheel(0, rand(300, 800));
            await page.waitForTimeout(rand(4000, 8000));
            await page.evaluate(() => window.scrollTo({top: 0, behavior: 'smooth'}));
            await page.waitForTimeout(rand(1000, 2000));


            // ===== CLICK RETWEET =====
            let retweetBtn = page.locator('[data-testid="retweet"]');

            if (!(await retweetBtn.count())) {
                console.log('❌ Không thấy nút retweet (tweet private?)');
                throw new HttpException('❌ Không thấy nút retweet (tweet private?)', 500);
            }

            await retweetBtn.first().click();

            await page.waitForTimeout(rand(1000, 2000));
            try {
                await page.locator('a[href="/compose/post"]').click({timeout: 2000});
            } catch {
                console.log('fallback → click by text');
                await page.locator('a[role="menuitem"]')
                    .filter({hasText: 'Quote'})
                    .click();
            }
            // // ===== CLICK QUOTE =====
            // let quoteBtn = page.locator('[data-testid="retweetWithComment"]');
            //
            // if (!(await quoteBtn.count())) {
            //     console.log('❌ Không thấy nút quote');
            //     return;
            // }
            //
            // await quoteBtn.first().click();

            await page.waitForTimeout(rand(2000, 3000));

            // ===== TYPE LIKE HUMAN =====
            // const content =  pick(quoteText);
            const content = quoteText;

            const box = page.locator('div[role="textbox"]').first();
// chọn đúng textbox đang visible
//             const box = page.locator('div[role="textbox"]:visible').first();

            if (!(await box.count())) {
                console.log('❌ Không thấy textbox');
                throw new HttpException('❌ Không thấy textbox', 500);
            }

// đợi nó xuất hiện thật sự
            await box.waitFor({state: 'visible', timeout: 7000});

// scroll nhẹ vào view (tránh bị offscreen)
//             await box.scrollIntoViewIfNeeded();

// focus trước khi gõ
            console.log('focus trước khi gõ')
            await box.click({delay: rand(50, 150)});


            for (let char of content) {
                await box.type(char, {delay: rand(50, 120)});
            }
            console.log('gõ quote xong ...')
            await page.waitForTimeout(rand(1000, 2000));

            // ===== POST =====
            let postBtn = page.locator('[data-testid="tweetButton"]');
            console.log('count ...')

            if ((await postBtn.count())) {
                console.log('click nút quote ...')
                await postBtn.click({timeout: 7000}).catch(async (e) => {
                    console.log('❌ Nut click khong duoc, thử dùng bàn phím Control+Enter');
                    await page.keyboard.press('Control+Enter');
                });
                await page.waitForTimeout(rand(4000, 6000));

                console.log('✅ Quoted thành công');
            } else {
                console.log('❌ Không thấy nút post, gọi Ctr + Enter');
                await page.keyboard.press('Control+Enter');
                await page.waitForTimeout(rand(4000, 6000));
                console.log('✅ Quoted thành công');
            }
            return {success: true, error: ''};

        } catch (err: any) {
            this.logger.error(`Browser post quote failed: ${err.message}`);
            // console.error(err);
            return {success: false, error: `Browser post quote failed: ${err.message}`};
        } finally {
            await page?.close().catch(() => null);
        }
    }

    async postReply(tweetUrl, content) {
        if (!content) {
            throw new HttpException('Nội dung trả lời không có', 500);
        }

        const accountInfo = {
            accountId: 'realflashkaze',
            cookies: buildXCookies()
        };

        let ctx = await this.getOrCreateContext(accountInfo);
        if (ctx.isClosed()) {
            console.log('browser is closeed, reopen');
            ctx = await this.getOrCreateContext(accountInfo, false);
        }
        const cookies = accountInfo.cookies.map((c) => ({
            ...c,
            domain: '.x.com',
            path: '/',
        }));
        await ctx.addCookies(cookies);
        const page = await ctx.newPage();

        try {
            // limit X
            // content = content.slice(0, 280);

            // vào tweet
            // ===== SAFE GOTO =====
            try {
                await page.goto(tweetUrl, {waitUntil: 'domcontentloaded', timeout: 30000});
            } catch (e) {
                console.log('❌ Load fail');
                throw e;
            }

            // đợi UI ổn
            console.log(`đợi UI ổn...`)
            await page.waitForSelector('article', {timeout: 7000});

            // scroll nhẹ
            console.log(`scroll nhẹ ...`)
            await page.mouse.wheel(0, 300);
            await page.waitForTimeout(1000 + Math.random() * 2000);

            // lấy textbox visible
            const box = page.locator('div[role="textbox"]:visible').first();

            await box.waitFor({state: 'visible', timeout: 7000});

            // focus
            console.log(`box focus ...`)
            await box.click();

            // nhập content (fallback nếu type fail)
            try {
                await box.fill(''); // clear
                await box.type(content, {delay: 30 + Math.random() * 150});
            } catch {
                await box.fill(content);
            }
            console.log(`nhập nội dung xong ...`)
            await page.waitForTimeout(800 + Math.random() * 1200);

            // nút reply
            const btn = page.locator('[data-testid="tweetButtonInline"]:visible');

            if (!(await btn.count())) {
                console.log('❌ Không thấy nút reply');
                return false;
            }

            await btn.click();
            console.log(`nhấn nút gửi ...`)
            await page.waitForTimeout(3000);

            console.log('✅ Reply OK');
            return {success: true, error: ''};
        } catch (err) {
            this.logger.error(`Browser reply failed: ${err.message}`);
            // console.error(err);
            return {success: false, error: `Browser reply failed: ${err.message}`};
        } finally {
            await page?.close().catch(() => null);

        }


    }

    private async cleanupStaleContexts() {
        const now = Date.now();
        for (const [id, entry] of this.contextPool.entries()) {
            if (now - entry.lastUsed > this.CONTEXT_TTL_MS) {
                await entry.ctx.close().catch(() => null);
                this.contextPool.delete(id);
                this.logger.log(`Closed stale context ${id}`);
            }
        }
    }

    async onModuleDestroy() {
        for (const {ctx} of this.contextPool.values()) {
            await ctx.close().catch(() => null);
        }
        this.contextPool.clear();
        await this.browser?.close().catch(() => null);
    }
}
