import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, screen, cleanup, waitFor, fireEvent } from '@testing-library/svelte';
import HealthChecks from './HealthChecks.svelte';
import * as adminApi from '$lib/api/admin';

afterEach(() => {
    cleanup();
    vi.restoreAllMocks();
});

function thresholds(): adminApi.HealthThresholds {
    return {
        disk_pct: 90,
        mem_pct: 90,
        dead_jobs_max: 100,
        crawl_fail_pct: 20,
        analysis_fail_pct: 20,
        cron_freshness_hours: 26,
        missing_covers_max: 500
    };
}

function report(over: Partial<adminApi.HealthReport> = {}): adminApi.HealthReport {
    return {
        status: 'warn',
        checks: [
            {
                id: 'dead_jobs',
                label: 'Dead jobs',
                status: 'warn',
                value: '137',
                threshold: '100',
                hint: 'jobs that exhausted their retries',
                action_href: '/admin/crawler'
            },
            {
                id: 'memory',
                label: 'Memory usage',
                status: 'ok',
                value: '48%',
                threshold: '90%',
                hint: null,
                action_href: null
            }
        ],
        thresholds: thresholds(),
        ...over
    };
}

describe('HealthChecks', () => {
    it('renders the overall status and each check, with a Fix link on failing ones', async () => {
        vi.spyOn(adminApi, 'getHealth').mockResolvedValue(report());
        render(HealthChecks);

        await waitFor(() => expect(screen.getByTestId('health-overall')).toBeTruthy());
        expect(screen.getByTestId('health-check-dead_jobs')).toBeTruthy();
        expect(screen.getByText('137')).toBeTruthy();
        // Failing check exposes a remediation link; the ok one does not.
        const fix = screen.getByRole('link', { name: /fix/i }) as HTMLAnchorElement;
        expect(fix.getAttribute('href')).toBe('/admin/crawler');
    });

    it('saves edited thresholds via updateHealthThresholds', async () => {
        vi.spyOn(adminApi, 'getHealth').mockResolvedValue(report());
        const update = vi
            .spyOn(adminApi, 'updateHealthThresholds')
            .mockResolvedValue(report({ status: 'ok' }));
        render(HealthChecks);

        await waitFor(() => expect(screen.getByTestId('health-thresholds-toggle')).toBeTruthy());
        await fireEvent.click(screen.getByTestId('health-thresholds-toggle'));

        const input = screen.getByTestId('threshold-dead_jobs_max') as HTMLInputElement;
        await fireEvent.input(input, { target: { value: '250' } });
        await fireEvent.click(screen.getByTestId('threshold-save'));

        await waitFor(() => expect(update).toHaveBeenCalledTimes(1));
        expect(update.mock.calls[0][0].dead_jobs_max).toBe(250);
    });
});
