/**
 * Tests for components/news.js
 *
 * Tests the news utility functions: formatTimeAgo, debounce, createTag,
 * showEmptyState, showLoadingState, showErrorState, showModal/hideModal.
 */

import '@js/components/news.js';

describe('NewsUtils', () => {
    describe('formatTimeAgo', () => {
        const now = new Date('2025-06-15T12:00:00Z');

        beforeEach(() => {
            vi.useFakeTimers();
            vi.setSystemTime(now);
        });

        afterEach(() => {
            vi.useRealTimers();
        });

        it('returns "just now" for < 60 seconds', () => {
            const time = new Date(now.getTime() - 30 * 1000);
            expect(window.formatTimeAgo(time)).toBe('just now');
        });

        it('returns minutes ago for < 1 hour', () => {
            const time = new Date(now.getTime() - 5 * 60 * 1000);
            expect(window.formatTimeAgo(time)).toBe('5 minutes ago');
        });

        it('returns hours ago for < 1 day', () => {
            const time = new Date(now.getTime() - 3 * 60 * 60 * 1000);
            expect(window.formatTimeAgo(time)).toBe('3 hours ago');
        });

        it('returns days ago for < 1 week', () => {
            const time = new Date(now.getTime() - 4 * 24 * 60 * 60 * 1000);
            expect(window.formatTimeAgo(time)).toBe('4 days ago');
        });

        it('returns date string for older timestamps', () => {
            const time = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
            const result = window.formatTimeAgo(time);
            // Should be a date string, not "X ago"
            expect(result).not.toContain('ago');
        });
    });

    describe('createTag', () => {
        it('creates a span element with the text', () => {
            const tag = window.createTag('javascript');
            expect(tag.tagName).toBe('SPAN');
            expect(tag.textContent).toBe('javascript');
        });

        it('uses default ldr-tag class', () => {
            const tag = window.createTag('test');
            expect(tag.className).toBe('ldr-tag');
        });

        it('accepts custom class name', () => {
            const tag = window.createTag('test', 'custom-class');
            expect(tag.className).toBe('custom-class');
        });

        it('uses textContent (XSS-safe)', () => {
            const tag = window.createTag('<script>alert(1)</script>');
            expect(tag.innerHTML).not.toContain('<script>');
            expect(tag.textContent).toBe('<script>alert(1)</script>');
        });
    });

    describe('debounce', () => {
        beforeEach(() => {
            vi.useFakeTimers();
        });

        afterEach(() => {
            vi.useRealTimers();
        });

        it('delays function execution', () => {
            const fn = vi.fn();
            const debounced = window.debounce(fn, 100);
            debounced();
            expect(fn).not.toHaveBeenCalled();
            vi.advanceTimersByTime(100);
            expect(fn).toHaveBeenCalledTimes(1);
        });

        it('cancels previous calls when called again', () => {
            const fn = vi.fn();
            const debounced = window.debounce(fn, 100);
            debounced();
            vi.advanceTimersByTime(50);
            debounced();
            vi.advanceTimersByTime(50);
            expect(fn).not.toHaveBeenCalled();
            vi.advanceTimersByTime(50);
            expect(fn).toHaveBeenCalledTimes(1);
        });

        it('passes arguments to debounced function', () => {
            const fn = vi.fn();
            const debounced = window.debounce(fn, 50);
            debounced('a', 'b', 'c');
            vi.advanceTimersByTime(50);
            expect(fn).toHaveBeenCalledWith('a', 'b', 'c');
        });
    });

    describe('showEmptyState', () => {
        it('renders empty state HTML in container', () => {
            const container = document.createElement('div');
            window.showEmptyState(container, 'No data');
            expect(container.querySelector('.ldr-empty-state')).not.toBeNull();
            expect(container.textContent).toContain('No data');
        });

        it('uses default newspaper icon', () => {
            const container = document.createElement('div');
            window.showEmptyState(container, 'Empty');
            expect(container.innerHTML).toContain('fa-newspaper');
        });

        it('accepts custom icon', () => {
            const container = document.createElement('div');
            window.showEmptyState(container, 'Empty', 'fas fa-search');
            expect(container.innerHTML).toContain('fa-search');
        });

        it('escapes HTML in message', () => {
            const container = document.createElement('div');
            window.showEmptyState(container, '<script>xss</script>');
            expect(container.innerHTML).not.toContain('<script>');
        });
    });

    describe('showLoadingState', () => {
        it('renders loading state HTML', () => {
            const container = document.createElement('div');
            window.showLoadingState(container);
            expect(container.querySelector('.ldr-loading-placeholder')).not.toBeNull();
            expect(container.textContent).toContain('Loading...');
        });

        it('accepts custom message', () => {
            const container = document.createElement('div');
            window.showLoadingState(container, 'Fetching data...');
            expect(container.textContent).toContain('Fetching data...');
        });

        it('escapes HTML in message', () => {
            const container = document.createElement('div');
            window.showLoadingState(container, '<img src=x onerror=alert(1)>');
            expect(container.innerHTML).not.toContain('<img');
        });
    });

    describe('showErrorState', () => {
        it('renders error state HTML', () => {
            const container = document.createElement('div');
            window.showErrorState(container, 'Connection failed');
            expect(container.querySelector('.ldr-error-state')).not.toBeNull();
            expect(container.textContent).toContain('Connection failed');
        });

        it('escapes HTML in message', () => {
            const container = document.createElement('div');
            window.showErrorState(container, '<script>xss</script>');
            expect(container.innerHTML).not.toContain('<script>');
        });
    });

    describe('showModal / hideModal', () => {
        let modal;

        beforeEach(() => {
            modal = document.createElement('div');
            modal.id = 'test-modal';
            modal.style.display = 'none';
            document.body.appendChild(modal);
        });

        afterEach(() => {
            modal.remove();
            document.body.style.overflow = '';
        });

        it('shows the modal by ID', () => {
            window.showModal('test-modal');
            expect(modal.style.display).toBe('flex');
        });

        it('locks body scroll when shown', () => {
            window.showModal('test-modal');
            expect(document.body.style.overflow).toBe('hidden');
        });

        it('hides the modal by ID', () => {
            window.showModal('test-modal');
            window.hideModal('test-modal');
            expect(modal.style.display).toBe('none');
        });

        it('restores body scroll when hidden', () => {
            window.showModal('test-modal');
            window.hideModal('test-modal');
            expect(document.body.style.overflow).toBe('auto');
        });

        it('does nothing for non-existent modal', () => {
            expect(() => window.showModal('nonexistent')).not.toThrow();
            expect(() => window.hideModal('nonexistent')).not.toThrow();
        });
    });
});
