/**
 * Authentication Helper for UI Tests
 * Handles login and registration for Puppeteer tests
 */

const crypto = require('crypto');

const DEFAULT_TEST_USER = {
    username: 'testuser',
    password: 'T3st!Secure#2024$LDR'
};

// Configuration constants - single source of truth for auth helper settings
const AUTH_CONFIG = {
    // Route paths
    paths: {
        login: '/auth/login',
        register: '/auth/register',
        logout: '/auth/logout'
    },
    // Timeouts (ms)
    timeouts: {
        navigation: 30000,
        formSelector: process.env.CI ? 10000 : 5000,
        submitNavigation: process.env.CI ? 10000 : 90000,
        urlCheck: 5000,
        errorCheck: 2000,
        logout: 10000
    },
    // Delays (ms)
    delays: {
        retryNavigation: 1000,
        afterRegistration: 3000,
        beforeRetry: 5000,
        afterLogout: 1000
    },
    // CI-specific settings
    ci: {
        waitUntil: 'domcontentloaded',
        maxLoginAttempts: 5
    }
};

// Generate random username for each test to avoid conflicts
function generateRandomUsername() {
    const timestamp = Date.now();
    let random;
    // Use rejection sampling to avoid bias
    const maxValue = 4294967295; // Max value for 32-bit unsigned int
    const limit = maxValue - (maxValue % 1000); // Largest multiple of 1000 that fits

    do {
        random = crypto.randomBytes(4).readUInt32BE(0);
    } while (random >= limit); // Reject values that would cause bias

    random = random % 1000;
    return `testuser_${timestamp}_${random}`;
}

class AuthHelper {
    constructor(page, baseUrl = 'http://127.0.0.1:5000') {
        this.page = page;
        this.baseUrl = baseUrl;
        this.isCI = !!process.env.CI;
    }

    /**
     * Helper method for delays
     */
    async _delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    /**
     * Navigate to an auth page with CI-aware retry logic
     * @param {string} path - The path to navigate to (e.g., AUTH_CONFIG.paths.login)
     * @param {string} expectedPathSegment - Path segment to verify arrival (e.g., '/auth/login')
     * @returns {string} The URL we arrived at
     */
    async _navigateToAuthPage(path, expectedPathSegment) {
        const targetUrl = `${this.baseUrl}${path}`;
        const waitUntil = this.isCI ? AUTH_CONFIG.ci.waitUntil : 'networkidle2';

        console.log(`  Navigating to ${path}...`);
        await this.page.goto(targetUrl, {
            waitUntil,
            timeout: AUTH_CONFIG.timeouts.navigation
        });

        let arrivedUrl = this.page.url();
        console.log(`  Arrived at: ${arrivedUrl}`);

        // In CI, verify we're on the correct page and retry if needed
        if (this.isCI && !arrivedUrl.includes(expectedPathSegment)) {
            console.log(`  CI: Not on ${expectedPathSegment}, retrying navigation...`);
            await this._delay(AUTH_CONFIG.delays.retryNavigation);
            await this.page.goto(targetUrl, {
                waitUntil: AUTH_CONFIG.ci.waitUntil,
                timeout: AUTH_CONFIG.timeouts.navigation
            });
            arrivedUrl = this.page.url();
            console.log(`  CI: Second attempt arrived at: ${arrivedUrl}`);
        }

        return arrivedUrl;
    }

    /**
     * Check if user is logged in by looking for logout button or username
     */
    async isLoggedIn() {
        try {
            // Check if we're on a page that requires auth
            const url = this.page.url();
            console.log('Checking login status at URL:', url);

            if (url.includes(AUTH_CONFIG.paths.login)) {
                console.log('On login page - not logged in');
                return false;
            }

            // Check for logout button/link
            const logoutSelectors = [
                'a.logout-btn',
                '#logout-form',
                'form[action="/auth/logout"]',
                'a[onclick*="logout"]'
            ];

            for (const selector of logoutSelectors) {
                try {
                    const element = await this.page.$(selector);
                    if (element) {
                        console.log(`Found logout element with selector: ${selector}`);
                        return true;
                    }
                } catch (e) {
                    // Some selectors might not be valid, continue
                }
            }

            // Check if we can access protected pages
            const currentUrl = this.page.url();
            if (currentUrl.includes('/settings') || currentUrl.includes('/metrics') || currentUrl.includes('/history')) {
                console.log('On protected page - logged in');
                return true;
            }

            // If we're on the home page, check for research form
            const researchForm = await this.page.$('form[action*="research"], #query, button[type="submit"]');
            if (researchForm) {
                console.log('Found research form - likely logged in');
                return true;
            }

            console.log('No login indicators found');
            return false;
        } catch (error) {
            console.log('Error checking login status:', error.message);
            return false;
        }
    }

    /**
     * Login with existing user credentials
     */
    async login(username = DEFAULT_TEST_USER.username, password = DEFAULT_TEST_USER.password) {
        console.log(`🔐 Attempting login as ${username}...`);

        // Check if already logged in
        if (await this.isLoggedIn()) {
            console.log('✅ Already logged in');
            return true;
        }

        // Navigate to login page only if not already there
        const currentUrl = this.page.url();
        console.log(`  Current URL: ${currentUrl}`);
        if (!currentUrl.includes(AUTH_CONFIG.paths.login)) {
            await this._navigateToAuthPage(AUTH_CONFIG.paths.login, AUTH_CONFIG.paths.login);
        }

        // Wait for login form
        console.log('  Waiting for login form...');
        await this.page.waitForSelector('input[name="username"]', { timeout: AUTH_CONFIG.timeouts.formSelector });

        // Check what's on the page
        const formAction = await this.page.$eval('form', form => form.action).catch(() => 'no form found');
        console.log(`  Form action: ${formAction}`);

        const submitButton = await this.page.$eval('button[type="submit"]', btn => btn.textContent).catch(() => 'no submit button');
        console.log(`  Submit button text: ${submitButton}`);

        // Fill in credentials
        console.log('  Filling in credentials...');

        // Clear fields first to ensure clean state
        await this.page.$eval('input[name="username"]', el => el.value = '');
        await this.page.$eval('input[name="password"]', el => el.value = '');

        // Type credentials
        await this.page.type('input[name="username"]', username);
        await this.page.type('input[name="password"]', password);

        // Check form values before submit
        const usernameValue = await this.page.$eval('input[name="username"]', el => el.value);
        const passwordValue = await this.page.$eval('input[name="password"]', el => el.value);
        console.log(`  Username field value: ${usernameValue}`);
        console.log(`  Password field has value: ${passwordValue.length > 0 ? 'yes' : 'no'} (length: ${passwordValue.length})`);

        // Submit form
        console.log('  Submitting form...');
        console.log('  Waiting for navigation after submit (timeout: 60s)...');

        // Listen to console messages from the page
        this.page.on('console', msg => console.log('  Browser console:', msg.text()));

        // Listen to page errors
        this.page.on('pageerror', error => console.log('  Page error:', error.message));

        // Listen to response events
        this.page.on('response', response => {
            if (response.url().includes('/auth/login') && response.request().method() === 'POST') {
                console.log(`  Login POST response: ${response.status()} ${response.statusText()}`);
            }
        });

        try {
            // In CI, try a different approach - click and wait for URL change
            if (this.isCI) {
                console.log('  Using CI-specific login approach (wait for redirect)');

                // Click the submit button
                await this.page.click('button[type="submit"]');

                // Wait for either navigation or timeout
                let redirected = false;
                for (let i = 0; i < 30; i++) {
                    await this._delay(AUTH_CONFIG.delays.retryNavigation);

                    // Try to get URL with a timeout
                    let currentUrl;
                    try {
                        currentUrl = await Promise.race([
                            this.page.evaluate(() => window.location.href),
                            new Promise((_, reject) => setTimeout(() => reject(new Error('URL check timeout')), AUTH_CONFIG.timeouts.urlCheck))
                        ]);
                        console.log(`  Checking URL (${i+1}/30): ${currentUrl}`);
                    } catch (urlError) {
                        console.log(`  Warning: Could not get URL (${i+1}/30): ${urlError.message}`);
                        continue; // Skip this check and try again
                    }

                    if (currentUrl && !currentUrl.includes(AUTH_CONFIG.paths.login)) {
                        console.log('  ✅ Redirected away from login page');
                        redirected = true;
                        break;
                    }

                    // Check if there's an error message on the page (with timeout protection)
                    try {
                        const errorElement = await Promise.race([
                            this.page.$('.alert-danger, .error-message, .flash-message'),
                            new Promise(resolve => setTimeout(() => resolve(null), AUTH_CONFIG.timeouts.errorCheck))
                        ]);
                        if (errorElement) {
                            const errorText = await this.page.evaluate(el => el.textContent, errorElement);
                            throw new Error(`Login failed with error: ${errorText.trim()}`);
                        }
                    } catch (errorCheckError) {
                        if (errorCheckError.message && errorCheckError.message.includes('Login failed')) {
                            throw errorCheckError;
                        }
                        // Otherwise ignore the error check failure
                    }

                    // In CI mode, give up after configured attempts if we're still on login page
                    // This likely means the user doesn't exist
                    if (i >= AUTH_CONFIG.ci.maxLoginAttempts && currentUrl && currentUrl.includes(AUTH_CONFIG.paths.login)) {
                        console.log(`  Login not succeeding after ${AUTH_CONFIG.ci.maxLoginAttempts} attempts, user likely does not exist`);
                        throw new Error('Login failed - user does not exist');
                    }
                }

                if (!redirected) {
                    // Check cookies to see if we're actually logged in
                    const cookies = await this.page.cookies();
                    console.log(`  Still on login page. Cookies: ${cookies.length}`);
                    const sessionCookie = cookies.find(c => c.name === 'session');
                    if (sessionCookie) {
                        console.log('  Session cookie exists, manually navigating to home');
                        await this.page.goto(this.baseUrl, { waitUntil: AUTH_CONFIG.ci.waitUntil });
                    } else {
                        throw new Error('Login failed - no redirect and no session cookie');
                    }
                }

                console.log('  Navigation completed');
            } else {
                // Original logic for non-CI
                await Promise.all([
                    this.page.waitForNavigation({
                        waitUntil: 'networkidle2',
                        timeout: AUTH_CONFIG.timeouts.submitNavigation
                    }),
                    this.page.click('button[type="submit"]')
                ]);
                console.log('  Navigation completed');
            }
        } catch (navError) {
            console.log(`  Navigation error: ${navError.message}`);
            console.log(`  Current URL after error: ${this.page.url()}`);

            // Check page content on error
            const pageTitle = await this.page.title();
            console.log(`  Page title: ${pageTitle}`);

            const alerts = await this.page.$$eval('.alert', alerts => alerts.map(a => a.textContent));
            if (alerts.length > 0) {
                console.log(`  Alerts on page: ${JSON.stringify(alerts)}`);
            }

            throw navError;
        }

        // Check if login was successful
        const finalUrl = this.page.url();
        console.log(`  Final URL: ${finalUrl}`);

        if (finalUrl.includes(AUTH_CONFIG.paths.login)) {
            // Still on login page - check for error
            const error = await this.page.$('.alert-danger, .error-message, .alert');
            if (error) {
                const errorText = await this.page.evaluate(el => el.textContent, error);
                console.log(`  Error message on page: ${errorText.trim()}`);
                throw new Error(`Login failed: ${errorText.trim()}`);
            }

            // Check form validation errors
            const validationErrors = await this.page.$$eval('.invalid-feedback, .help-block', els =>
                els.map(el => el.textContent.trim()).filter(text => text.length > 0)
            );
            if (validationErrors.length > 0) {
                console.log(`  Validation errors: ${JSON.stringify(validationErrors)}`);
            }

            throw new Error('Login failed - still on login page');
        }

        console.log('✅ Login successful');
        return true;
    }

    /**
     * Register a new user
     */
    async register(username = DEFAULT_TEST_USER.username, password = DEFAULT_TEST_USER.password) {
        console.log(`📝 Attempting registration for ${username}...`);

        // Navigate to registration page using the helper
        const arrivedUrl = await this._navigateToAuthPage(AUTH_CONFIG.paths.register, AUTH_CONFIG.paths.register);

        // If redirected to login, registration might be disabled
        if (arrivedUrl.includes(AUTH_CONFIG.paths.login)) {
            throw new Error('Registration page redirected to login - registrations may be disabled');
        }

        // Wait for registration form
        await this.page.waitForSelector('input[name="username"]', { timeout: AUTH_CONFIG.timeouts.formSelector });

        // Fill in registration form
        await this.page.type('input[name="username"]', username);
        await this.page.type('input[name="password"]', password);
        await this.page.type('input[name="confirm_password"]', password);

        // Check acknowledgment checkbox if present
        const acknowledgeCheckbox = await this.page.$('input[name="acknowledge"]');
        if (acknowledgeCheckbox) {
            await this.page.click('input[name="acknowledge"]');
        }

        // Submit form
        try {
            await Promise.all([
                this.page.waitForNavigation({
                    waitUntil: 'domcontentloaded',
                    timeout: AUTH_CONFIG.timeouts.submitNavigation
                }),
                this.page.click('button[type="submit"]')
            ]);
        } catch (navError) {
            // In CI, navigation errors are expected due to frame detachment
            if (this.isCI && navError.message.includes('detached')) {
                console.log('  CI: Navigation error (expected):', navError.message);
                // Wait for registration to complete server-side
                await this._delay(AUTH_CONFIG.delays.afterRegistration);

                // Navigate back to home page after registration
                try {
                    await this.page.goto(this.baseUrl, {
                        waitUntil: AUTH_CONFIG.ci.waitUntil,
                        timeout: AUTH_CONFIG.timeouts.formSelector
                    });
                } catch (gotoError) {
                    console.log('  CI: Could not navigate after registration:', gotoError.message);
                }

                console.log('✅ Registration completed (CI mode)');
                return true;
            }
            throw navError;
        }

        // Check if registration was successful
        const currentUrl = this.page.url();
        if (currentUrl.includes(AUTH_CONFIG.paths.register)) {
            // Still on registration page - check for actual errors (not warnings)
            const error = await this.page.$('.alert-danger:not(.alert-warning), .error-message');
            if (error) {
                const errorText = await this.page.evaluate(el => el.textContent, error);
                if (errorText.includes('already exists')) {
                    console.log('⚠️  User already exists, attempting login instead');
                    return await this.login(username, password);
                }
                throw new Error(`Registration failed: ${errorText}`);
            }

            // Check for security warnings (these are not errors)
            const warning = await this.page.$('.alert-warning');
            if (warning) {
                const warningText = await this.page.evaluate(el => el.textContent, warning);
                console.log('⚠️  Security warning:', warningText.trim().replace(/\s+/g, ' '));
            }

            throw new Error('Registration failed - still on registration page');
        }

        console.log('✅ Registration successful');
        return true;
    }

    /**
     * Ensure user is authenticated - register if needed, then login
     */
    async ensureAuthenticated(username = null, password = DEFAULT_TEST_USER.password, retries = 2) {
        // Generate random username if not provided
        if (!username) {
            username = generateRandomUsername();
            console.log(`🎲 Using random username: ${username}`);
        }

        // Check if already logged in
        if (await this.isLoggedIn()) {
            console.log('✅ Already logged in');
            return true;
        }

        let lastError;
        for (let attempt = 1; attempt <= retries; attempt++) {
            try {
                // Try to login first
                return await this.login(username, password);
            } catch (loginError) {
                console.log(`⚠️  Login failed (attempt ${attempt}/${retries}), attempting registration...`);
                try {
                    // If login fails, try to register
                    return await this.register(username, password);
                } catch (registerError) {
                    lastError = registerError;
                    // If registration also fails (user exists), try login again
                    if (registerError.message.includes('already exists')) {
                        try {
                            return await this.login(username, password);
                        } catch (secondLoginError) {
                            lastError = secondLoginError;
                        }
                    }

                    if (attempt < retries && registerError.message.includes('timeout')) {
                        console.log(`⚠️  Timeout error, retrying (${attempt}/${retries})...`);
                        await this._delay(AUTH_CONFIG.delays.beforeRetry);
                        continue;
                    }

                    if (attempt === retries) {
                        throw registerError;
                    }
                }
            }
        }

        throw lastError || new Error('Failed to authenticate after retries');
    }

    /**
     * Logout the current user
     */
    async logout() {
        console.log('🚪 Logging out...');

        try {
            // Try to find and submit the logout form directly (more reliable than clicking link)
            const logoutForm = await this.page.$('#logout-form');
            if (logoutForm) {
                console.log('  Found logout form, submitting directly...');
                await Promise.all([
                    this.page.waitForNavigation({
                        waitUntil: 'networkidle2',
                        timeout: AUTH_CONFIG.timeouts.logout
                    }).catch(() => {
                        console.log('  Navigation wait timed out, checking URL...');
                    }),
                    this.page.evaluate(() => {
                        document.getElementById('logout-form').submit();
                    })
                ]);
            } else {
                // Fallback: look for logout link/button and click it
                const logoutLink = await this.page.$('a.logout-btn');
                if (logoutLink) {
                    console.log('  Found logout link, clicking...');
                    await Promise.all([
                        this.page.waitForNavigation({
                            waitUntil: 'networkidle2',
                            timeout: AUTH_CONFIG.timeouts.logout
                        }).catch(() => {
                            console.log('  Navigation wait timed out, checking URL...');
                        }),
                        this.page.click('a.logout-btn')
                    ]);
                } else {
                    // Last resort: navigate directly to logout URL
                    console.log(`  No logout form/button found, navigating directly to ${AUTH_CONFIG.paths.logout}...`);
                    await this.page.goto(`${this.page.url().split('/').slice(0, 3).join('/')}${AUTH_CONFIG.paths.logout}`, {
                        waitUntil: 'networkidle2',
                        timeout: AUTH_CONFIG.timeouts.logout
                    });
                }
            }

            // Give it a moment for any redirects
            await this._delay(AUTH_CONFIG.delays.afterLogout);

            // Ensure we're on the login page or logged out
            const currentUrl = this.page.url();
            console.log(`  Current URL after logout: ${currentUrl}`);

            // Check if we're logged out by looking for login form
            const loginForm = await this.page.$('form[action*="login"], input[name="username"]');
            if (loginForm || currentUrl.includes(AUTH_CONFIG.paths.login)) {
                console.log('✅ Logged out successfully');
            } else {
                // Double-check by trying to access a protected page
                await this.page.goto(`${this.page.url().split('/').slice(0, 3).join('/')}/settings/`, {
                    waitUntil: 'networkidle2',
                    timeout: AUTH_CONFIG.timeouts.formSelector
                }).catch(() => {});

                const finalUrl = this.page.url();
                if (finalUrl.includes(AUTH_CONFIG.paths.login)) {
                    console.log('✅ Logged out successfully (verified via protected page)');
                } else {
                    console.log(`Warning: May not be fully logged out. Current URL: ${finalUrl}`);
                }
            }
        } catch (error) {
            console.log(`⚠️ Logout error: ${error.message}`);
            // Continue anyway - we'll verify logout status
        }
    }
}

module.exports = AuthHelper;
