#!/usr/bin/env node
/**
 * Metrics Dashboard UI Tests
 *
 * Tests for the metrics dashboard, cost analytics, star reviews, and link analytics pages.
 *
 * Run: node test_metrics_dashboard_ci.js
 */

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

// ============================================================================
// Metrics Dashboard Tests
// ============================================================================
const MetricsDashboardTests = {
    async metricsDashboardLoads(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            return {
                hasContent: !!document.querySelector('.metrics-container, .ldr-metrics, #metrics, .dashboard'),
                hasHeader: !!document.querySelector('h1, .metrics-header, .page-title'),
                headerText: document.querySelector('h1, .metrics-header, .page-title')?.textContent?.trim(),
                hasCards: document.querySelectorAll('.metric-card, .stats-card, .card, .ldr-card').length
            };
        });

        const passed = result.hasContent || result.hasHeader || result.hasCards > 0;
        return {
            passed,
            message: passed
                ? `Metrics dashboard loaded (header: "${result.headerText}", cards: ${result.hasCards})`
                : 'Metrics dashboard failed to load'
        };
    },

    async metricsOverviewCards(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            const cards = document.querySelectorAll('.metric-card, .stats-card, .card, .ldr-card');
            const cardTexts = Array.from(cards).map(c => c.textContent?.toLowerCase() || '');

            return {
                cardCount: cards.length,
                hasTokenCard: cardTexts.some(t => t.includes('token') || t.includes('usage')),
                hasSearchCard: cardTexts.some(t => t.includes('search') || t.includes('queries')),
                hasSatisfactionCard: cardTexts.some(t => t.includes('satisfaction') || t.includes('rating')),
                hasCostCard: cardTexts.some(t => t.includes('cost') || t.includes('spend'))
            };
        });

        if (result.cardCount === 0) {
            return { passed: null, skipped: true, message: 'No metric cards found on dashboard' };
        }

        return {
            passed: result.hasTokenCard || result.hasSearchCard || result.hasSatisfactionCard,
            message: `Found ${result.cardCount} cards (tokens=${result.hasTokenCard}, search=${result.hasSearchCard}, satisfaction=${result.hasSatisfactionCard})`
        };
    },

    async periodFilterDropdown(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            const periodFilter = document.querySelector(
                'select[name*="period"], ' +
                '#period-filter, ' +
                '.period-filter, ' +
                'select[id*="period"]'
            );

            if (!periodFilter) {
                // Check for button-based filter
                const buttons = document.querySelectorAll('.period-btn, .time-filter button, [data-period]');
                if (buttons.length > 0) {
                    return {
                        exists: true,
                        isButtonBased: true,
                        buttonCount: buttons.length,
                        options: Array.from(buttons).map(b => b.textContent?.trim()).slice(0, 6)
                    };
                }
                return { exists: false };
            }

            const options = Array.from(periodFilter.options);
            return {
                exists: true,
                isButtonBased: false,
                optionCount: options.length,
                options: options.map(o => o.text).slice(0, 6)
            };
        });

        if (!result.exists) {
            return { passed: null, skipped: true, message: 'No period filter found on metrics page' };
        }

        return {
            passed: true,
            message: result.isButtonBased
                ? `Period filter (buttons): ${result.options.join(', ')}`
                : `Period filter dropdown: ${result.options.join(', ')}`
        };
    },

    async modeFilterDropdown(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            const modeFilter = document.querySelector(
                'select[name*="mode"], ' +
                '#mode-filter, ' +
                '.mode-filter'
            );

            if (!modeFilter) {
                const buttons = document.querySelectorAll('.mode-btn, [data-mode]');
                if (buttons.length > 0) {
                    return {
                        exists: true,
                        isButtonBased: true,
                        options: Array.from(buttons).map(b => b.textContent?.trim())
                    };
                }
                return { exists: false };
            }

            return {
                exists: true,
                options: Array.from(modeFilter.options).map(o => o.text)
            };
        });

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

        return {
            passed: true,
            message: `Mode filter: ${result.options.join(', ')}`
        };
    },

    async tokenUsageChart(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/`, { waitUntil: 'networkidle2' });
        await delay(1000); // Wait for charts to render

        const result = await page.evaluate(() => {
            const charts = document.querySelectorAll('canvas, svg, .chart, .recharts-wrapper, [class*="chart"]');
            const tokenChart = document.querySelector('[class*="token"], [id*="token"], [data-chart*="token"]');

            return {
                hasCharts: charts.length > 0,
                chartCount: charts.length,
                hasTokenChart: !!tokenChart,
                chartTypes: Array.from(charts).map(c => c.tagName.toLowerCase()).slice(0, 5)
            };
        });

        if (!result.hasCharts) {
            return { passed: null, skipped: true, message: 'No charts found on metrics page' };
        }

        return {
            passed: true,
            message: `Found ${result.chartCount} charts (types: ${result.chartTypes.join(', ')})`
        };
    },

    async searchMetricsChart(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/`, { waitUntil: 'networkidle2' });
        await delay(1000);

        const result = await page.evaluate(() => {
            const searchSection = document.querySelector(
                '[class*="search"], ' +
                '[id*="search-metrics"], ' +
                '.search-stats'
            );

            const hasSearchData = document.body.textContent?.toLowerCase().includes('search') &&
                                  (document.body.textContent?.toLowerCase().includes('queries') ||
                                   document.body.textContent?.toLowerCase().includes('engine'));

            return {
                hasSearchSection: !!searchSection,
                hasSearchData
            };
        });

        if (!result.hasSearchSection && !result.hasSearchData) {
            return { passed: null, skipped: true, message: 'No search metrics section found' };
        }

        return {
            passed: true,
            message: 'Search metrics section found'
        };
    },

    async rateLimitingSection(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            const rateSection = document.querySelector(
                '[class*="rate-limit"], ' +
                '[id*="rate-limit"], ' +
                '.rate-limiting'
            );

            const hasRateData = document.body.textContent?.toLowerCase().includes('rate limit');

            return {
                hasRateSection: !!rateSection,
                hasRateData
            };
        });

        if (!result.hasRateSection && !result.hasRateData) {
            return { passed: null, skipped: true, message: 'No rate limiting section found' };
        }

        return {
            passed: true,
            message: 'Rate limiting section found'
        };
    },

    async userSatisfactionSection(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            const satisfactionSection = document.querySelector(
                '[class*="satisfaction"], ' +
                '[class*="rating"], ' +
                '[id*="satisfaction"]'
            );

            const hasSatisfactionData = document.body.textContent?.toLowerCase().includes('satisfaction') ||
                                        document.body.textContent?.toLowerCase().includes('rating');

            return {
                hasSatisfactionSection: !!satisfactionSection,
                hasSatisfactionData
            };
        });

        if (!result.hasSatisfactionSection && !result.hasSatisfactionData) {
            return { passed: null, skipped: true, message: 'No satisfaction metrics found' };
        }

        return {
            passed: true,
            message: 'User satisfaction metrics found'
        };
    }
};

// ============================================================================
// Cost Analytics Tests
// ============================================================================
const CostAnalyticsTests = {
    async costAnalyticsPageLoads(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/costs`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            return {
                hasContent: !!document.querySelector('.cost-analytics, .costs-container, #costs, .analytics'),
                hasHeader: !!document.querySelector('h1, .page-title'),
                headerText: document.querySelector('h1, .page-title')?.textContent?.trim(),
                hasCostData: document.body.textContent?.toLowerCase().includes('cost') ||
                            document.body.textContent?.toLowerCase().includes('$')
            };
        });

        const passed = result.hasContent || result.hasHeader || result.hasCostData;
        return {
            passed,
            message: passed
                ? `Cost analytics page loaded (header: "${result.headerText}")`
                : 'Cost analytics page failed to load'
        };
    },

    async costBreakdownChart(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/costs`, { waitUntil: 'networkidle2' });
        await delay(1000);

        const result = await page.evaluate(() => {
            const charts = document.querySelectorAll('canvas, svg, .chart, .recharts-wrapper');
            const breakdownSection = document.querySelector('[class*="breakdown"], [id*="breakdown"]');

            return {
                hasCharts: charts.length > 0,
                chartCount: charts.length,
                hasBreakdown: !!breakdownSection
            };
        });

        if (!result.hasCharts && !result.hasBreakdown) {
            return { passed: null, skipped: true, message: 'No cost breakdown visualization found' };
        }

        return {
            passed: true,
            message: `Cost breakdown found (charts: ${result.chartCount})`
        };
    },

    async pricingTableDisplays(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/costs`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            const table = document.querySelector('table, .pricing-table, .ldr-table');
            const pricingSection = document.querySelector('[class*="pricing"], [id*="pricing"]');

            if (table) {
                const headers = Array.from(table.querySelectorAll('th')).map(th => th.textContent?.toLowerCase().trim());
                const hasPriceColumn = headers.some(h => h.includes('price') || h.includes('cost') || h.includes('$'));
                const hasModelColumn = headers.some(h => h.includes('model') || h.includes('provider'));

                return {
                    hasTable: true,
                    headerCount: headers.length,
                    hasPriceColumn,
                    hasModelColumn
                };
            }

            return { hasTable: false, hasPricingSection: !!pricingSection };
        });

        if (!result.hasTable && !result.hasPricingSection) {
            return { passed: null, skipped: true, message: 'No pricing table found' };
        }

        return {
            passed: result.hasTable,
            message: result.hasTable
                ? `Pricing table found (price column=${result.hasPriceColumn}, model column=${result.hasModelColumn})`
                : 'Pricing section found'
        };
    },

    async researchCostsTable(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/costs`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            const tables = document.querySelectorAll('table');
            const researchTable = Array.from(tables).find(t => {
                const text = t.textContent?.toLowerCase() || '';
                return text.includes('research') || text.includes('query');
            });

            if (researchTable) {
                const rows = researchTable.querySelectorAll('tbody tr');
                return {
                    hasTable: true,
                    rowCount: rows.length
                };
            }

            return { hasTable: false };
        });

        if (!result.hasTable) {
            return { passed: null, skipped: true, message: 'No research costs table found' };
        }

        return {
            passed: true,
            message: `Research costs table found (${result.rowCount} rows)`
        };
    }
};

// ============================================================================
// Star Reviews Tests
// ============================================================================
const StarReviewsTests = {
    async starReviewsPageLoads(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/star-reviews`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            return {
                hasContent: !!document.querySelector('.star-reviews, .reviews-container, #reviews'),
                hasHeader: !!document.querySelector('h1, .page-title'),
                headerText: document.querySelector('h1, .page-title')?.textContent?.trim(),
                hasStars: document.querySelectorAll('.star, [class*="star"], .fa-star, svg[class*="star"]').length
            };
        });

        const passed = result.hasContent || result.hasHeader || result.hasStars > 0;
        return {
            passed,
            message: passed
                ? `Star reviews page loaded (header: "${result.headerText}", stars: ${result.hasStars})`
                : 'Star reviews page failed to load'
        };
    },

    async ratingsDistributionChart(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/star-reviews`, { waitUntil: 'networkidle2' });
        await delay(1000);

        const result = await page.evaluate(() => {
            const charts = document.querySelectorAll('canvas, svg, .chart, .recharts-wrapper');
            const distribution = document.querySelector('[class*="distribution"], [id*="distribution"]');

            // Check for bar chart or histogram
            const bars = document.querySelectorAll('.bar, rect, [class*="bar"]');

            return {
                hasCharts: charts.length > 0,
                hasDistribution: !!distribution,
                hasBars: bars.length > 0,
                chartCount: charts.length
            };
        });

        if (!result.hasCharts && !result.hasDistribution && !result.hasBars) {
            return { passed: null, skipped: true, message: 'No ratings distribution chart found' };
        }

        return {
            passed: true,
            message: `Ratings distribution found (charts: ${result.chartCount})`
        };
    },

    async averageRatingDisplay(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/star-reviews`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            const avgElement = document.querySelector(
                '[class*="average"], ' +
                '[id*="average"], ' +
                '.overall-rating, ' +
                '.avg-rating'
            );

            // Look for a number that could be an average (e.g., 4.2, 3.5)
            const avgPattern = /(\d+\.?\d*)\s*(\/\s*5|stars?|out of)/i;
            const bodyText = document.body.textContent || '';
            const avgMatch = bodyText.match(avgPattern);

            return {
                hasAvgElement: !!avgElement,
                avgText: avgElement?.textContent?.trim(),
                foundAvgInText: !!avgMatch,
                avgValue: avgMatch ? avgMatch[1] : null
            };
        });

        if (!result.hasAvgElement && !result.foundAvgInText) {
            return { passed: null, skipped: true, message: 'No average rating display found' };
        }

        return {
            passed: true,
            message: result.hasAvgElement
                ? `Average rating displayed: "${result.avgText}"`
                : `Average rating found in text: ${result.avgValue}`
        };
    }
};

// ============================================================================
// Link Analytics Tests
// ============================================================================
const LinkAnalyticsTests = {
    async linkAnalyticsPageLoads(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/links`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            return {
                hasContent: !!document.querySelector('.link-analytics, .links-container, #links'),
                hasHeader: !!document.querySelector('h1, .page-title'),
                headerText: document.querySelector('h1, .page-title')?.textContent?.trim(),
                hasTable: !!document.querySelector('table')
            };
        });

        const passed = result.hasContent || result.hasHeader || result.hasTable;
        return {
            passed,
            message: passed
                ? `Link analytics page loaded (header: "${result.headerText}")`
                : 'Link analytics page failed to load'
        };
    },

    async topDomainsTable(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/links`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            const table = document.querySelector('table');
            if (!table) return { hasTable: false };

            const headers = Array.from(table.querySelectorAll('th')).map(th => th.textContent?.toLowerCase().trim());
            const rows = table.querySelectorAll('tbody tr');

            return {
                hasTable: true,
                hasDomainColumn: headers.some(h => h.includes('domain') || h.includes('site') || h.includes('url')),
                hasCountColumn: headers.some(h => h.includes('count') || h.includes('visits') || h.includes('links')),
                rowCount: rows.length
            };
        });

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

        return {
            passed: result.hasDomainColumn,
            message: `Domains table found (${result.rowCount} rows, domain=${result.hasDomainColumn}, count=${result.hasCountColumn})`
        };
    },

    async domainClassificationSection(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/links`, { waitUntil: 'networkidle2' });

        const result = await page.evaluate(() => {
            const classSection = document.querySelector(
                '[class*="classification"], ' +
                '[id*="classification"], ' +
                '.domain-types'
            );

            const hasCategories = document.body.textContent?.toLowerCase().includes('academic') ||
                                  document.body.textContent?.toLowerCase().includes('news') ||
                                  document.body.textContent?.toLowerCase().includes('government');

            return {
                hasClassSection: !!classSection,
                hasCategories
            };
        });

        if (!result.hasClassSection && !result.hasCategories) {
            return { passed: null, skipped: true, message: 'No domain classification section found' };
        }

        return {
            passed: true,
            message: 'Domain classification section found'
        };
    }
};

// ============================================================================
// Metrics API Tests
// ============================================================================
const MetricsApiTests = {
    async metricsApiResponds(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/`, { waitUntil: 'networkidle2' });

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

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

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

    async pricingApiResponds(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/costs`, { waitUntil: 'networkidle2' });

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

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

        if (!result.ok && result.status === 404) {
            return { passed: null, skipped: true, message: 'Pricing API endpoint not found' };
        }

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

    async starReviewsApiResponds(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/star-reviews`, { waitUntil: 'networkidle2' });

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

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

        if (!result.ok && result.status === 404) {
            return { passed: null, skipped: true, message: 'Star reviews API endpoint not found' };
        }

        return {
            passed: result.ok,
            message: result.ok
                ? `Star reviews API responds (hasData=${result.hasData})`
                : `Star reviews API failed: ${result.error || 'status ' + result.status}`
        };
    },

    async linkAnalyticsApiResponds(page, baseUrl) {
        await page.goto(`${baseUrl}/metrics/links`, { waitUntil: 'networkidle2' });

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

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

        if (!result.ok && result.status === 404) {
            return { passed: null, skipped: true, message: 'Link analytics API endpoint not found' };
        }

        return {
            passed: result.ok,
            message: result.ok
                ? `Link analytics API responds (hasData=${result.hasData})`
                : `Link analytics API failed: ${result.error || 'status ' + result.status}`
        };
    }
};

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

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

    try {
        // Metrics Dashboard Tests
        log.section('Metrics Dashboard');
        await results.run('Dashboard', 'Metrics Dashboard Loads', () => MetricsDashboardTests.metricsDashboardLoads(page, baseUrl), { page });

        const cardsResult = await MetricsDashboardTests.metricsOverviewCards(page, baseUrl);
        if (cardsResult.skipped) {
            results.skip('Dashboard', 'Metrics Overview Cards', cardsResult.message);
        } else {
            results.add('Dashboard', 'Metrics Overview Cards', cardsResult.passed, cardsResult.message);
        }

        const periodResult = await MetricsDashboardTests.periodFilterDropdown(page, baseUrl);
        if (periodResult.skipped) {
            results.skip('Dashboard', 'Period Filter Dropdown', periodResult.message);
        } else {
            results.add('Dashboard', 'Period Filter Dropdown', periodResult.passed, periodResult.message);
        }

        const modeResult = await MetricsDashboardTests.modeFilterDropdown(page, baseUrl);
        if (modeResult.skipped) {
            results.skip('Dashboard', 'Mode Filter Dropdown', modeResult.message);
        } else {
            results.add('Dashboard', 'Mode Filter Dropdown', modeResult.passed, modeResult.message);
        }

        const tokenChartResult = await MetricsDashboardTests.tokenUsageChart(page, baseUrl);
        if (tokenChartResult.skipped) {
            results.skip('Dashboard', 'Token Usage Chart', tokenChartResult.message);
        } else {
            results.add('Dashboard', 'Token Usage Chart', tokenChartResult.passed, tokenChartResult.message);
        }

        const searchResult = await MetricsDashboardTests.searchMetricsChart(page, baseUrl);
        if (searchResult.skipped) {
            results.skip('Dashboard', 'Search Metrics Chart', searchResult.message);
        } else {
            results.add('Dashboard', 'Search Metrics Chart', searchResult.passed, searchResult.message);
        }

        const rateResult = await MetricsDashboardTests.rateLimitingSection(page, baseUrl);
        if (rateResult.skipped) {
            results.skip('Dashboard', 'Rate Limiting Section', rateResult.message);
        } else {
            results.add('Dashboard', 'Rate Limiting Section', rateResult.passed, rateResult.message);
        }

        const satisfactionResult = await MetricsDashboardTests.userSatisfactionSection(page, baseUrl);
        if (satisfactionResult.skipped) {
            results.skip('Dashboard', 'User Satisfaction Section', satisfactionResult.message);
        } else {
            results.add('Dashboard', 'User Satisfaction Section', satisfactionResult.passed, satisfactionResult.message);
        }

        // Cost Analytics Tests
        log.section('Cost Analytics');
        await results.run('Costs', 'Cost Analytics Page Loads', () => CostAnalyticsTests.costAnalyticsPageLoads(page, baseUrl), { page });

        const costChartResult = await CostAnalyticsTests.costBreakdownChart(page, baseUrl);
        if (costChartResult.skipped) {
            results.skip('Costs', 'Cost Breakdown Chart', costChartResult.message);
        } else {
            results.add('Costs', 'Cost Breakdown Chart', costChartResult.passed, costChartResult.message);
        }

        const pricingResult = await CostAnalyticsTests.pricingTableDisplays(page, baseUrl);
        if (pricingResult.skipped) {
            results.skip('Costs', 'Pricing Table Displays', pricingResult.message);
        } else {
            results.add('Costs', 'Pricing Table Displays', pricingResult.passed, pricingResult.message);
        }

        const researchCostsResult = await CostAnalyticsTests.researchCostsTable(page, baseUrl);
        if (researchCostsResult.skipped) {
            results.skip('Costs', 'Research Costs Table', researchCostsResult.message);
        } else {
            results.add('Costs', 'Research Costs Table', researchCostsResult.passed, researchCostsResult.message);
        }

        // Star Reviews Tests
        log.section('Star Reviews');
        await results.run('Reviews', 'Star Reviews Page Loads', () => StarReviewsTests.starReviewsPageLoads(page, baseUrl), { page });

        const distResult = await StarReviewsTests.ratingsDistributionChart(page, baseUrl);
        if (distResult.skipped) {
            results.skip('Reviews', 'Ratings Distribution Chart', distResult.message);
        } else {
            results.add('Reviews', 'Ratings Distribution Chart', distResult.passed, distResult.message);
        }

        const avgResult = await StarReviewsTests.averageRatingDisplay(page, baseUrl);
        if (avgResult.skipped) {
            results.skip('Reviews', 'Average Rating Display', avgResult.message);
        } else {
            results.add('Reviews', 'Average Rating Display', avgResult.passed, avgResult.message);
        }

        // Link Analytics Tests
        log.section('Link Analytics');
        await results.run('Links', 'Link Analytics Page Loads', () => LinkAnalyticsTests.linkAnalyticsPageLoads(page, baseUrl), { page });

        const domainsResult = await LinkAnalyticsTests.topDomainsTable(page, baseUrl);
        if (domainsResult.skipped) {
            results.skip('Links', 'Top Domains Table', domainsResult.message);
        } else {
            results.add('Links', 'Top Domains Table', domainsResult.passed, domainsResult.message);
        }

        const classResult = await LinkAnalyticsTests.domainClassificationSection(page, baseUrl);
        if (classResult.skipped) {
            results.skip('Links', 'Domain Classification Section', classResult.message);
        } else {
            results.add('Links', 'Domain Classification Section', classResult.passed, classResult.message);
        }

        // Metrics API Tests
        log.section('Metrics APIs');
        await results.run('API', 'Metrics API Responds', () => MetricsApiTests.metricsApiResponds(page, baseUrl), { page });

        const pricingApiResult = await MetricsApiTests.pricingApiResponds(page, baseUrl);
        if (pricingApiResult.skipped) {
            results.skip('API', 'Pricing API Responds', pricingApiResult.message);
        } else {
            results.add('API', 'Pricing API Responds', pricingApiResult.passed, pricingApiResult.message);
        }

        const starApiResult = await MetricsApiTests.starReviewsApiResponds(page, baseUrl);
        if (starApiResult.skipped) {
            results.skip('API', 'Star Reviews API Responds', starApiResult.message);
        } else {
            results.add('API', 'Star Reviews API Responds', starApiResult.passed, starApiResult.message);
        }

        const linkApiResult = await MetricsApiTests.linkAnalyticsApiResponds(page, baseUrl);
        if (linkApiResult.skipped) {
            results.skip('API', 'Link Analytics API Responds', linkApiResult.message);
        } else {
            results.add('API', 'Link Analytics API Responds', linkApiResult.passed, linkApiResult.message);
        }

    } catch (error) {
        // Check if this is a navigation/browser error (not a real test failure)
        const isNavigationError = error.message &&
            (error.message.includes('context was destroyed') ||
             error.message.includes('Target closed') ||
             error.message.includes('Protocol error') ||
             error.message.includes('Session closed') ||
             error.message.includes('page has been closed'));

        if (isNavigationError) {
            log.warn(`Browser navigation error (test infrastructure issue): ${error.message}`);
            // Don't count as failure - this is a flaky test issue
        } else {
            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 = { MetricsDashboardTests, CostAnalyticsTests, StarReviewsTests, LinkAnalyticsTests, MetricsApiTests };
