#!/usr/bin/env node
/**
 * History Page UI Tests
 *
 * Tests for the research history page and its functionality.
 *
 * Run: node test_history_page_ci.js
 */
const { setupTest, teardownTest, TestResults, log, delay, navigateTo, withTimeout } = require('./test_lib');

// ============================================================================
// History Page Structure Tests
// ============================================================================
const HistoryPageTests = {
    async historyPageLoads(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/history`);

        // Assert the page-specific container AND its header text. The history
        // template renders `<div class="ldr-page" id="history">` with an
        // `<h1>Research History</h1>` server-side (no data / no LLM needed),
        // so a wrong page (login/error) would fail this — unlike a generic
        // `h1`/`.page-title` OR-chain that matches any page's chrome.
        const result = await page.evaluate(() => {
            const container = document.querySelector('#history.ldr-page');
            const header = container?.querySelector('.ldr-page-header h1');
            return {
                hasContainer: !!container,
                headerText: header?.textContent?.trim() || '',
                // The history list mount point is part of the page chrome and
                // renders regardless of how many items the API returns.
                hasHistoryList: !!document.querySelector('#history-items.ldr-history-list'),
                itemCount: document.querySelectorAll('.ldr-history-item').length
            };
        });

        const headerMatches = /history/i.test(result.headerText);
        const passed = result.hasContainer && headerMatches && result.hasHistoryList;
        return {
            passed,
            message: passed
                ? `History page loaded (container #history, header: "${result.headerText}", items: ${result.itemCount})`
                : `History page failed to load (container=${result.hasContainer}, header="${result.headerText}", list=${result.hasHistoryList})`
        };
    },

    async historyTableStructure(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/history`);

        // Wait for JS to render history items from API
        await page.waitForFunction(
            () => {
                if (document.querySelectorAll('.ldr-history-item, .history-item, [data-research-id], table tbody tr').length > 0) {
                    return true;
                }
                // The empty-state node (#history-empty-message.ldr-empty-state) is
                // server-rendered with display:none and only toggled VISIBLE by
                // renderHistoryItems when the list is genuinely empty. Require it
                // to be visible (not merely present) so this wait actually blocks
                // until the client-side render has run, instead of resolving on the
                // first poll against the always-present hidden node.
                const empty = document.querySelector('.ldr-empty-state, .no-results');
                return !!empty && getComputedStyle(empty).display !== 'none';
            },
            { timeout: 5000 }
        ).catch(() => {});

        const result = await page.evaluate(() => {
            const table = document.querySelector('table, .history-table');
            if (table) {
                const headers = Array.from(table.querySelectorAll('th')).map(th => th.textContent?.toLowerCase().trim());
                const rows = table.querySelectorAll('tbody tr, .history-item');
                return {
                    hasTable: true,
                    hasQuery: headers.some(h => h.includes('query') || h.includes('question') || h.includes('topic')),
                    hasMode: headers.some(h => h.includes('mode') || h.includes('type')),
                    hasStatus: headers.some(h => h.includes('status')),
                    hasDate: headers.some(h => h.includes('date') || h.includes('time') || h.includes('created')),
                    hasDuration: headers.some(h => h.includes('duration') || h.includes('time')),
                    headerCount: headers.length,
                    rowCount: rows.length,
                    headers: headers.slice(0, 8)
                };
            }

            // Check for list/card-based layout (ldr-history-list with ldr-history-item).
            // The app renders each card as `.ldr-history-item` containing
            // `.ldr-history-item-title` (query/title), `.ldr-history-item-date`
            // and `.ldr-history-item-status` sub-elements (see history.js
            // createHistoryItemElement). Assert those exist on the first card so
            // the branch mirrors the table branch instead of being always-true.
            const items = document.querySelectorAll('.ldr-history-item');
            if (items.length > 0) {
                const first = items[0];
                return {
                    hasTable: false,
                    hasCards: true,
                    cardCount: items.length,
                    cardHasTitle: !!first.querySelector('.ldr-history-item-title'),
                    cardHasDate: !!first.querySelector('.ldr-history-item-date'),
                    cardHasStatus: !!first.querySelector('.ldr-history-item-status'),
                    titleText: first.querySelector('.ldr-history-item-title')?.textContent?.trim() || ''
                };
            }

            return { hasTable: false, hasCards: false };
        });

        if (result.hasTable) {
            return {
                passed: result.hasQuery || result.hasDate,
                message: `History table: ${result.headerCount} columns (${result.headers.join(', ')}), ${result.rowCount} rows`
            };
        }

        if (result.hasCards) {
            // A real card must expose its query/title plus a date or status —
            // mirrors the table branch's hasQuery/hasDate requirement.
            const passed = result.cardHasTitle && (result.cardHasDate || result.cardHasStatus);
            return {
                passed,
                message: passed
                    ? `History card layout (${result.cardCount} items; first title="${result.titleText}", date=${result.cardHasDate}, status=${result.cardHasStatus})`
                    : `History card missing expected fields (title=${result.cardHasTitle}, date=${result.cardHasDate}, status=${result.cardHasStatus})`
            };
        }

        return { passed: null, skipped: true, message: 'No history table or cards found' };
    },

    async historyItemActions(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/history`);

        // Wait for JS to render history items from API
        await page.waitForFunction(
            () => {
                if (document.querySelectorAll('.ldr-history-item, .history-item, [data-research-id]').length > 0) {
                    return true;
                }
                // Require the empty-state to be VISIBLE, not just present: it is
                // server-rendered hidden and only shown once the client render
                // runs (see historyTableStructure). Avoids resolving before the
                // items have rendered.
                const empty = document.querySelector('.ldr-empty-state');
                return !!empty && getComputedStyle(empty).display !== 'none';
            },
            { timeout: 5000 }
        ).catch(() => {});

        const result = await page.evaluate(() => {
            const items = Array.from(document.querySelectorAll('.ldr-history-item'));
            if (items.length === 0) return { hasItems: false };

            // Page-specific action contract (history.js createHistoryItemElement):
            //   - every item renders a delete control (.ldr-delete-item-btn),
            //     unconditionally on the /history default load;
            //   - the View/Open-Chat control (.ldr-view-btn) renders ONLY on
            //     completed or chat items — both of which carry the
            //     `ldr-status-completed` status class (isCompleted <=> status
            //     'completed'; chat groups hard-code that class).
            // So assert: all items expose delete, AND every completed item
            // exposes a View control. This stays a real, page-specific assertion
            // without false-failing when the DB happens to hold only in-progress
            // researches (which legitimately render no View button).
            const allHaveDelete = items.every(it => it.querySelector('.ldr-delete-item-btn'));
            const completed = items.filter(it =>
                it.querySelector('.ldr-history-item-status.ldr-status-completed'));
            const completedHaveView = completed.every(it => it.querySelector('.ldr-view-btn'));

            return {
                hasItems: true,
                itemCount: items.length,
                completedCount: completed.length,
                allHaveDelete,
                completedHaveView
            };
        });

        if (!result.hasItems) {
            return { passed: null, skipped: true, message: 'No history items to test actions' };
        }

        const passed = result.allHaveDelete && result.completedHaveView;
        return {
            passed,
            message: passed
                ? `History item actions present: ${result.itemCount} items all expose delete; ${result.completedCount} completed item(s) all expose View`
                : `History item actions missing: allHaveDelete=${result.allHaveDelete}, completedHaveView=${result.completedHaveView} (items=${result.itemCount}, completed=${result.completedCount})`
        };
    },

    async clearHistoryButton(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/history`);

        const result = await page.evaluate(() => {
            const buttons = Array.from(document.querySelectorAll('button, a.btn'));
            const clearBtn = buttons.find(b =>
                b.textContent?.toLowerCase().includes('clear') ||
                b.textContent?.toLowerCase().includes('delete all') ||
                b.textContent?.toLowerCase().includes('remove all')
            );

            return {
                hasClearButton: !!clearBtn,
                buttonText: clearBtn?.textContent?.trim()
            };
        });

        if (!result.hasClearButton) {
            return { passed: null, skipped: true, message: 'No clear all history button found' };
        }

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

    async historySearchFilter(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/history`);

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

            const filterSelect = document.querySelector(
                'select[name*="filter"], ' +
                'select[name*="status"], ' +
                '.filter-dropdown'
            );

            return {
                hasSearch: !!searchInput,
                searchPlaceholder: searchInput?.placeholder,
                hasFilter: !!filterSelect,
                filterOptions: filterSelect ? Array.from(filterSelect.options).map(o => o.text).slice(0, 5) : []
            };
        });

        const hasAny = result.hasSearch || result.hasFilter;
        if (!hasAny) {
            return { passed: null, skipped: true, message: 'No search/filter functionality on history page' };
        }

        return {
            passed: true,
            message: result.hasSearch
                ? `Search input found (placeholder: "${result.searchPlaceholder}")`
                : `Filter dropdown found (options: ${result.filterOptions.join(', ')})`
        };
    },

    async historyPagination(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/history`);

        const result = await page.evaluate(() => {
            const pagination = document.querySelector('.pagination, .pager, nav[aria-label*="pagination"]');
            const pageLinks = document.querySelectorAll('.page-link, .pagination a, .pagination button');
            const loadMoreBtn = document.querySelector('.load-more, button[onclick*="loadMore"]');

            return {
                hasPagination: !!pagination || pageLinks.length > 0,
                pageCount: pageLinks.length,
                hasLoadMore: !!loadMoreBtn,
                hasNextPrev: !!document.querySelector('[aria-label*="next"], [aria-label*="previous"], .next, .prev')
            };
        });

        if (!result.hasPagination && !result.hasLoadMore) {
            return { passed: null, skipped: true, message: 'No pagination controls (may have few items)' };
        }

        return {
            passed: true,
            message: result.hasPagination
                ? `Pagination found (${result.pageCount} page links, next/prev=${result.hasNextPrev})`
                : 'Load more button found'
        };
    }
};

// ============================================================================
// History API Tests
// ============================================================================
const HistoryApiTests = {
    async historyApiResponds(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/history`);

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

                const data = await response.json();
                return {
                    ok: true,
                    status: response.status,
                    itemCount: Array.isArray(data) ? data.length : (data.items?.length || data.history?.length || 0)
                };
            } catch (e) {
                return { ok: false, error: e.message };
            }
        }, baseUrl);

        return {
            passed: result.ok,
            message: result.ok
                ? `History API responds (${result.itemCount} items)`
                : `History API failed: ${result.error || 'status ' + result.status}`
        };
    },

    async historyItemDetailsApi(page, baseUrl) {
        await navigateTo(page, `${baseUrl}/history`);

        // Get first research ID
        const researchId = await page.evaluate(() => {
            const item = document.querySelector('[data-research-id], [data-id], a[href*="/results/"]');
            if (!item) return null;

            return item.dataset?.researchId ||
                   item.dataset?.id ||
                   item.href?.match(/\/results\/(\d+)/)?.[1];
        });

        if (!researchId) {
            return { passed: null, skipped: true, message: 'No research ID found to test details API' };
        }

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

                const data = await response.json();
                return {
                    ok: true,
                    status: response.status,
                    hasQuery: !!data.query,
                    hasStatus: !!data.status
                };
            } catch (e) {
                return { ok: false, error: e.message };
            }
        }, baseUrl, researchId);

        return {
            passed: result.ok,
            message: result.ok
                ? `Research details API responds (query=${result.hasQuery}, status=${result.hasStatus})`
                : `Research details API failed: ${result.error || 'status ' + result.status}`
        };
    }
};

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

    const ctx = await setupTest({ authenticate: true });
    const results = new TestResults('History Page 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', 'History Page Loads', (p, u) => HistoryPageTests.historyPageLoads(p, u));
        await run('Structure', 'History Table Structure', (p, u) => HistoryPageTests.historyTableStructure(p, u));
        await run('Structure', 'History Item Actions', (p, u) => HistoryPageTests.historyItemActions(p, u));
        await run('Structure', 'Clear History Button', (p, u) => HistoryPageTests.clearHistoryButton(p, u));

        // Search/Filter Tests
        log.section('Search & Filter');
        await run('Filter', 'History Search/Filter', (p, u) => HistoryPageTests.historySearchFilter(p, u));
        await run('Filter', 'History Pagination', (p, u) => HistoryPageTests.historyPagination(p, u));

        // API Tests
        log.section('History APIs');
        await run('API', 'History API Responds', (p, u) => HistoryApiTests.historyApiResponds(p, u));
        await run('API', 'Research Details API', (p, u) => HistoryApiTests.historyItemDetailsApi(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 = { HistoryPageTests, HistoryApiTests };
