/**
 * Tests for config/urls.js — URLBuilder behavior
 *
 * Focuses on the URLBuilder logic (placeholder substitution, ID extraction,
 * page-type detection). Does NOT test that URL constants equal specific
 * literals — those tests would just assert the source against itself.
 */

import '@js/config/urls.js';

const { URLS, URLBuilder } = window;

describe('URLBuilder', () => {
    describe('build', () => {
        it('replaces {id} placeholder', () => {
            expect(URLBuilder.build('/api/research/{id}', 42)).toBe('/api/research/42');
        });

        it('handles string IDs', () => {
            expect(URLBuilder.build('/api/research/{id}', 'abc-123')).toBe('/api/research/abc-123');
        });

        it('only replaces first {id} occurrence', () => {
            expect(URLBuilder.build('/api/{id}/{id}', 5)).toBe('/api/5/{id}');
        });

        it('returns template unchanged when no {id} placeholder', () => {
            expect(URLBuilder.build('/api/static', 5)).toBe('/api/static');
        });
    });

    describe('buildWithReplacements', () => {
        it('replaces multiple distinct placeholders', () => {
            const result = URLBuilder.buildWithReplacements(
                '/api/collection/{collectionId}/document/{documentId}',
                { collectionId: '10', documentId: '20' }
            );
            expect(result).toBe('/api/collection/10/document/20');
        });

        it('handles missing placeholder gracefully', () => {
            const result = URLBuilder.buildWithReplacements(
                '/api/{a}/{b}',
                { a: '1' }
            );
            expect(result).toBe('/api/1/{b}');
        });

        it('handles empty replacements object', () => {
            expect(URLBuilder.buildWithReplacements('/api/{id}', {})).toBe('/api/{id}');
        });
    });

    describe('convenience methods build URLs from URLS constants', () => {
        // These test that the convenience methods correctly compose URLBuilder.build
        // with the right URLS constant — not what those constants are.

        it('progressPage substitutes ID into PAGES.PROGRESS', () => {
            expect(URLBuilder.progressPage(42)).toBe(URLS.PAGES.PROGRESS.replace('{id}', '42'));
        });

        it('researchStatus substitutes ID into API.RESEARCH_STATUS', () => {
            expect(URLBuilder.researchStatus(42)).toBe(URLS.API.RESEARCH_STATUS.replace('{id}', '42'));
        });

        it('terminateResearch substitutes ID into API.TERMINATE_RESEARCH', () => {
            expect(URLBuilder.terminateResearch(42)).toBe(URLS.API.TERMINATE_RESEARCH.replace('{id}', '42'));
        });

        it('deleteResearch substitutes ID into API.DELETE_RESEARCH', () => {
            expect(URLBuilder.deleteResearch(42)).toBe(URLS.API.DELETE_RESEARCH.replace('{id}', '42'));
        });

        it('historyStatus substitutes ID into HISTORY_API.STATUS', () => {
            expect(URLBuilder.historyStatus(42)).toBe(URLS.HISTORY_API.STATUS.replace('{id}', '42'));
        });

        it('researchLogs returns the bare URL when no limit passed', () => {
            const url = URLBuilder.researchLogs(42);
            expect(url).toBe(URLS.API.RESEARCH_LOGS.replace('{id}', '42'));
            expect(url).not.toContain('?');
        });

        it('researchLogs appends ?limit=N when limit passed', () => {
            const url = URLBuilder.researchLogs(42, 500);
            expect(url).toContain('limit=500');
        });

        it('researchLogs encodes the limit value', () => {
            // Sanity: limit should be safe to pass directly, but encodeURIComponent
            // means anything unexpected stays escaped.
            const url = URLBuilder.researchLogs('abc-123', 5000);
            expect(url).toBe(`${URLS.API.RESEARCH_LOGS.replace('{id}', 'abc-123')}?limit=5000`);
        });

        it('getSetting substitutes key into SETTINGS_API.GET_SETTING', () => {
            const url = URLBuilder.getSetting('llm.model');
            expect(url).toContain('llm.model');
            expect(url).toBe(URLS.SETTINGS_API.GET_SETTING.replace('{key}', 'llm.model'));
        });

        it('researchMetrics substitutes ID into METRICS_API.RESEARCH', () => {
            expect(URLBuilder.researchMetrics(42)).toBe(URLS.METRICS_API.RESEARCH.replace('{id}', '42'));
        });

        it('journalQualityPage scopes the dashboard to an encoded research ID', () => {
            expect(URLBuilder.journalQualityPage('abc 123')).toBe(
                `${URLS.PAGES.JOURNAL_QUALITY}?research_id=abc%20123`
            );
        });
    });

    describe('extractResearchIdFromPattern', () => {
        // We can't easily change window.location.pathname in happy-dom,
        // but we can verify the regex pattern construction works for the
        // current path (which is "/" by default → no match).
        it('returns null when pattern not in current path', () => {
            expect(URLBuilder.extractResearchIdFromPattern('results')).toBeNull();
        });
    });

    describe('extractResearchId', () => {
        it('returns null when no ID pattern matches current path', () => {
            // Default happy-dom path is "/"
            expect(URLBuilder.extractResearchId()).toBeNull();
        });
    });

    describe('getCurrentPageType', () => {
        it('returns a valid page type for the current path', () => {
            const validTypes = ['home', 'results', 'details', 'progress',
                               'history', 'settings', 'metrics', 'unknown'];
            expect(validTypes).toContain(URLBuilder.getCurrentPageType());
        });
    });
});

describe('URLS constants — structural sanity (not literal values)', () => {
    // Lightweight invariants that catch typos without asserting exact strings.

    it('all URL templates start with /', () => {
        const collectGroup = (group) => Object.values(group);
        const allUrls = [
            ...collectGroup(URLS.API),
            ...collectGroup(URLS.PAGES),
            ...collectGroup(URLS.HISTORY_API),
            ...collectGroup(URLS.SETTINGS_API),
            ...collectGroup(URLS.METRICS_API),
            ...collectGroup(URLS.LIBRARY_API),
        ];
        for (const url of allUrls) {
            expect(url).toMatch(/^\//);
        }
    });

    it('no URLs contain accidental double slashes', () => {
        const collectGroup = (group) => Object.values(group);
        const allUrls = [
            ...collectGroup(URLS.API),
            ...collectGroup(URLS.PAGES),
            ...collectGroup(URLS.HISTORY_API),
            ...collectGroup(URLS.SETTINGS_API),
        ];
        for (const url of allUrls) {
            expect(url).not.toMatch(/\/\//);
        }
    });

    it('settings API routes are namespaced under /settings/', () => {
        for (const [key, url] of Object.entries(URLS.SETTINGS_API)) {
            expect(url, `SETTINGS_API.${key}`).toMatch(/^\/settings\//);
        }
    });

    it('metrics API routes are namespaced under /metrics/', () => {
        for (const [key, url] of Object.entries(URLS.METRICS_API)) {
            expect(url, `METRICS_API.${key}`).toMatch(/^\/metrics\//);
        }
    });

    it('journal quality page is served from the metrics blueprint', () => {
        expect(URLS.PAGES.JOURNAL_QUALITY).toMatch(/^\/metrics\//);
    });

    it('library API routes are namespaced under /library/', () => {
        for (const [key, url] of Object.entries(URLS.LIBRARY_API)) {
            expect(url, `LIBRARY_API.${key}`).toMatch(/^\/library\//);
        }
    });
});
