import { describe, it, expect, vi, afterEach } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import { createVuetify } from 'vuetify';
import { createRouter, createMemoryHistory } from 'vue-router';
import { createPinia, setActivePinia } from 'pinia';
import DealsView from '../../resources/js/views/DealsView.vue';
import * as dealsApi from '../../resources/js/api/deals';
import { useAuthStore } from '../../resources/js/stores/auth';
import type { AuthUser } from '../../resources/js/api/auth';
import type { MockDeal } from '../../resources/js/composables/mockDeals';

function apiDeal(id: number, over: Partial<dealsApi.ApiDeal> = {}): dealsApi.ApiDeal {
    return {
        id,
        tenant_id: 42,
        project_id: 1,
        project_name: 'Окна',
        phone: `+7 916 000-00-0${id}`,
        contact_name: null,
        status: 'new',
        manager_id: null,
        manager_name: null,
        manager_initials: null,
        received_at: '2026-05-15T09:00:00+00:00',
        comment: null,
        city: null,
        project_signal_type: 'call',
        ...over,
    };
}

async function mountDeals(deals: dealsApi.ApiDeal[] = [apiDeal(1), apiDeal(2)], total = 2) {
    setActivePinia(createPinia());
    const auth = useAuthStore();
    auth.user = { id: 1, tenant_id: 42, email: 't@t.com' } as AuthUser;
    const dealsSpy = vi.spyOn(dealsApi, 'listDeals').mockResolvedValue({ deals, total, limit: 20, offset: 0 });
    vi.spyOn(dealsApi, 'listProjects').mockResolvedValue([{ id: 1, name: 'Окна', tag: null, type: 'supplier' }]);
    const router = createRouter({
        history: createMemoryHistory(),
        routes: [{ path: '/deals', component: DealsView }],
    });
    await router.push('/deals');
    await router.isReady();
    const wrapper = mount(DealsView, {
        global: { plugins: [createVuetify(), router], stubs: { DealDetailDrawer: true, DealsFilters: true } },
    });
    await flushPromises();
    // Reset call history so subsequent vi.spyOn calls in tests start from count=0.
    dealsSpy.mockClear();
    return wrapper;
}

describe('DealsView.vue — реестр лидов', () => {
    it('заголовок «Сделки»', async () => {
        expect((await mountDeals()).find('h1').text()).toBe('Сделки');
    });

    it('панель экспорта: поля дат + кнопки Excel/CSV', async () => {
        const w = await mountDeals();
        expect(w.find('[data-testid="export-from"]').exists()).toBe(true);
        expect(w.find('[data-testid="export-to"]').exists()).toBe(true);
        expect(w.find('[data-testid="export-xlsx-btn"]').exists()).toBe(true);
        expect(w.find('[data-testid="export-csv-btn"]').exists()).toBe(true);
    });

    it('селектор «Показывать по» с вариантами 10/20/50', async () => {
        const w = await mountDeals();
        const toggle = w.find('[data-testid="perpage-toggle"]');
        expect(toggle.exists()).toBe(true);
        ['10', '20', '50'].forEach((n) => expect(toggle.text()).toContain(n));
    });

    it('НЕТ кнопки «Новая сделка» и режима «Корзина»', async () => {
        const w = await mountDeals();
        // «Новая сделка» присутствует как статус-пилюля в таблице (slug `new` —
        // редизайн воронки 2026-05-17), поэтому проверяем отсутствие именно
        // КНОПКИ создания сделки: ручное создание убрано в реестре лидов.
        const buttons = w.findAll('button');
        expect(buttons.some((b) => b.text().includes('Новая сделка'))).toBe(false);
        expect(w.text()).not.toContain('Корзина');
    });

    it('загружает сделки в dealsState через API', async () => {
        const w = await mountDeals([apiDeal(1), apiDeal(2), apiDeal(3)], 3);
        const vm = w.vm as unknown as { dealsState: MockDeal[]; total: number };
        expect(vm.dealsState.length).toBe(3);
        expect(vm.total).toBe(3);
    });

    it('openPanel выбирает сделку, повторный клик закрывает', async () => {
        const w = await mountDeals();
        const vm = w.vm as unknown as {
            dealsState: MockDeal[];
            panelOpen: boolean;
            selectedDeal: MockDeal | null;
            openPanel: (d: MockDeal) => void;
        };
        vm.openPanel(vm.dealsState[0]);
        expect(vm.panelOpen).toBe(true);
        expect(vm.selectedDeal?.id).toBe(1);
        vm.openPanel(vm.dealsState[0]);
        expect(vm.panelOpen).toBe(false);
    });

    it('при selected=1 drawer авто-открывается, bulk-полоса скрыта (18.05.2026 ux)', async () => {
        const w = await mountDeals();
        const vm = w.vm as unknown as {
            selected: number[];
            panelOpen: boolean;
            selectedDeal: MockDeal | null;
        };
        vm.selected = [1];
        await flushPromises();
        expect(vm.panelOpen).toBe(true);
        expect(vm.selectedDeal?.id).toBe(1);
        expect(w.find('[data-testid="bulk-bar"]').exists()).toBe(false);
    });

    it('при selected≥2 drawer закрывается, bulk-полоса видна (18.05.2026 ux)', async () => {
        const w = await mountDeals();
        const vm = w.vm as unknown as {
            selected: number[];
            panelOpen: boolean;
            dealsState: MockDeal[];
            openPanel: (d: MockDeal) => void;
        };
        vm.openPanel(vm.dealsState[0]);
        expect(vm.panelOpen).toBe(true);
        vm.selected = [1, 2];
        await flushPromises();
        expect(vm.panelOpen).toBe(false);
        expect(w.find('[data-testid="bulk-bar"]').exists()).toBe(true);
    });

    it('bulk-bar появляется при выборе и applyBulkStatus меняет статус', async () => {
        const w = await mountDeals();
        const vm = w.vm as unknown as {
            selected: number[];
            dealsState: MockDeal[];
            applyBulkStatus: (s: string) => Promise<void>;
        };
        vi.spyOn(dealsApi, 'transitionDeals').mockResolvedValue({ updated: 2, requested: 2, status: 'viewed' });
        vm.selected = [1, 2];
        await flushPromises();
        expect(w.find('[data-testid="bulk-bar"]').exists()).toBe(true);
        await vm.applyBulkStatus('viewed');
        expect(vm.dealsState.find((d) => d.id === 1)?.statusSlug).toBe('viewed');
    });

    it('exportByRange xlsx вызывает exportDealsByRange', async () => {
        const w = await mountDeals();
        const spy = vi.spyOn(dealsApi, 'exportDealsByRange').mockResolvedValue(new Blob());
        Object.defineProperty(URL, 'createObjectURL', { value: vi.fn(() => 'blob:m'), configurable: true });
        Object.defineProperty(URL, 'revokeObjectURL', { value: vi.fn(), configurable: true });
        vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {});
        const vm = w.vm as unknown as { exportByRange: (f: string) => Promise<void>; exportToastOpen: boolean };
        await vm.exportByRange('xlsx');
        expect(spy).toHaveBeenCalledOnce();
        expect(vm.exportToastOpen).toBe(true);
    });

    it('смена фильтра вызывает loadDeals ровно один раз (без двойного fetch)', async () => {
        const w = await mountDeals();
        const vm = w.vm as unknown as { filterStatus: string | null; page: number };
        // Установим spy до смены page, чтобы перехватить все вызовы
        const spy = vi.spyOn(dealsApi, 'listDeals').mockResolvedValue({ deals: [], total: 0, limit: 20, offset: 0 });
        // Переходим на страницу 3 — это вызовет watch(page) → loadDeals один раз
        vm.page = 3;
        await flushPromises();
        spy.mockClear(); // сбрасываем счётчик: интересует только смена фильтра
        // Смена фильтра при page=3: A10 fix должен лишь сбросить page→1 (без прямого loadDeals),
        // затем watch(page) делает ровно один fetch
        vm.filterStatus = 'viewed';
        await flushPromises();
        expect(spy).toHaveBeenCalledTimes(1);
    });

    it('loadDeals reject → dealsState пустой + fetchError', async () => {
        setActivePinia(createPinia());
        const auth = useAuthStore();
        auth.user = { id: 1, tenant_id: 42, email: 't@t.com' } as AuthUser;
        vi.spyOn(dealsApi, 'listDeals').mockRejectedValue(new Error('500'));
        vi.spyOn(dealsApi, 'listProjects').mockResolvedValue([]);
        const router = createRouter({
            history: createMemoryHistory(),
            routes: [{ path: '/deals', component: DealsView }],
        });
        await router.push('/deals');
        await router.isReady();
        const w = mount(DealsView, {
            global: { plugins: [createVuetify(), router], stubs: { DealDetailDrawer: true, DealsFilters: true } },
        });
        await flushPromises();
        const vm = w.vm as unknown as { dealsState: MockDeal[]; fetchError: boolean };
        expect(vm.dealsState.length).toBe(0);
        expect(vm.fetchError).toBe(true);
    });
});

afterEach(() => vi.restoreAllMocks());
