#!/usr/bin/env node
/**
 * Settings Interactions UI Tests
 *
 * Tests for settings page interactions including tabs, search,
 * toggles, inputs, and save functionality.
 *
 * Run: node test_settings_interactions_ci.js
 */

const { setupTest, teardownTest, TestResults, log, navigateTo, withTimeout } = require('./test_lib');

// ============================================================================
// Settings Page Structure Tests
// ============================================================================
const SettingsPageTests = {
    async settingsPageLoads(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/settings`);

        const result = await page.evaluate(() => {
            const hasContent = document.body.textContent.length > 100;
            const title = document.title.toLowerCase();
            const hasSettingsContent = title.includes('settings') || title.includes('configuration') ||
                                      !!document.querySelector('.settings, #settings, [class*="settings"]');

            return {
                hasContent,
                hasSettingsContent,
                title,
                url: window.location.href
            };
        });

        return {
            passed: result.hasContent,
            message: `Settings page loads (title: "${result.title}")`
        };
    },

    async settingsSearchFilter(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/settings`);

        const result = await page.evaluate(() => {
            const searchInput = document.querySelector(
                'input[type="search"], ' +
                'input[placeholder*="search"], ' +
                'input[placeholder*="filter"], ' +
                '#settings-search, ' +
                '.settings-filter'
            );

            return {
                hasSearch: !!searchInput,
                placeholder: searchInput?.placeholder
            };
        });

        if (!result.hasSearch) {
            return { passed: null, skipped: true, message: 'No settings search found' };
        }

        return {
            passed: true,
            message: `Settings search: placeholder="${result.placeholder}"`
        };
    },

    async settingsSearchFiltering(page, baseUrl) {
        // #settings-search re-renders #settings-content with a filtered subset
        // (settings.js::handleSearchInput filters allSettings by key/name/
        // description/category) and restores the full active tab when cleared.
        // The old test typed 'model', never asserted the count actually
        // changed, and always returned passed:true.
        await navigateTo(page, `${baseUrl}/settings`);
        await page.waitForSelector('#settings-content .ldr-settings-item', { timeout: 15000 });

        const count = () => page.$$eval('#settings-content .ldr-settings-item', els => els.length);
        const initial = await count();
        if (initial === 0) {
            return { passed: false, message: 'No settings rendered to filter' };
        }

        // A term matching nothing collapses the rendered list. The .catch
        // tolerates the wait timing out — the count assertion below reports the
        // actual diff, a clearer failure than a bare waitForFunction timeout.
        await page.type('#settings-search', 'zzznomatchzzz');
        await page.waitForFunction(
            () => document.querySelectorAll('#settings-content .ldr-settings-item').length === 0,
            { timeout: 5000 }
        ).catch(() => {});
        const filtered = await count();

        // ...and clearing it restores the list. Set value + dispatch a real
        // 'input' event (what handleSearchInput listens for) — more portable
        // than triple-click + Backspace across headless targets.
        await page.$eval('#settings-search', el => {
            el.value = '';
            el.dispatchEvent(new Event('input', { bubbles: true }));
        });
        await page.waitForFunction(
            (n) => document.querySelectorAll('#settings-content .ldr-settings-item').length === n,
            { timeout: 5000 }, initial
        ).catch(() => {});
        const restored = await count();

        const passed = filtered < initial && restored === initial;
        return {
            passed,
            message: passed
                ? `Search filters list (${initial} → ${filtered} on no-match → ${restored} restored)`
                : `Filter contract failed (initial=${initial}, filtered=${filtered}, restored=${restored})`
        };
    }
};

// ============================================================================
// Settings Tabs Tests
// ============================================================================
const SettingsTabsTests = {
    async settingsTabsExist(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/settings`);

        const result = await page.evaluate(() => {
            const tabs = document.querySelectorAll(
                '.tab, ' +
                '.nav-tab, ' +
                '[role="tab"], ' +
                '.settings-tab, ' +
                '.nav-link'
            );

            const tabTexts = Array.from(tabs).slice(0, 10).map(t => t.textContent?.trim());

            return {
                tabCount: tabs.length,
                tabs: tabTexts
            };
        });

        if (result.tabCount === 0) {
            return { passed: null, skipped: true, message: 'No settings tabs found' };
        }

        return {
            passed: true,
            message: `${result.tabCount} tabs: ${result.tabs.join(', ')}`
        };
    },

    async tabNavigationWorks(page, baseUrl) {
        // Tabs are .ldr-settings-tab[data-tab]; clicking one adds .active and
        // re-renders #settings-content in place (settings.js:3930-3976) — there
        // is no page navigation and no .tab-content/.tab-pane element, so the
        // old probe matched nothing and the catch hardcoded passed:true.
        await navigateTo(page, `${baseUrl}/settings`);
        await page.waitForSelector('#settings-content .ldr-settings-item', { timeout: 15000 });
        await page.waitForSelector('.ldr-settings-tab[data-tab="llm"]', { timeout: 5000 });

        // Precondition: the dashboard opens on the 'all' tab. Make it explicit
        // so a changed default fails here with a clear message.
        const start = await page.evaluate(() => ({
            allActive: document.querySelector('.ldr-settings-tab[data-tab="all"]')?.classList.contains('active'),
            itemCount: document.querySelectorAll('#settings-content .ldr-settings-item').length,
        }));
        if (start.allActive !== true) {
            return { passed: false, message: `Expected 'all' tab active on load (allActive=${start.allActive})` };
        }

        // Switch to 'llm' — a strict subset of 'all', so the rendered item set
        // must shrink (proves the content actually re-rendered, not just the
        // tab class flipping).
        await page.click('.ldr-settings-tab[data-tab="llm"]');
        await page.waitForFunction(
            () => document.querySelector('.ldr-settings-tab[data-tab="llm"]')?.classList.contains('active') === true,
            { timeout: 5000 }
        );

        const state = await page.evaluate(() => ({
            llmActive: document.querySelector('.ldr-settings-tab[data-tab="llm"]')?.classList.contains('active'),
            allActive: document.querySelector('.ldr-settings-tab[data-tab="all"]')?.classList.contains('active'),
            itemCount: document.querySelectorAll('#settings-content .ldr-settings-item').length,
        }));

        const reRendered = state.itemCount < start.itemCount;
        const passed = state.llmActive === true && state.allActive === false && reRendered;
        return {
            passed,
            message: passed
                ? `Tab switch works (llm active, all deactivated, items ${start.itemCount}→${state.itemCount})`
                : `Tab switch failed (llmActive=${state.llmActive}, allActive=${state.allActive}, items ${start.itemCount}→${state.itemCount})`
        };
    },

    async specificTabsPresent(page, baseUrl) {
        // The dashboard renders a fixed set of category tabs as
        // .ldr-settings-tab[data-tab] (settings_dashboard.html). Assert the
        // real data-tab set rather than fuzzy-matching body text.
        await navigateTo(page, `${baseUrl}/settings`);
        await page.waitForSelector('.ldr-settings-tab[data-tab]', { timeout: 10000 });

        const tabs = await page.$$eval('.ldr-settings-tab[data-tab]', els => els.map(e => e.getAttribute('data-tab')));
        const expected = ['llm', 'search', 'report', 'app', 'notifications'];
        const missing = expected.filter(t => !tabs.includes(t));

        return {
            passed: missing.length === 0,
            message: missing.length === 0
                ? `All expected category tabs present: ${tabs.join(', ')}`
                : `Missing category tabs: ${missing.join(', ')} (present: ${tabs.join(', ')})`
        };
    }
};

// ============================================================================
// Settings Controls Tests
// ============================================================================
const SettingsControlsTests = {
    async textInputSettings(page, baseUrl) {
        // Real rendered text settings are input.ldr-settings-input[type=text]
        // inside #settings-content (settings_form.html). The old selector list
        // (.settings input etc.) matched nothing → permanent skip.
        await navigateTo(page, `${baseUrl}/settings`);
        await page.waitForSelector('#settings-content .ldr-settings-item', { timeout: 15000 });

        const result = await page.evaluate(() => {
            const inputs = document.querySelectorAll('#settings-content input.ldr-settings-input[type="text"][name]');
            const first = inputs[0];
            return { count: inputs.length, firstName: first?.name };
        });

        return {
            passed: result.count > 0,
            message: result.count > 0
                ? `Text settings inputs: ${result.count} (first: "${result.firstName}")`
                : 'No .ldr-settings-input[type=text] rendered in #settings-content'
        };
    },

    async numberInputSettings(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/settings`);

        await page.waitForSelector('#settings-content .ldr-settings-item', { timeout: 15000 });
        const result = await page.evaluate(() => {
            const inputs = document.querySelectorAll('#settings-content input[type="number"][name]');
            const first = inputs[0];
            return {
                count: inputs.length,
                firstName: first?.name,
                min: first?.min,
                max: first?.max,
                step: first?.step
            };
        });

        return {
            passed: result.count > 0,
            message: result.count > 0
                ? `Number settings inputs: ${result.count} (first: "${result.firstName}", min=${result.min}, max=${result.max}, step=${result.step})`
                : 'No number inputs rendered in #settings-content'
        };
    },

    async toggleSwitchSettings(page, baseUrl) {
        // Real setting checkboxes are input.ldr-settings-checkbox
        // (settings_form.html), each paired with a .ldr-checkbox-hidden-fallback.
        // The old broad selectors matched nothing → permanent skip.
        await navigateTo(page, `${baseUrl}/settings`);
        await page.waitForSelector('#settings-content .ldr-settings-item', { timeout: 15000 });

        const result = await page.evaluate(() => {
            const boxes = document.querySelectorAll('#settings-content input.ldr-settings-checkbox[name]');
            const first = boxes[0];
            return { count: boxes.length, firstName: first?.name, checked: first?.checked };
        });

        return {
            passed: result.count > 0,
            message: result.count > 0
                ? `Setting checkboxes: ${result.count} (first: "${result.firstName}", checked=${result.checked})`
                : 'No .ldr-settings-checkbox rendered in #settings-content'
        };
    },

    async dropdownSelectSettings(page, baseUrl) {
        // Dropdown settings render either as a plain <select.ldr-settings-select>
        // or — for llm.provider / llm.model / search.tool — as a custom
        // .ldr-custom-dropdown widget (components/custom_dropdown.html). The
        // default config only has the custom kind (no plain <select>), so the
        // contract is "at least one dropdown control exists".
        await navigateTo(page, `${baseUrl}/settings`);
        await page.waitForSelector('#settings-content .ldr-settings-item', { timeout: 15000 });

        const result = await page.evaluate(() => {
            const selects = document.querySelectorAll('#settings-content select.ldr-settings-select[name]');
            const customs = document.querySelectorAll('#settings-content .ldr-custom-dropdown');
            const first = selects[0];
            return {
                selectCount: selects.length,
                customCount: customs.length,
                firstName: first?.name,
                optionCount: first ? first.options.length : 0,
            };
        });

        const total = result.selectCount + result.customCount;
        // A plain <select>, if present, must have options; custom dropdowns
        // populate their list lazily, so only their presence is asserted.
        const selectsOk = result.selectCount === 0 || result.optionCount > 0;
        return {
            passed: total > 0 && selectsOk,
            message: total > 0
                ? `Dropdown settings: ${result.selectCount} <select>${result.firstName ? ` ("${result.firstName}", ${result.optionCount} opts)` : ''} + ${result.customCount} custom-dropdown`
                : 'No dropdown controls (.ldr-settings-select / .ldr-custom-dropdown) rendered in #settings-content'
        };
    },

    async toggleSwitchToggleable(page, baseUrl) {
        // Click a real, enabled setting checkbox and assert its checked state
        // flips. Uses page.click (a real user gesture) on the confirmed
        // .ldr-settings-checkbox selector.
        await navigateTo(page, `${baseUrl}/settings`);
        const sel = '#settings-content input.ldr-settings-checkbox[name]:not([disabled])';
        await page.waitForSelector(sel, { timeout: 15000 });

        const before = await page.$eval(sel, el => el.checked);
        await page.click(sel);
        const after = await page.$eval(sel, el => el.checked);

        const toggled = before !== after;
        return {
            passed: toggled,
            message: toggled
                ? `Checkbox toggles (before=${before}, after=${after})`
                : `Checkbox did not flip (before=${before}, after=${after})`
        };
    }
};

// ============================================================================
// Settings Save Tests
// ============================================================================
const SettingsSaveTests = {
    async saveButtonExists(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/settings`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, input[type="submit"], .btn'));
            const saveBtn = buttons.find(btn => {
                const text = btn.textContent?.toLowerCase() || btn.value?.toLowerCase() || '';
                return text.includes('save') || text.includes('apply') || text.includes('update');
            });

            return {
                hasSaveBtn: !!saveBtn,
                buttonText: saveBtn?.textContent?.trim() || saveBtn?.value
            };
        });

        if (!result.hasSaveBtn) {
            // Check for auto-save indicator
            const autoSave = await page.evaluate(() => {
                const text = document.body.textContent.toLowerCase();
                return text.includes('auto-save') || text.includes('automatically saved');
            });

            if (autoSave) {
                return { passed: true, message: 'Settings use auto-save (no manual save button needed)' };
            }

            return { passed: null, skipped: true, message: 'No save button found (may use auto-save)' };
        }

        return {
            passed: true,
            message: `Save button: "${result.buttonText}"`
        };
    },

    async resetToDefaultsButton(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/settings`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, .btn'));
            const resetBtn = buttons.find(btn => {
                const text = btn.textContent?.toLowerCase() || '';
                return text.includes('reset') || text.includes('default') || text.includes('restore');
            });

            return {
                hasResetBtn: !!resetBtn,
                buttonText: resetBtn?.textContent?.trim()
            };
        });

        if (!result.hasResetBtn) {
            return { passed: null, skipped: true, message: 'No reset to defaults button found' };
        }

        return {
            passed: true,
            message: `Reset button: "${result.buttonText}"`
        };
    },

    async autoSaveFunctionality(page, baseUrl) {
        // Settings auto-save on change: handleInputChange schedules a save and,
        // on success, showSaveSuccess() adds .ldr-save-success to the changed
        // input (settings.js:2651). The old selector '.settings input' matched
        // nothing (permanent skip) and it looked for a .toast that isn't used.
        await navigateTo(page, `${baseUrl}/settings`);
        const sel = '#settings-content input.ldr-settings-checkbox[name]:not([disabled])';
        await page.waitForSelector(sel, { timeout: 15000 });

        await page.click(sel);

        // Wait for the save round-trip to flag success on the input.
        let sawSuccess = false;
        try {
            await page.waitForFunction(
                () => !!document.querySelector('.ldr-save-success'),
                { timeout: 8000 }
            );
            sawSuccess = true;
        } catch { /* timeout — no save feedback appeared */ }

        return {
            passed: sawSuccess,
            message: sawSuccess
                ? 'Auto-save fired (.ldr-save-success applied after changing a setting)'
                : 'No .ldr-save-success feedback within 8s of changing a setting'
        };
    }
};

// ============================================================================
// Settings Help Tests
// ============================================================================
const SettingsHelpTests = {
    async settingDescriptions(page, baseUrl) {
        // Real per-setting help text is .ldr-input-help (settings_form.html).
        // The old probe [class*=description] only matched static page-header
        // divs (tautological).
        await navigateTo(page, `${baseUrl}/settings`);
        await page.waitForSelector('#settings-content .ldr-settings-item', { timeout: 15000 });

        const result = await page.evaluate(() => {
            const helps = Array.from(document.querySelectorAll('#settings-content .ldr-input-help'))
                .filter(el => el.textContent.trim().length > 0);
            return { count: helps.length, firstText: helps[0]?.textContent.trim().slice(0, 80) };
        });

        return {
            passed: result.count > 0,
            message: result.count > 0
                ? `Setting help text: ${result.count} non-empty .ldr-input-help (first: "${result.firstText}")`
                : 'No non-empty .ldr-input-help rendered in #settings-content'
        };
    },

    async tooltipsPresent(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/settings`);

        const result = await page.evaluate(() => {
            const tooltipTriggers = document.querySelectorAll(
                '[data-tooltip], ' +
                '[title], ' +
                '.tooltip-trigger, ' +
                '[data-toggle="tooltip"], ' +
                '.info-icon, ' +
                '.help-icon'
            );

            return {
                count: tooltipTriggers.length,
                hasTooltips: tooltipTriggers.length > 0
            };
        });

        if (!result.hasTooltips) {
            return { passed: null, skipped: true, message: 'No tooltips found' };
        }

        return {
            passed: true,
            message: `Tooltips: ${result.count} found`
        };
    }
};

// ============================================================================
// Raw Config Editor Tests
// ============================================================================
const RawConfigTests = {
    async rawConfigEditorExists(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/settings`);

        const result = await page.evaluate(() => {
            const jsonEditor = document.querySelector(
                '.json-editor, ' +
                '#raw-config, ' +
                'textarea.config-editor, ' +
                '.code-editor, ' +
                '[class*="raw-config"]'
            );

            const toggleBtn = document.querySelector(
                '[data-toggle="raw-config"], ' +
                '.show-raw-config, ' +
                '#toggle-raw-config'
            );

            return {
                hasEditor: !!jsonEditor,
                hasToggle: !!toggleBtn,
                toggleText: toggleBtn?.textContent?.trim()
            };
        });

        if (!result.hasEditor && !result.hasToggle) {
            return { passed: null, skipped: true, message: 'No raw config editor found' };
        }

        return {
            passed: true,
            message: `Raw config: editor=${result.hasEditor}, toggle="${result.toggleText}"`
        };
    }
};

// ============================================================================
// Main Test Runner
// ============================================================================
async function main() {
    log.section('Settings Interactions Tests');

    const ctx = await setupTest({ authenticate: true });
    const results = new TestResults('Settings Interactions Tests');
    const { page } = ctx;
    const { baseUrl } = ctx.config;

    const subTestTimeout = ctx.config.isCI ? 60000 : 30000;
    async function run(category, name, testFn) {
        try {
            const result = await withTimeout(
                testFn(page, baseUrl),
                subTestTimeout,
                `${category}/${name}`
            );
            if (result.skipped) {
                results.skip(category, name, result.message);
            } else {
                results.add(category, name, result.passed, result.message);
            }
        } catch (error) {
            results.add(category, name, false, `Error: ${error.message}`);
        }
    }

    try {
        // Settings Page Structure
        log.section('Settings Page Structure');

        await run('Page', 'Loads', (p, u) => SettingsPageTests.settingsPageLoads(p, u));
        await run('Page', 'Search Filter', (p, u) => SettingsPageTests.settingsSearchFilter(p, u));
        await run('Page', 'Search Filtering', (p, u) => SettingsPageTests.settingsSearchFiltering(p, u));

        // Settings Tabs
        log.section('Settings Tabs');

        await run('Tabs', 'Exist', (p, u) => SettingsTabsTests.settingsTabsExist(p, u));
        await run('Tabs', 'Navigation', (p, u) => SettingsTabsTests.tabNavigationWorks(p, u));
        await run('Tabs', 'Categories', (p, u) => SettingsTabsTests.specificTabsPresent(p, u));

        // Settings Controls
        log.section('Settings Controls');

        await run('Controls', 'Text Inputs', (p, u) => SettingsControlsTests.textInputSettings(p, u));
        await run('Controls', 'Number Inputs', (p, u) => SettingsControlsTests.numberInputSettings(p, u));
        await run('Controls', 'Toggles', (p, u) => SettingsControlsTests.toggleSwitchSettings(p, u));
        await run('Controls', 'Dropdowns', (p, u) => SettingsControlsTests.dropdownSelectSettings(p, u));
        await run('Controls', 'Toggle Works', (p, u) => SettingsControlsTests.toggleSwitchToggleable(p, u));

        // Settings Save
        log.section('Settings Save');

        await run('Save', 'Button Exists', (p, u) => SettingsSaveTests.saveButtonExists(p, u));
        await run('Save', 'Reset Button', (p, u) => SettingsSaveTests.resetToDefaultsButton(p, u));
        await run('Save', 'Auto-Save', (p, u) => SettingsSaveTests.autoSaveFunctionality(p, u));

        // Settings Help
        log.section('Settings Help');

        await run('Help', 'Descriptions', (p, u) => SettingsHelpTests.settingDescriptions(p, u));
        await run('Help', 'Tooltips', (p, u) => SettingsHelpTests.tooltipsPresent(p, u));

        // Raw Config
        log.section('Raw Config');

        await run('RawConfig', 'Editor Exists', (p, u) => RawConfigTests.rawConfigEditorExists(p, u));

    } catch (error) {
        log.error(`Fatal error: ${error.message}`);
        console.error(error.stack);
    } finally {
        results.print();
        results.save();
        await teardownTest(ctx);
        process.exit(results.exitCode());
    }
}

// Run if executed directly
if (require.main === module) {
    main().catch(error => {
        console.error('Test runner failed:', error);
        process.exit(1);
    });
}

module.exports = { SettingsPageTests, SettingsTabsTests, SettingsControlsTests, SettingsSaveTests, SettingsHelpTests, RawConfigTests };
