#!/usr/bin/env node
/**
 * Settings Pages UI Tests
 *
 * Tests for the settings dashboard, tabs, and configuration options.
 *
 * Run: node test_settings_pages_ci.js
 */
const { setupTest, teardownTest, TestResults, log, delay, waitForVisible, 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(() => {
            return {
                hasSettingsContent: !!document.querySelector('.settings-container, .ldr-settings, #settings, .settings-dashboard'),
                hasForm: !!document.querySelector('form'),
                hasTabs: !!document.querySelector('.nav-tabs, .tab-list, [role="tablist"], .settings-tabs'),
                pageTitle: document.title,
                hasAnyInputs: document.querySelectorAll('input, select, textarea').length > 0
            };
        });

        const passed = result.hasSettingsContent || result.hasAnyInputs;
        return {
            passed,
            message: passed
                ? `Settings page loaded (content=${result.hasSettingsContent}, form=${result.hasForm}, tabs=${result.hasTabs})`
                : 'Settings page failed to load expected content'
        };
    },

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

        const result = await page.evaluate(() => {
            const tabs = document.querySelectorAll('.nav-tabs .nav-link, .tab-list button, [role="tab"], .settings-tab');
            const tabTexts = Array.from(tabs).map(t => t.textContent?.trim());

            // Also check for category sections if tabs aren't used
            const categories = document.querySelectorAll('.settings-category, .setting-section, h2, h3');
            const categoryTexts = Array.from(categories).map(c => c.textContent?.trim()).filter(t => t && t.length < 50);

            return {
                tabCount: tabs.length,
                tabNames: tabTexts.slice(0, 10),
                categoryCount: categories.length,
                categoryNames: categoryTexts.slice(0, 10)
            };
        });

        const hasTabs = result.tabCount > 0;
        const hasCategories = result.categoryCount > 0;

        return {
            passed: hasTabs || hasCategories,
            message: hasTabs
                ? `Found ${result.tabCount} tabs: ${result.tabNames.join(', ')}`
                : hasCategories
                    ? `Found ${result.categoryCount} setting categories`
                    : 'No tabs or categories found'
        };
    },

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

        const tabs = await page.$$('.nav-tabs .nav-link, [role="tab"]');
        if (tabs.length < 2) {
            return { passed: null, skipped: true, message: 'Not enough tabs to test navigation' };
        }

        try {
            // Click second tab using evaluate to avoid stale element issues
            await page.evaluate(() => {
                const tabElements = document.querySelectorAll('.nav-tabs .nav-link, [role="tab"]');
                if (tabElements[1]) {
                    tabElements[1].click();
                }
            });

            // Wait for page to stabilize
            await delay(1000);

            // Wait for network to settle in case of navigation
            await page.waitForFunction(() => document.readyState === 'complete', { timeout: 5000 }).catch(() => {});

            const result = await page.evaluate(() => {
                const activeTab = document.querySelector('.nav-link.active, [role="tab"][aria-selected="true"]');
                const visiblePanel = document.querySelector('.tab-pane.active, .tab-pane.show, [role="tabpanel"]:not([hidden])');

                return {
                    hasActiveTab: !!activeTab,
                    activeTabText: activeTab?.textContent?.trim(),
                    hasVisiblePanel: !!visiblePanel
                };
            });

            return {
                passed: result.hasActiveTab,
                message: result.hasActiveTab
                    ? `Tab navigation works (active: "${result.activeTabText}")`
                    : 'Tab navigation not working properly'
            };
        } catch (err) {
            // Handle navigation or context destruction gracefully
            if (err.message && (err.message.includes('context') || err.message.includes('navigation') || err.message.includes('Target closed'))) {
                return { passed: null, skipped: true, message: 'Tab click caused page navigation - skipping' };
            }
            return { passed: null, skipped: true, message: `Tab click failed: ${err.message}` };
        }
    }
};

// ============================================================================
// Settings Input Tests
// ============================================================================
const SettingsInputTests = {
    async modelProviderSetting(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/settings/`);

        // Wait for settings form to render (inputs are loaded async via JS)
        try {
            await page.waitForSelector('[name="llm.provider"], select[name*="provider"]', { timeout: 15000 });
        } catch {
            return { passed: null, skipped: true, message: 'No model provider setting found (form did not render)' };
        }

        const result = await page.evaluate(() => {
            const providerEl = document.querySelector(
                'select[name*="provider"], ' +
                'select[data-setting*="provider"], ' +
                '#llm_provider, ' +
                '[name="llm.provider"]'
            );

            if (!providerEl) return { exists: false };

            // Accept both <select> and <input> (custom dropdown)
            if (providerEl.tagName === 'SELECT') {
                const options = Array.from(providerEl.options);
                return {
                    exists: true,
                    optionCount: options.length,
                    options: options.map(o => ({ value: o.value, text: o.text })).slice(0, 8),
                    currentValue: providerEl.value
                };
            }

            return {
                exists: true,
                isCustom: true,
                currentValue: providerEl.value
            };
        });

        if (!result.exists) {
            return { passed: null, skipped: true, message: 'No model provider setting found' };
        }

        if (result.isCustom) {
            return {
                passed: true,
                message: `Model provider setting found (custom input, current: ${result.currentValue || 'default'})`
            };
        }

        return {
            passed: result.optionCount > 0,
            message: `Model provider dropdown has ${result.optionCount} options (current: ${result.currentValue})`
        };
    },

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

        // Ensure "All Settings" tab is active (previous test may have switched tabs)
        await page.evaluate(() => {
            const allTab = document.querySelector('[data-tab="all"], .ldr-settings-tab');
            if (allTab) allTab.click();
        });

        // Wait for settings form to render (search.tool is a hidden input in a custom dropdown)
        try {
            await page.waitForSelector('[name="search.tool"], select[name*="search"]', { timeout: 15000 });
        } catch {
            return { passed: null, skipped: true, message: 'No search engine setting found (form did not render)' };
        }

        const result = await page.evaluate(() => {
            // Check for standard <select>
            const engineSelect = document.querySelector(
                'select[name*="search"], ' +
                'select[data-setting*="search_tool"]'
            );

            if (engineSelect && engineSelect.tagName === 'SELECT') {
                const options = Array.from(engineSelect.options);
                return {
                    exists: true,
                    optionCount: options.length,
                    options: options.map(o => o.text).slice(0, 8),
                    currentValue: engineSelect.value
                };
            }

            // Check for custom dropdown component (used by LDR)
            const customInput = document.querySelector(
                '#search_tool, ' +
                '[name="search.tool"], ' +
                'input[id*="search"][id*="tool"]'
            );

            if (customInput) {
                const dropdown = customInput.closest('.ldr-custom-dropdown') || customInput.parentElement;
                return {
                    exists: true,
                    isCustom: true,
                    currentValue: customInput.value
                };
            }

            return { exists: false };
        });

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

        if (result.isCustom) {
            return {
                passed: true,
                message: `Search engine setting found (custom dropdown, current: ${result.currentValue || 'default'})`
            };
        }

        return {
            passed: result.optionCount > 0,
            message: `Search engine dropdown has ${result.optionCount} options: ${result.options.slice(0, 4).join(', ')}`
        };
    },

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

        // Wait for settings form to render
        try {
            await page.waitForSelector('[name="llm.temperature"], input[name*="temperature"]', { timeout: 15000 });
        } catch {
            return { passed: null, skipped: true, message: 'No temperature setting found (form did not render)' };
        }

        const result = await page.evaluate(() => {
            const tempInput = document.querySelector(
                'input[name*="temperature"], ' +
                'input[data-setting*="temperature"], ' +
                '#temperature, ' +
                '[name="llm.temperature"]'
            );

            if (!tempInput) return { exists: false };

            return {
                exists: true,
                type: tempInput.type,
                value: tempInput.value,
                min: tempInput.min,
                max: tempInput.max
            };
        });

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

        return {
            passed: true,
            message: `Temperature input found (type: ${result.type}, value: ${result.value}, range: ${result.min}-${result.max})`
        };
    },

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

        // Wait for settings form to render
        try {
            await page.waitForSelector('input[name*="api_key"], input[type="password"][name*="key"]', { timeout: 15000 });
        } catch {
            return { passed: null, skipped: true, message: 'No API key fields found (form did not render)' };
        }

        const result = await page.evaluate(() => {
            const apiKeyInputs = document.querySelectorAll(
                'input[name*="api_key"], ' +
                'input[name*="apikey"], ' +
                'input[type="password"][name*="key"], ' +
                '[data-setting*="api_key"]'
            );

            if (apiKeyInputs.length === 0) return { exists: false };

            const firstKey = apiKeyInputs[0];
            return {
                exists: true,
                count: apiKeyInputs.length,
                isMasked: firstKey.type === 'password',
                inputType: firstKey.type,
                placeholder: firstKey.placeholder
            };
        });

        if (!result.exists) {
            return { passed: null, skipped: true, message: 'No API key fields found' };
        }

        return {
            passed: result.isMasked,
            message: result.isMasked
                ? `${result.count} API key field(s) are masked (type=password)`
                : `API key fields found but not masked (type=${result.inputType})`
        };
    }
};

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

        const result = await page.evaluate(() => {
            // Note: :contains() is not valid CSS, removed from selector
            const saveButtonsByCss = document.querySelectorAll(
                'button[type="submit"], ' +
                '.btn-save, ' +
                '[onclick*="save"], ' +
                'button.btn-primary'
            );

            // More thorough search - find buttons by text content
            const allButtons = Array.from(document.querySelectorAll('button, input[type="submit"]'));
            const saveBtn = allButtons.find(b =>
                b.textContent?.toLowerCase().includes('save') ||
                b.value?.toLowerCase().includes('save')
            );

            return {
                hasSaveButton: !!saveBtn || saveButtonsByCss.length > 0,
                buttonText: saveBtn?.textContent?.trim() || saveBtn?.value,
                buttonCount: allButtons.filter(b =>
                    b.textContent?.toLowerCase().includes('save') ||
                    b.value?.toLowerCase().includes('save')
                ).length
            };
        });

        return {
            passed: result.hasSaveButton,
            message: result.hasSaveButton
                ? `Save button found ("${result.buttonText}")`
                : 'No save button found'
        };
    },

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

        const result = await page.evaluate(() => {
            const allButtons = Array.from(document.querySelectorAll('button, input[type="button"], input[type="reset"]'));
            const resetBtn = allButtons.find(b =>
                b.textContent?.toLowerCase().includes('reset') ||
                b.textContent?.toLowerCase().includes('default') ||
                b.value?.toLowerCase().includes('reset')
            );

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

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

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

    async searchFilterExists(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 search/filter input on settings page' };
        }

        return {
            passed: true,
            message: `Settings search/filter found (placeholder: "${result.placeholder}")`
        };
    }
};

// ============================================================================
// Settings Status Tests
// ============================================================================
const SettingsStatusTests = {
    async warningsDisplay(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/settings/`);

        const result = await page.evaluate(() => {
            const warnings = document.querySelectorAll(
                '.alert-warning, ' +
                '.warning, ' +
                '.config-warning, ' +
                '[class*="warning"]'
            );

            const warningTexts = Array.from(warnings)
                .map(w => w.textContent?.trim())
                .filter(t => t && t.length < 200);

            return {
                warningCount: warnings.length,
                warningTexts: warningTexts.slice(0, 3)
            };
        });

        // This is informational - either warnings or no warnings is OK
        return {
            passed: true,
            message: result.warningCount > 0
                ? `${result.warningCount} configuration warning(s) displayed`
                : 'No configuration warnings (good configuration)'
        };
    },

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

        // Wait for settings form to render before checking for ollama elements
        try {
            await page.waitForSelector('[name="llm.provider"], input[name*="api_key"]', { timeout: 15000 });
        } catch {
            // Form didn't render — skip
        }

        const result = await page.evaluate(() => {
            const ollamaStatus = document.querySelector(
                '.ollama-status, ' +
                '[data-ollama-status], ' +
                '#ollama-status, ' +
                '.status-indicator[class*="ollama"]'
            );

            const ollamaSection = document.querySelector('[class*="ollama"], [id*="ollama"]');

            return {
                hasStatusIndicator: !!ollamaStatus,
                hasOllamaSection: !!ollamaSection,
                statusText: ollamaStatus?.textContent?.trim()
            };
        });

        if (!result.hasStatusIndicator && !result.hasOllamaSection) {
            return { passed: null, skipped: true, message: 'No Ollama status indicator found' };
        }

        return {
            passed: true,
            message: result.hasStatusIndicator
                ? `Ollama status: "${result.statusText}"`
                : 'Ollama section present on page'
        };
    },

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

        const result = await page.evaluate(async (url) => {
            try {
                const response = await fetch(`${url}/settings/api/available-models`);
                if (!response.ok) return { ok: false, status: response.status };

                const data = await response.json();
                return {
                    ok: true,
                    hasProviders: Object.keys(data).length > 0,
                    providers: Object.keys(data).slice(0, 5)
                };
            } catch (e) {
                return { ok: false, error: e.message };
            }
        }, baseUrl);

        return {
            passed: result.ok,
            message: result.ok
                ? `Available models API works (providers: ${result.providers?.join(', ') || 'none'})`
                : `Available models API failed: ${result.error || 'status ' + result.status}`
        };
    },

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

        const result = await page.evaluate(async (url) => {
            try {
                const response = await fetch(`${url}/settings/api/available-search-engines`);
                if (!response.ok) return { ok: false, status: response.status };

                const data = await response.json();
                const engines = Array.isArray(data) ? data : Object.keys(data);
                return {
                    ok: true,
                    engineCount: engines.length,
                    engines: engines.slice(0, 5)
                };
            } catch (e) {
                return { ok: false, error: e.message };
            }
        }, baseUrl);

        return {
            passed: result.ok,
            message: result.ok
                ? `Search engines API works (${result.engineCount} engines: ${result.engines?.join(', ')})`
                : `Search engines API failed: ${result.error || 'status ' + result.status}`
        };
    }
};

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

    const ctx = await setupTest({ authenticate: true });
    const results = new TestResults('Settings Pages 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 {
        // Page Structure Tests
        log.section('Page Structure');
        await run('Structure', 'Settings Page Loads', (p, u) => SettingsPageTests.settingsPageLoads(p, u));
        await run('Structure', 'Settings Tabs Exist', (p, u) => SettingsPageTests.settingsTabsExist(p, u));
        await run('Structure', 'Tab Navigation', (p, u) => SettingsPageTests.settingsTabNavigation(p, u));

        // Input Tests
        log.section('Settings Inputs');
        await run('Inputs', 'Model Provider Setting', (p, u) => SettingsInputTests.modelProviderSetting(p, u));
        await run('Inputs', 'Search Engine Setting', (p, u) => SettingsInputTests.searchEngineSetting(p, u));
        await run('Inputs', 'Temperature Setting', (p, u) => SettingsInputTests.temperatureSetting(p, u));
        await run('Inputs', 'API Key Field Masked', (p, u) => SettingsInputTests.apiKeyFieldMasked(p, u));

        // Action Tests
        log.section('Settings Actions');
        await run('Actions', 'Save Button Exists', (p, u) => SettingsActionTests.saveButtonExists(p, u));
        await run('Actions', 'Reset Button Exists', (p, u) => SettingsActionTests.resetButtonExists(p, u));
        await run('Actions', 'Search Filter Exists', (p, u) => SettingsActionTests.searchFilterExists(p, u));

        // Status Tests
        log.section('Settings Status & APIs');
        await run('Status', 'Warnings Display', (p, u) => SettingsStatusTests.warningsDisplay(p, u));
        await run('Status', 'Ollama Status Indicator', (p, u) => SettingsStatusTests.ollamaStatusIndicator(p, u));
        await run('API', 'Available Models API', (p, u) => SettingsStatusTests.availableModelsApiWorks(p, u));
        await run('API', 'Available Search Engines API', (p, u) => SettingsStatusTests.availableSearchEnginesApiWorks(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, SettingsInputTests, SettingsActionTests, SettingsStatusTests };
