#!/usr/bin/env node
/**
 * Research Results & Export UI Tests
 *
 * Tests for the results page including star ratings, export buttons,
 * and download functionality.
 *
 * Run: node test_results_exports_ci.js
 */

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

// ============================================================================
// Helper to find a completed research
// ============================================================================
async function findCompletedResearch(page, baseUrl) {
    return await page.evaluate(async (url) => {
        try {
            const response = await fetch(`${url}/history/api`);
            if (!response.ok) return null;
            const data = await response.json();
            const completed = data.items?.find(item => item.status === 'completed');
            return completed?.id || null;
        } catch {
            return null;
        }
    }, baseUrl);
}

// ============================================================================
// Results Page Structure Tests
// ============================================================================
const ResultsPageTests = {
    async resultsPageLoads(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research found to test results page' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            const hasContent = document.body.textContent.length > 100;
            const hasResultsContainer = !!document.querySelector('.results, .research-results, #results, main');
            const title = document.title;

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

        return {
            passed: result.hasContent && result.hasResultsContainer,
            message: `Results page loads (content=${result.hasContent}, container=${result.hasResultsContainer})`
        };
    },

    async resultsMetadataDisplay(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for metadata test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            // Look for metadata elements
            const queryDisplay = document.querySelector('.query, .research-query, [class*="query"]');
            const dateDisplay = document.querySelector('.date, .created, [class*="date"], time');
            const modeBadge = document.querySelector('.badge, .mode, [class*="mode"]');
            const durationDisplay = document.querySelector('.duration, [class*="duration"], [class*="time"]');

            return {
                hasQuery: !!queryDisplay,
                queryText: queryDisplay?.textContent?.trim()?.substring(0, 50),
                hasDate: !!dateDisplay,
                hasMode: !!modeBadge,
                hasDuration: !!durationDisplay,
                modeText: modeBadge?.textContent?.trim()
            };
        });

        const hasMetadata = result.hasQuery || result.hasDate || result.hasMode;

        return {
            passed: hasMetadata,
            message: `Metadata: query="${result.queryText}", date=${result.hasDate}, mode="${result.modeText}"`
        };
    },

    async resultsContentRendered(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for content test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            // Look for rendered markdown content
            const contentArea = document.querySelector('.markdown, .content, .results-content, .report, article');
            const hasHeadings = document.querySelectorAll('h1, h2, h3').length > 0;
            const hasParagraphs = document.querySelectorAll('p').length > 0;
            const hasLists = document.querySelectorAll('ul, ol').length > 0;
            const hasLinks = document.querySelectorAll('a[href^="http"]').length > 0;

            return {
                hasContentArea: !!contentArea,
                contentLength: contentArea?.textContent?.length || 0,
                hasHeadings,
                hasParagraphs,
                hasLists,
                hasLinks
            };
        });

        // Consider content rendered if we have a content area with text OR if structural elements exist
        const hasContent = (result.hasContentArea && result.contentLength > 100) ||
                          (result.hasHeadings && result.hasParagraphs);

        // Skip instead of fail if no completed research was available
        if (!hasContent && result.contentLength === 0) {
            return { passed: null, skipped: true, message: 'No content rendered (may not have completed research data)' };
        }

        return {
            passed: hasContent,
            message: `Content rendered: ${result.contentLength} chars, headings=${result.hasHeadings}, paragraphs=${result.hasParagraphs}, lists=${result.hasLists}`
        };
    }
};

// ============================================================================
// Star Rating Tests
// ============================================================================
const StarRatingTests = {
    async starRatingWidgetExists(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for star rating test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            const starWidget = document.querySelector(
                '.star-rating, ' +
                '.rating, ' +
                '[class*="star"], ' +
                '[class*="rating"], ' +
                '.stars'
            );

            const stars = document.querySelectorAll(
                '.star, ' +
                '[class*="star"]:not([class*="start"]), ' +
                'input[type="radio"][name*="rating"], ' +
                '.rating-star'
            );

            return {
                hasWidget: !!starWidget,
                starCount: stars.length,
                widgetClass: starWidget?.className
            };
        });

        if (!result.hasWidget && result.starCount === 0) {
            return { passed: null, skipped: true, message: 'No star rating widget found on results page' };
        }

        return {
            passed: true,
            message: `Star rating widget: ${result.starCount} stars found, class="${result.widgetClass}"`
        };
    },

    async starRatingClickable(page, baseUrl) {
        // The star-rating widget (#research-rating > .ldr-star) is rendered
        // server-side on every /results/<id> page and its click handler wires
        // up purely from the URL id, so it works with no LLM and even when the
        // DB has no completed research. Use a completed research if one exists,
        // otherwise a synthetic id — the widget renders either way.
        const researchId = (await findCompletedResearch(page, baseUrl)) || '0';

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            const widget = document.getElementById('research-rating');
            const stars = Array.from(document.querySelectorAll('#research-rating .ldr-star'));

            if (!widget || stars.length === 0) {
                return { onResultsPage: !!widget, starCount: stars.length };
            }

            // Click the third star (rating of 3).
            const targetStar = stars[2];
            targetStar.click();

            // Real signal: clicking the Nth star adds the "active" class to that
            // star and every lower-indexed star (setRating in results.js), and
            // leaves higher-indexed stars unmarked.
            const activeFlags = stars.map(s => s.classList.contains('active'));

            return {
                onResultsPage: true,
                starCount: stars.length,
                activeFlags,
                lowerActive: activeFlags.slice(0, 3).every(Boolean),
                higherInactive: activeFlags.slice(3).every(v => !v)
            };
        });

        // The widget must actually be present on the results page; if it's not,
        // the wrong page (login/error) loaded — that's a real failure, not a skip.
        if (!result.onResultsPage || result.starCount === 0) {
            return {
                passed: false,
                message: `Star rating widget not found on results page (onResultsPage=${result.onResultsPage}, stars=${result.starCount})`
            };
        }

        const passed = result.starCount === 5 && result.lowerActive && result.higherInactive;

        return {
            passed,
            message: `Click on 3rd star sets active state [${result.activeFlags.map(v => (v ? '1' : '0')).join('')}] (expected 11100)`
        };
    },

    async starRatingHoverFeedback(page, baseUrl) {
        // Same widget as starRatingClickable — rendered server-side on every
        // /results/<id> page, so this works with no LLM and an empty DB.
        const researchId = (await findCompletedResearch(page, baseUrl)) || '0';

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const stars = await page.$$('#research-rating .ldr-star');

        // Star widget must be present on the results page; if it's missing the
        // wrong page loaded — a real failure, not a skip.
        if (stars.length !== 5) {
            return {
                passed: false,
                message: `Expected 5 star elements on results page, found ${stars.length}`
            };
        }

        // No star should carry the hover class before we hover.
        const beforeHover = await page.evaluate(() =>
            Array.from(document.querySelectorAll('#research-rating .ldr-star'))
                .some(s => s.classList.contains('ldr-hover'))
        );

        // Hover the 4th star (mouseenter triggers highlightStars in results.js).
        await stars[3].hover();
        await delay(200);

        // Specific expected affordance: hovering the Nth star adds the
        // "ldr-hover" class to that star and every lower-indexed star.
        const afterHover = await page.evaluate(() => {
            const flags = Array.from(document.querySelectorAll('#research-rating .ldr-star'))
                .map(s => s.classList.contains('ldr-hover'));
            return {
                flags,
                lowerHovered: flags.slice(0, 4).every(Boolean),
                higherNotHovered: flags.slice(4).every(v => !v)
            };
        });

        const passed = !beforeHover && afterHover.lowerHovered && afterHover.higherNotHovered;

        return {
            passed,
            message: `Hover on 4th star adds ldr-hover [${afterHover.flags.map(v => (v ? '1' : '0')).join('')}] (expected 11110, beforeAnyHover=${beforeHover})`
        };
    }
};

// ============================================================================
// Export Button Tests
// ============================================================================
const ExportButtonTests = {
    async exportButtonsExist(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for export button test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, a.btn, .btn'));
            const exportButtons = {};

            for (const btn of buttons) {
                const text = btn.textContent?.toLowerCase() || '';
                const href = btn.href?.toLowerCase() || '';
                const onclick = btn.getAttribute('onclick')?.toLowerCase() || '';

                if (text.includes('pdf') || href.includes('pdf') || onclick.includes('pdf')) {
                    exportButtons.pdf = true;
                }
                if (text.includes('markdown') || text.includes('.md') || href.includes('markdown')) {
                    exportButtons.markdown = true;
                }
                if (text.includes('latex') || text.includes('.tex') || href.includes('latex')) {
                    exportButtons.latex = true;
                }
                if (text.includes('quarto') || text.includes('.qmd') || href.includes('quarto')) {
                    exportButtons.quarto = true;
                }
                if (text.includes('ris') || text.includes('zotero') || href.includes('ris')) {
                    exportButtons.ris = true;
                }
                if (text.includes('export') || text.includes('download')) {
                    exportButtons.generic = true;
                }
            }

            return {
                found: Object.keys(exportButtons),
                pdf: exportButtons.pdf || false,
                markdown: exportButtons.markdown || false,
                latex: exportButtons.latex || false,
                quarto: exportButtons.quarto || false,
                ris: exportButtons.ris || false,
                generic: exportButtons.generic || false
            };
        });

        const hasExportButtons = result.found.length > 0;

        return {
            passed: hasExportButtons,
            message: `Export buttons: ${result.found.join(', ') || 'none found'}`
        };
    },

    async pdfExportButton(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for PDF export test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, a.btn, .btn, a[download]'));
            const pdfButton = buttons.find(btn => {
                const text = btn.textContent?.toLowerCase() || '';
                const href = btn.href?.toLowerCase() || '';
                return text.includes('pdf') || href.includes('pdf') || href.includes('/export/');
            });

            return {
                hasPdfButton: !!pdfButton,
                buttonText: pdfButton?.textContent?.trim(),
                href: pdfButton?.href
            };
        });

        if (!result.hasPdfButton) {
            return { passed: null, skipped: true, message: 'No PDF export button found' };
        }

        return {
            passed: true,
            message: `PDF export button: "${result.buttonText}" (href: ${result.href?.substring(0, 50)}...)`
        };
    },

    async markdownExportButton(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for Markdown export test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, a.btn, .btn, a[download]'));
            const mdButton = buttons.find(btn => {
                const text = btn.textContent?.toLowerCase() || '';
                const href = btn.href?.toLowerCase() || '';
                return text.includes('markdown') || text.includes('.md') || href.includes('markdown');
            });

            return {
                hasMdButton: !!mdButton,
                buttonText: mdButton?.textContent?.trim(),
                href: mdButton?.href
            };
        });

        if (!result.hasMdButton) {
            return { passed: null, skipped: true, message: 'No Markdown export button found' };
        }

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

    async exportDropdownMenu(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for dropdown test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            // Look for export dropdown
            const dropdown = document.querySelector(
                '.dropdown, ' +
                '[class*="export-dropdown"], ' +
                '.export-menu, ' +
                'details.export, ' +
                '[data-dropdown="export"]'
            );

            const dropdownToggle = document.querySelector(
                '.dropdown-toggle, ' +
                '[data-toggle="dropdown"], ' +
                'summary, ' +
                '.export-btn'
            );

            return {
                hasDropdown: !!dropdown,
                hasToggle: !!dropdownToggle,
                toggleText: dropdownToggle?.textContent?.trim()
            };
        });

        if (!result.hasDropdown && !result.hasToggle) {
            return { passed: null, skipped: true, message: 'No export dropdown menu found' };
        }

        return {
            passed: true,
            message: `Export dropdown: toggle="${result.toggleText}"`
        };
    }
};

// ============================================================================
// Action Button Tests
// ============================================================================
const ActionButtonTests = {
    async backToHistoryButton(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for back button test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, a.btn, .btn, a'));
            const backButton = buttons.find(btn => {
                const text = btn.textContent?.toLowerCase() || '';
                const href = btn.href?.toLowerCase() || '';
                return text.includes('back') || text.includes('history') ||
                       href.includes('history') || btn.classList.contains('back');
            });

            return {
                hasBackButton: !!backButton,
                buttonText: backButton?.textContent?.trim(),
                href: backButton?.href
            };
        });

        return {
            passed: result.hasBackButton,
            message: result.hasBackButton
                ? `Back button: "${result.buttonText}"`
                : 'No back to history button found'
        };
    },

    async viewMetricsButton(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for metrics button test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, a.btn, .btn, a'));
            const metricsButton = buttons.find(btn => {
                const text = btn.textContent?.toLowerCase() || '';
                const href = btn.href?.toLowerCase() || '';
                return text.includes('metric') || text.includes('stats') ||
                       text.includes('analytics') || href.includes('metrics');
            });

            return {
                hasMetricsButton: !!metricsButton,
                buttonText: metricsButton?.textContent?.trim()
            };
        });

        if (!result.hasMetricsButton) {
            return { passed: null, skipped: true, message: 'No view metrics button found' };
        }

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

    async helpImproveButton(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for help button test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, a.btn, .btn, a'));
            const helpButton = buttons.find(btn => {
                const text = btn.textContent?.toLowerCase() || '';
                return text.includes('help') || text.includes('improve') ||
                       text.includes('feedback') || text.includes('report');
            });

            return {
                hasHelpButton: !!helpButton,
                buttonText: helpButton?.textContent?.trim()
            };
        });

        if (!result.hasHelpButton) {
            return { passed: null, skipped: true, message: 'No help/feedback button found' };
        }

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

// ============================================================================
// Log Panel Tests
// ============================================================================
const LogPanelTests = {
    async logPanelExists(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for log panel test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        const result = await page.evaluate(() => {
            const logPanel = document.querySelector(
                '.logs, ' +
                '.log-panel, ' +
                '#logs, ' +
                '[class*="log"], ' +
                '.research-logs, ' +
                'details.logs, ' +
                '.accordion-logs'
            );

            const logToggle = document.querySelector(
                '[data-toggle="logs"], ' +
                '.log-toggle, ' +
                'summary:has(~ .logs), ' +
                '[aria-controls*="log"]'
            );

            return {
                hasLogPanel: !!logPanel,
                hasToggle: !!logToggle,
                toggleText: logToggle?.textContent?.trim()
            };
        });

        if (!result.hasLogPanel && !result.hasToggle) {
            return { passed: null, skipped: true, message: 'No log panel found on results page' };
        }

        return {
            passed: true,
            message: `Log panel: panel=${result.hasLogPanel}, toggle="${result.toggleText}"`
        };
    },

    async logPanelExpandable(page, baseUrl) {
        const researchId = await findCompletedResearch(page, baseUrl);

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No completed research for expandable log test' };
        }

        await navigateTo(page, `${baseUrl}/results/${researchId}`);

        // Click log toggle
        const clicked = await page.evaluate(() => {
            const toggle = document.querySelector(
                '[data-toggle="logs"], ' +
                '.log-toggle, ' +
                'summary, ' +
                '[aria-controls*="log"], ' +
                '.collapsible-header'
            );

            if (toggle) {
                toggle.click();
                return true;
            }
            return false;
        });

        if (!clicked) {
            return { passed: null, skipped: true, message: 'No log toggle to click' };
        }

        await delay(300);

        const result = await page.evaluate(() => {
            const logContent = document.querySelector(
                '.logs, .log-content, .log-entries, pre.logs, code.logs'
            );

            return {
                hasContent: !!logContent,
                contentLength: logContent?.textContent?.length || 0,
                visible: logContent ? window.getComputedStyle(logContent).display !== 'none' : false
            };
        });

        return {
            passed: result.visible || result.contentLength > 0,
            message: `Log panel expandable: visible=${result.visible}, content=${result.contentLength} chars`
        };
    }
};

// ============================================================================
// Main Test Runner
// ============================================================================
async function main() {
    log.section('Research Results & Export Tests');

    const ctx = await setupTest({ authenticate: true });
    const results = new TestResults('Results & Export 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 {
        // Results Page Structure
        log.section('Results Page Structure');

        await run('Page', 'Results Page Loads', (p, u) => ResultsPageTests.resultsPageLoads(p, u));
        await run('Page', 'Metadata Display', (p, u) => ResultsPageTests.resultsMetadataDisplay(p, u));
        await run('Page', 'Content Rendered', (p, u) => ResultsPageTests.resultsContentRendered(p, u));

        // Star Rating Tests
        log.section('Star Rating');

        await run('Stars', 'Widget Exists', (p, u) => StarRatingTests.starRatingWidgetExists(p, u));
        await run('Stars', 'Clickable', (p, u) => StarRatingTests.starRatingClickable(p, u));
        await run('Stars', 'Hover Feedback', (p, u) => StarRatingTests.starRatingHoverFeedback(p, u));

        // Export Button Tests
        log.section('Export Buttons');

        await run('Export', 'Buttons Exist', (p, u) => ExportButtonTests.exportButtonsExist(p, u));
        await run('Export', 'PDF Button', (p, u) => ExportButtonTests.pdfExportButton(p, u));
        await run('Export', 'Markdown Button', (p, u) => ExportButtonTests.markdownExportButton(p, u));
        await run('Export', 'Dropdown Menu', (p, u) => ExportButtonTests.exportDropdownMenu(p, u));

        // Action Buttons
        log.section('Action Buttons');

        await run('Actions', 'Back to History', (p, u) => ActionButtonTests.backToHistoryButton(p, u));
        await run('Actions', 'View Metrics', (p, u) => ActionButtonTests.viewMetricsButton(p, u));
        await run('Actions', 'Help/Feedback', (p, u) => ActionButtonTests.helpImproveButton(p, u));

        // Log Panel
        log.section('Log Panel');

        await run('Logs', 'Panel Exists', (p, u) => LogPanelTests.logPanelExists(p, u));
        await run('Logs', 'Expandable', (p, u) => LogPanelTests.logPanelExpandable(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 = { ResultsPageTests, StarRatingTests, ExportButtonTests, ActionButtonTests, LogPanelTests };
