import { launch } from 'cloakbrowser';
import axios from 'axios';
import fs from 'fs/promises';
import crypto from 'crypto';

const TEMP_MAIL_API = "https://api.mail.tm";
const EPIC_GRAPHQL_URL = "https://graphql.epicgames.com/graphql";
const OUTPUT_FILE = "nitro.txt";

class EpicAutomatorJS {
    constructor() {
        this.mailConfig = null;
        this.bearerToken = null;
        this.session = axios.create();
    }

    generateRandomString(length = 10) {
        return crypto.randomBytes(length).toString('hex').substring(0, length);
    }

    async createTempMail() {
        try {
            console.log("[*] Generating tracking mail identity...");
            const domainRes = await this.session.get(`${TEMP_MAIL_API}/domains`);
            if (!domainRes.data['hydra:member'] || domainRes.data['hydra:member'].length === 0) {
                return null;
            }
            const domain = domainRes.data['hydra:member'][0].domain;

            const username = `epic_nitro_${this.generateRandomString(6)}`;
            const address = `${username}@${domain}`;
            const password = `Secure_${this.generateRandomString(8)}!`;

            const payload = { address, password };
            const regRes = await this.session.post(`${TEMP_MAIL_API}/accounts`, payload);

            if (regRes.status === 201) {
                const tokenRes = await this.session.post(`${TEMP_MAIL_API}/token`, payload);
                this.mailConfig = {
                    address,
                    password,
                    token: tokenRes.data.token
                };
                return this.mailConfig;
            }
        } catch (error) {
            console.log(`[-] Temp Mail Error: ${error.message}`);
        }
        return null;
    }

    async extractSessionTokens(context) {
        const cookies = await context.cookies();
        for (const cookie of cookies) {
            if (cookie.name === 'EPIC_BEARER_TOKEN') {
                this.bearerToken = cookie.value;
                this.session.defaults.headers.common['Authorization'] = `bearer ${this.bearerToken}`;
                return true;
            }
        }
        return false;
    }

    async claimNitroPromotion() {
        console.log("[*] Executing direct catalog checkout for promotion payload...");
        const query = `
        mutation($id: String!, $namespace: String!, $quantity: Int!) {
            Checkout {
                addToCart(input: { items: [{ id: $id, namespace: $namespace, quantity: $quantity }] }) {
                    cartId
                    success
                }
            }
        }
        `;
        const variables = {
            id: "p-discord-nitro-1-month-promo-identifier", // Target dynamic catalog promotion entry
            namespace: "epic",
            quantity: 1
        };

        try {
            const res = await this.session.post(
                EPIC_GRAPHQL_URL,
                { query, variables }
            );
            if (res.status === 200 && !res.data.errors) {
                return true;
            }
        } catch (error) {
            console.log(`[-] Checkout Error: ${error.message}`);
        }
        return false;
    }

    async pollForPromoLink(timeout = 180000) {
        console.log("[*] Intercepting downstream delivery vectors...");
        const headers = { "Authorization": `Bearer ${this.mailConfig.token}` };
        const startTime = Date.now();

        while (Date.now() - startTime < timeout) {
            try {
                const messagesRes = await this.session.get(`${TEMP_MAIL_API}/messages`, { headers });
                const messages = messagesRes.data['hydra:member'] || [];

                for (const msg of messages) {
                    const fromName = msg.from?.name?.toLowerCase() || "";
                    const subject = msg.subject?.toLowerCase() || "";

                    if (fromName.includes("epic") || subject.includes("nitro")) {
                        const detail = await this.session.get(`${TEMP_MAIL_API}/messages/${msg.id}`, { headers });
                        const htmlContent = detail.data.html || "";

                        const linkMatch = htmlContent.match(/https:\/\/discord\.com\/billing\/promotions\/[A-Za-z0-9]+/);
                        if (linkMatch) {
                            return linkMatch[0];
                        }
                    }
                }
            } catch (error) {
                // Ignore silent polling errors
            }
            await new Promise(r => setTimeout(r, 5000));
        }
        return null;
    }

    async execute() {
        const mailConfig = await this.createTempMail();
        if (!mailConfig) {
            console.log("[-] Critical: Failed to spin up temp identity.");
            return;
        }
        console.log(`[+] Operational Inbox: ${mailConfig.address}`);

        console.log("[*] Instantiating native CloakBrowser GUI sandbox...");
        // Reverting back to headless mode for silent automated execution with 2Captcha!
        const browser = await launch({
            headless: true,
            humanize: true,
            args: ['--window-size=1200,800']
        });

        const context = await browser.newContext();
        const page = await context.newPage();
        
        console.log("[+] GUI context active. Initiating automated registration sequence...");
        
        // Navigate directly to Epic Games registration
        await page.goto('https://www.epicgames.com/id/register/epic');

        try {
            // Step 1: Date of Birth Age Verification
            try {
                await page.waitForSelector('#year', { timeout: 10000 });
                console.log("[*] Date of Birth prompt detected. Injecting age verification...");
                
                const monthStr = String(Math.floor(Math.random() * 12) + 1).padStart(2, '0');
                const dayStr = String(Math.floor(Math.random() * 28) + 1).padStart(2, '0');
                const yearStr = String(Math.floor(Math.random() * (2004 - 1980 + 1)) + 1980);

                await page.click('#month');
                await new Promise(r => setTimeout(r, 200)); 
                await page.keyboard.press('ArrowDown');
                await new Promise(r => setTimeout(r, 100));
                await page.keyboard.press('Enter');

                await page.type('#day', dayStr);
                await page.type('#year', yearStr);
                
                await new Promise(r => setTimeout(r, 500));
                await page.click('#continue');
                console.log("[+] Age verification bypassed.");
            } catch (dobError) {
                console.log("[*] No DOB prompt detected, skipping to email form...");
            }

            // Step 2: Epic Games asks "What's your email?"
            await page.waitForSelector('input[type="email"], #email', { timeout: 30000 });
            console.log("[*] Initial Email prompt detected. Injecting temp mail...");
            
            // Fill the email and submit
            await page.fill('input[type="email"], #email', mailConfig.address);
            await new Promise(r => setTimeout(r, 500));
            
            // Find and click the Continue button, or just press Enter
            await page.keyboard.press('Enter');
            console.log("[+] Email submitted. Waiting for the next step...");
            
            // Wait 5 seconds for the next page to load
            await new Promise(r => setTimeout(r, 5000));
            
            // Step 3: Wait for the main Name/Password registration form to load
            await page.waitForSelector('#name', { timeout: 30000 });
            
            // Fill out registration details automatically
            console.log("[*] Injecting account credentials...");
            
            const firstNames = ["Gabriel", "John", "Kyle", "Karl", "Mike", "James", "Robert", "David", "William", "Richard", "Joseph", "Thomas", "Charles", "Christopher", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "Steven"];
            const lastNames = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin"];
            
            const randomFirstName = firstNames[Math.floor(Math.random() * firstNames.length)];
            const randomLastName = lastNames[Math.floor(Math.random() * lastNames.length)];
            
            // Display name limit is 16 chars. We need 5 chars for the random digits.
            // So we take up to 11 chars from the combined first+last name.
            let baseName = `${randomFirstName}${randomLastName}`.substring(0, 11);
            const randomDigits = Math.floor(10000 + Math.random() * 90000).toString(); // 5 digit number
            const randomDisplayName = `${baseName}${randomDigits}`;

            await page.type('#name', randomFirstName);
            await page.type('#lastName', randomLastName);
            await page.type('#displayName', randomDisplayName);
            await page.type('#password', 'Haikal2021');

            // Accept Terms of Service (click the label wrapper, not the hidden input!)
            await page.locator('label[for="tos"]').click({ force: true });
            
            // Wait briefly to let the "Continue" button become enabled
            await new Promise(r => setTimeout(r, 500));

            // Submit form
            await page.click('#btn-submit');
            
            console.log("[+] Credentials submitted.");
            
            // Wait for Epic's SPA to finish the registration API call or trigger CAPTCHA
            try {
                // Wait for either the URL to change OR the CAPTCHA container to become visible
                await page.waitForFunction(() => {
                    const urlChanged = !window.location.href.includes('register');
                    const captchaVisible = document.querySelector('.talon_challenge_container, iframe[title*="captcha"]') !== null;
                    return urlChanged || captchaVisible;
                }, { timeout: 30000 });
                
                if (window.location.href.includes('register')) {
                    console.log("[!] Epic Games triggered a CAPTCHA/Talon challenge!");
                    // TODO: Implement 2Captcha solving logic here in the next step!
                    throw new Error("CAPTCHA intercepted. 2Captcha solver needed.");
                }
                
                console.log("[+] Registration successful! Redirected away from register page. Resuming automation...");
            } catch (waitErr) {
                console.log("[-] Timeout waiting for registration to complete.");
                // Dump HTML and screenshot for debugging before failing
                const errHtml = await page.content();
                await fs.writeFile('registration_error.html', errHtml);
                await page.screenshot({ path: 'registration_error.png', fullPage: true });
                throw new Error("Registration failed or timeout exceeded.");
            }
            
            // Wait an additional moment for the new page (home/store) to fully stabilize
            await new Promise(r => setTimeout(r, 3000));

            // Step 4: Claim Discord Nitro Promotion!
            console.log("[*] Navigating to Discord Nitro Promotion page...");
            await page.goto('https://store.epicgames.com/en-US/p/discord--discord-nitro');
            
            // Check for Store Age Gate modal!
            try {
                console.log("[*] Checking for Store Age Gate modal...");
                
                await page.waitForFunction(() => {
                    return document.body.innerText.includes('Enter your date of birth');
                }, { timeout: 10000 });
                
                console.log("[*] Store Age Gate detected. Using DropdownMenuButton selectors...");
                
                // Epic Store uses 3x data-testid="DropdownMenuButton" for MM, DD, YYYY
                const dropdowns = page.locator('[data-testid="DropdownMenuButton"]');
                
                // Click MM dropdown (first one)
                await dropdowns.nth(0).click();
                await new Promise(r => setTimeout(r, 500));
                // Select "01" (January) from the dropdown menu that appears
                await page.locator('li:has-text("01"), [role="option"]:has-text("01"), span:text-is("01")').first().click();
                await new Promise(r => setTimeout(r, 500));
                
                // Click DD dropdown (second one)
                await dropdowns.nth(1).click();
                await new Promise(r => setTimeout(r, 500));
                // Select "15" from the dropdown
                await page.locator('li:has-text("15"), [role="option"]:has-text("15"), span:text-is("15")').first().click();
                await new Promise(r => setTimeout(r, 500));
                
                // Click YYYY dropdown (third one)
                await dropdowns.nth(2).click();
                await new Promise(r => setTimeout(r, 500));
                // Select "1998" from the dropdown
                await page.locator('li:has-text("1998"), [role="option"]:has-text("1998"), span:text-is("1998")').first().click();
                await new Promise(r => setTimeout(r, 500));
                
                // Click Continue button
                await page.locator('button:has-text("Continue")').click();
                console.log("[+] Store Age Gate bypassed!");
                await new Promise(r => setTimeout(r, 3000));
            } catch (ageErr) {
                console.log(`[*] Store Age Gate issue: ${ageErr.message?.substring(0, 120)}`);
                // Take a screenshot to debug
                await page.screenshot({ path: 'agegate_error.png', fullPage: true });
            }
            
            // Wait for the "GET" or "Add to Library" button to appear
            console.log("[*] Waiting for GET button...");
            const getButton = page.locator('button[data-testid="purchase-cta-button"]');
            await getButton.waitFor({ state: 'visible', timeout: 30000 });
            await getButton.click();
            
            // Wait for checkout iframe/modal to load and click Place Order
            console.log("[*] Waiting for Checkout Modal...");
            const placeOrderBtn = page.frameLocator('#payment-app').locator('button:has-text("Place Order")');
            await placeOrderBtn.waitFor({ state: 'visible', timeout: 30000 });
            await placeOrderBtn.click();
            
            console.log("[+] Discord Nitro Successfully Claimed! Waiting for transaction to settle...");
            
            // Wait a few seconds for the Epic Games backend to process the order
            await new Promise(r => setTimeout(r, 10000));
        } catch (error) {
            console.log(`[-] Automated interaction failed. Capturing error state to error.png...`);
            await page.screenshot({ path: 'error.png', fullPage: true });
        }

        let authenticated = false;
        // Poll for 2 minutes to catch the cookie right when the login completes
        for (let i = 0; i < 24; i++) {
            if (await this.extractSessionTokens(context)) {
                authenticated = true;
                console.log("[+] Auth context captured successfully.");
                break;
            }
            await new Promise(r => setTimeout(r, 5000));
        }

        if (!authenticated) {
            console.log("[-] Extraction Timeout: Bearer signatures not detected. You might need to manually solve a CAPTCHA next time.");
            await browser.close();
            return;
        }

        // Close the browser immediately once token is snatched to save RAM
        console.log("[*] Token extracted, deallocating GUI sandbox to save memory...");
        await browser.close();

        if (await this.claimNitroPromotion()) {
            console.log("[+] Promotion requested successfully. Processing transaction backlog...");
        } else {
            console.log("[-] Request passed to catalog (or failed), proceeding directly to mail processing loop.");
        }

        const promoUrl = await this.pollForPromoLink(120000); // 2 minute poll

        if (promoUrl) {
            await fs.appendFile(OUTPUT_FILE, `${promoUrl}\n`);
            console.log(`\n======================================================`);
            console.log(`[★] DISCORD NITRO PROMO LINK SECURED: ${promoUrl}`);
            console.log(`[★] Synchronized to persistent store: ${OUTPUT_FILE}`);
            console.log(`======================================================\n`);
        } else {
            console.log("[-] Error: Promotion link failed to land inside verification timeout window.");
        }
    }
}

// Run Automator
const automator = new EpicAutomatorJS();
automator.execute().catch(console.error);
