import { describe, it, expect, beforeEach, vi } from 'vitest';
import { createPinia, setActivePinia } from 'pinia';

// Мокаем api/notifications до import'а store.
vi.mock('../../resources/js/api/notifications', () => ({
    listNotifications: vi.fn(),
    markNotificationRead: vi.fn(),
    markAllNotificationsRead: vi.fn(),
    deleteNotification: vi.fn(),
}));

vi.mock('../../resources/js/api/client', () => ({
    apiClient: {},
    ensureCsrfCookie: vi.fn(),
    extractValidationErrors: vi.fn(() => null),
    extractErrorMessage: vi.fn(() => 'Произошла ошибка.'),
    extractRateLimitRetry: vi.fn(() => null),
}));

import * as notificationsApi from '../../resources/js/api/notifications';
import { useNotificationsStore } from '../../resources/js/stores/notifications';
import type { ApiInAppNotification } from '../../resources/js/api/notifications';

const mockNotif = (id: number, overrides: Partial<ApiInAppNotification> = {}): ApiInAppNotification => ({
    id,
    event: 'new_lead',
    title: `Уведомление #${id}`,
    body: 'Тестовое тело',
    deal_id: 100 + id,
    payload: { deal_id: 100 + id, project_name: 'Caranga' },
    read_at: null,
    created_at: new Date(2026, 4, 9, 12, id).toISOString(),
    ...overrides,
});

describe('useNotificationsStore', () => {
    beforeEach(() => {
        setActivePinia(createPinia());
        vi.clearAllMocks();
    });

    it('initial state: items=[], unreadCount=0, total=0', () => {
        const store = useNotificationsStore();
        expect(store.items).toEqual([]);
        expect(store.unreadCount).toBe(0);
        expect(store.total).toBe(0);
        expect(store.fetchError).toBe(false);
    });

    it('load() заполняет items + unreadCount + total из API', async () => {
        vi.mocked(notificationsApi.listNotifications).mockResolvedValue({
            items: [mockNotif(1), mockNotif(2, { read_at: '2026-05-09T10:00:00Z' })],
            unread_count: 1,
            total: 2,
        });

        const store = useNotificationsStore();
        await store.load();

        expect(store.items).toHaveLength(2);
        expect(store.unreadCount).toBe(1);
        expect(store.total).toBe(2);
        expect(store.fetchError).toBe(false);
    });

    it('load() при reject ставит fetchError=true, items не меняются', async () => {
        vi.mocked(notificationsApi.listNotifications).mockRejectedValue(new Error('network'));

        const store = useNotificationsStore();
        await store.load();

        expect(store.fetchError).toBe(true);
        expect(store.items).toEqual([]);
    });

    it('markRead() optimistic ставит read_at + уменьшает unreadCount', async () => {
        vi.mocked(notificationsApi.listNotifications).mockResolvedValue({
            items: [mockNotif(1)],
            unread_count: 1,
            total: 1,
        });
        vi.mocked(notificationsApi.markNotificationRead).mockResolvedValue({
            id: 1,
            read_at: '2026-05-09T13:00:00Z',
        });

        const store = useNotificationsStore();
        await store.load();
        expect(store.unreadCount).toBe(1);

        await store.markRead(1);
        expect(store.unreadCount).toBe(0);
        expect(store.items[0]!.read_at).toBe('2026-05-09T13:00:00Z');
    });

    it('markRead() при reject — revert read_at + unreadCount', async () => {
        vi.mocked(notificationsApi.listNotifications).mockResolvedValue({
            items: [mockNotif(1)],
            unread_count: 1,
            total: 1,
        });
        vi.mocked(notificationsApi.markNotificationRead).mockRejectedValue(new Error('500'));

        const store = useNotificationsStore();
        await store.load();
        await store.markRead(1);

        expect(store.unreadCount).toBe(1);
        expect(store.items[0]!.read_at).toBeNull();
    });

    it('markRead() для уже прочитанного НЕ вызывает API', async () => {
        vi.mocked(notificationsApi.listNotifications).mockResolvedValue({
            items: [mockNotif(1, { read_at: '2026-05-09T10:00:00Z' })],
            unread_count: 0,
            total: 1,
        });

        const store = useNotificationsStore();
        await store.load();
        await store.markRead(1);

        expect(notificationsApi.markNotificationRead).not.toHaveBeenCalled();
    });

    it('markAllRead() optimistic + вызывает API', async () => {
        vi.mocked(notificationsApi.listNotifications).mockResolvedValue({
            items: [mockNotif(1), mockNotif(2)],
            unread_count: 2,
            total: 2,
        });
        vi.mocked(notificationsApi.markAllNotificationsRead).mockResolvedValue({ updated: 2 });

        const store = useNotificationsStore();
        await store.load();
        expect(store.unreadCount).toBe(2);

        await store.markAllRead();
        expect(store.unreadCount).toBe(0);
        expect(store.items.every((n) => n.read_at !== null)).toBe(true);
        expect(notificationsApi.markAllNotificationsRead).toHaveBeenCalledOnce();
    });

    it('markAllRead() при unreadCount=0 НЕ вызывает API', async () => {
        const store = useNotificationsStore();
        await store.markAllRead();

        expect(notificationsApi.markAllNotificationsRead).not.toHaveBeenCalled();
    });

    it('remove() optimistic убирает из items + decrement total', async () => {
        vi.mocked(notificationsApi.listNotifications).mockResolvedValue({
            items: [mockNotif(1), mockNotif(2)],
            unread_count: 2,
            total: 2,
        });
        vi.mocked(notificationsApi.deleteNotification).mockResolvedValue();

        const store = useNotificationsStore();
        await store.load();
        await store.remove(1);

        expect(store.items).toHaveLength(1);
        expect(store.items[0]!.id).toBe(2);
        expect(store.total).toBe(1);
        expect(store.unreadCount).toBe(1);
    });

    it('remove() при reject — revert items + total + unreadCount', async () => {
        vi.mocked(notificationsApi.listNotifications).mockResolvedValue({
            items: [mockNotif(1), mockNotif(2)],
            unread_count: 2,
            total: 2,
        });
        vi.mocked(notificationsApi.deleteNotification).mockRejectedValue(new Error('500'));

        const store = useNotificationsStore();
        await store.load();
        await store.remove(1);

        expect(store.items).toHaveLength(2);
        expect(store.total).toBe(2);
        expect(store.unreadCount).toBe(2);
    });

    it('sortedItems сортирует по created_at DESC', async () => {
        const earlier = mockNotif(1, { created_at: '2026-05-09T10:00:00Z' });
        const later = mockNotif(2, { created_at: '2026-05-09T15:00:00Z' });

        vi.mocked(notificationsApi.listNotifications).mockResolvedValue({
            items: [earlier, later],
            unread_count: 2,
            total: 2,
        });

        const store = useNotificationsStore();
        await store.load();

        expect(store.sortedItems[0]!.id).toBe(2); // later first
        expect(store.sortedItems[1]!.id).toBe(1);
    });

    it('reset() очищает state', async () => {
        vi.mocked(notificationsApi.listNotifications).mockResolvedValue({
            items: [mockNotif(1)],
            unread_count: 1,
            total: 1,
        });

        const store = useNotificationsStore();
        await store.load();
        store.reset();

        expect(store.items).toEqual([]);
        expect(store.unreadCount).toBe(0);
        expect(store.total).toBe(0);
        expect(store.fetchError).toBe(false);
    });
});
