import { mount, type VueWrapper } from '@vue/test-utils';
import {
    defineComponent,
    h,
    nextTick,
} from 'vue';
import {
    afterEach,
    beforeEach,
    describe,
    expect,
    it,
    vi,
} from 'vitest';
import {
    KviewToastProvider,
    KviewUiProvider,
    useKviewToast,
} from '@kview/ui-core/vue';
import type {
    KviewToastApi,
    KviewUiMessageOverrides,
} from '@kview/ui-core/vue';

let toastApi: KviewToastApi | null = null;
const wrappers: VueWrapper[] = [];

const ToastProbe = defineComponent({
    name: 'ToastProbe',
    setup() {
        toastApi = useKviewToast();

        return () => h('button', { id: 'toast-focus-probe', type: 'button' }, 'Focus probe');
    },
});

const api = (): KviewToastApi => {
    if (!toastApi) {
        throw new Error('The toast test provider has not mounted.');
    }

    return toastApi;
};

const mountProvider = (messages: KviewUiMessageOverrides = {}): VueWrapper => {
    const wrapper = mount(KviewUiProvider, {
        attachTo: document.body,
        props: { messages },
        slots: {
            default: () => h(KviewToastProvider, null, {
                default: () => h(ToastProbe),
            }),
        },
    });

    wrappers.push(wrapper);

    return wrapper;
};

beforeEach(() => {
    document.body.innerHTML = '';
    toastApi = null;
    vi.useFakeTimers();
});

afterEach(() => {
    wrappers.splice(0).forEach((wrapper) => wrapper.unmount());
    vi.clearAllTimers();
    vi.useRealTimers();
    document.body.innerHTML = '';
});

describe('KviewToastProvider', () => {
    it('teleports localized status and alert feedback without stealing focus', async () => {
        const wrapper = mountProvider({
            toast: {
                success: 'Réussite',
                dismiss: 'Fermer la notification',
            },
        });
        const focusProbe = wrapper.get<HTMLButtonElement>('#toast-focus-probe');

        focusProbe.element.focus();
        const id = api().success({ message: 'Le fund a été enregistré.', duration: 0 });
        await nextTick();

        const viewport = document.body.querySelector<HTMLElement>('.ui-toast-viewport');
        const status = document.body.querySelector<HTMLElement>('.ui-toast');

        expect(viewport?.parentElement).toBe(document.body);
        expect(viewport?.getAttribute('aria-live')).toBe('polite');
        expect(viewport?.getAttribute('aria-atomic')).toBe('false');
        expect(status?.getAttribute('role')).toBe('status');
        expect(status?.textContent).toContain('Réussite');
        expect(status?.textContent).toContain('Le fund a été enregistré.');
        expect(document.activeElement).toBe(focusProbe.element);
        expect(status?.querySelector('button')?.getAttribute('aria-label')).toBe('Fermer la notification');

        expect(api().update(id, {
            type: 'error',
            message: 'La sauvegarde a échoué.',
            duration: 0,
        })).toBe(id);
        await nextTick();

        const alert = document.body.querySelector<HTMLElement>('.ui-toast');
        expect(alert?.getAttribute('role')).toBe('alert');
        expect(alert?.classList.contains('ui-toast--error')).toBe(true);
        expect(alert?.textContent).toContain('Error');
        expect(alert?.textContent).toContain('La sauvegarde a échoué.');

        document.body.querySelector<HTMLButtonElement>(
            'button[aria-label="Fermer la notification"]',
        )?.click();
        await nextTick();
        expect(document.body.querySelector('.ui-toast')?.classList.contains('is-exiting')).toBe(true);

        await vi.advanceTimersByTimeAsync(180);
        await nextTick();
        expect(document.body.querySelector('.ui-toast')).toBeNull();
    });

    it('updates stable ids, restarts dismissal timers and cleans them on unmount', async () => {
        const wrapper = mountProvider();
        const id = api().show({
            id: 'stable-toast',
            message: 'Initial message',
            duration: 1000,
        });

        expect(id).toBe('stable-toast');
        await nextTick();
        await vi.advanceTimersByTimeAsync(900);

        expect(api().show({
            id: 'stable-toast',
            type: 'warning',
            message: 'Updated message',
            duration: 1000,
        })).toBe('stable-toast');
        await nextTick();

        expect(document.body.querySelectorAll('.ui-toast')).toHaveLength(1);
        expect(document.body.querySelector('.ui-toast')?.textContent).toContain('Updated message');

        await vi.advanceTimersByTimeAsync(999);
        expect(document.body.querySelector('.ui-toast')?.classList.contains('is-exiting')).toBe(false);

        await vi.advanceTimersByTimeAsync(1);
        await nextTick();
        expect(document.body.querySelector('.ui-toast')?.classList.contains('is-exiting')).toBe(true);

        wrapper.unmount();
        await vi.runAllTimersAsync();
        expect(document.body.querySelector('.ui-toast-viewport')).toBeNull();
    });

    it('tracks promise loading, success and failure through one toast id', async () => {
        mountProvider();
        let resolvePending!: (value: number) => void;
        const pending = new Promise<number>((resolve) => {
            resolvePending = resolve;
        });
        const tracked = api().promise(pending, {
            success: (value) => `Saved ${value} records.`,
        });

        await nextTick();
        expect(document.body.querySelectorAll('.ui-toast')).toHaveLength(1);
        expect(document.body.querySelector('.ui-toast')?.textContent).toContain('Working');
        expect(document.body.querySelector('.ui-toast')?.textContent).toContain('Please wait...');
        expect(document.body.querySelector('.ui-toast-progress')).toBeNull();

        resolvePending(12);
        await expect(tracked).resolves.toBe(12);
        await nextTick();

        const success = document.body.querySelector<HTMLElement>('.ui-toast');
        expect(success?.getAttribute('role')).toBe('status');
        expect(success?.classList.contains('ui-toast--success')).toBe(true);
        expect(success?.textContent).toContain('Complete');
        expect(success?.textContent).toContain('Saved 12 records.');
        expect(document.body.querySelectorAll('.ui-toast')).toHaveLength(1);

        const failed = api().promise(Promise.reject(new Error('Server unavailable.')));

        await expect(failed).rejects.toThrow('Server unavailable.');
        await nextTick();

        const alerts = Array.from(document.body.querySelectorAll<HTMLElement>('.ui-toast[role="alert"]'));
        expect(alerts).toHaveLength(1);
        expect(alerts[0]?.textContent).toContain('Something went wrong');
        expect(alerts[0]?.textContent).toContain('Server unavailable.');
    });

    it('fails explicitly when a consumer is mounted outside the provider', () => {
        expect(() => mount(ToastProbe)).toThrow(
            'useKviewToast() requires an ancestor KviewToastProvider.',
        );
    });
});
