import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import { createVuetify } from 'vuetify';
import { createPinia, setActivePinia } from 'pinia';
import KanbanView from '../../resources/js/views/KanbanView.vue';
import { useAuthStore } from '../../resources/js/stores/auth';
import * as dealsApi from '../../resources/js/api/deals';
import { LEAD_STATUSES } from '../../resources/js/composables/leadStatuses';
import { MOCK_DEALS, type MockDeal } from '../../resources/js/composables/mockDeals';

describe('KanbanView.vue', () => {
    // KanbanView содержит DealDetailDrawer (v-navigation-drawer), который требует
    // injected layout от v-app — оборачиваем в v-app для теста.
    // KanbanView содержит DealDetailDrawer (v-navigation-drawer) — stub'им,
    // т.к. layout-injection недоступна в Vitest. Drawer тестируется отдельно.
    const factory = () => {
        setActivePinia(createPinia());
        return mount(KanbanView, {
            global: {
                plugins: [createVuetify()],
                stubs: { DealDetailDrawer: true, NewDealDialog: true },
            },
        });
    };

    it('монтируется и содержит заголовок «Канбан»', () => {
        const wrapper = factory();
        expect(wrapper.find('h1').text()).toBe('Канбан');
    });

    it('рендерит ровно 5 KanbanColumn (по числу lead_statuses)', () => {
        const wrapper = factory();
        const cols = wrapper.findAllComponents({ name: 'KanbanColumn' });
        expect(cols).toHaveLength(LEAD_STATUSES.length);
        expect(cols).toHaveLength(5);
    });

    it('каждая колонка получает соответствующий статус', () => {
        const wrapper = factory();
        const cols = wrapper.findAllComponents({ name: 'KanbanColumn' });
        cols.forEach((col, i) => {
            expect(col.props('status').slug).toBe(LEAD_STATUSES[i].slug);
        });
    });

    it('содержит page-stats с числом статусов и сделок', () => {
        const wrapper = factory();
        const text = wrapper.text();
        expect(text).toContain('5');
        expect(text).toContain('статусов');
        expect(text).toContain('сделок');
    });

    it('содержит кнопку «Новая сделка»', () => {
        const wrapper = factory();
        expect(wrapper.text()).toContain('Новая сделка');
    });

    it('содержит подсказку про перетаскивание (DnD активен)', () => {
        const wrapper = factory();
        expect(wrapper.text()).toMatch(/[Пп]еретаскивание/);
    });

    // Кнопка «Новая сделка» убрана из Канбана (задача 22.06 — создание сделок не с доски).
    // Тест клика по `new-deal-btn` удалён вместе с кнопкой.

    it('onDealCreated кладёт сделку в правильную колонку по statusSlug', async () => {
        const wrapper = factory();
        const vm = wrapper.vm as unknown as {
            dealsByStatus: Record<string, Array<{ id: number; statusSlug: string }>>;
            onDealCreated: (deal: Record<string, unknown>) => void;
            totalDeals: number;
        };
        const beforeNew = vm.dealsByStatus.new.length;
        const beforeTotal = vm.totalDeals;
        // Передаём полную форму deal — Kanban-карточка ожидает manager/cost/etc.
        vm.onDealCreated({
            id: 999,
            name: 'Тест',
            phone: '+7 (999) 000-00-00',
            statusSlug: 'new',
            project: 'Окна Москва',
            manager: { initials: 'Т', name: 'Тест' },
            cost: 100,
            receivedMinutesAgo: 0,
        });
        await wrapper.vm.$nextTick();
        expect(vm.dealsByStatus.new.length).toBe(beforeNew + 1);
        expect(vm.dealsByStatus.new[0].id).toBe(999);
        expect(vm.totalDeals).toBe(beforeTotal + 1);
    });

    it('обновляет statusSlug сделки при drop в новую колонку (event.added)', async () => {
        const wrapper = factory();
        // Засеваем dealsByStatus фикстурой MOCK_DEALS (init теперь пустой).
        const vm = wrapper.vm as unknown as { dealsByStatus: Record<string, MockDeal[]> };
        for (const deal of MOCK_DEALS) {
            if (vm.dealsByStatus[deal.statusSlug]) {
                vm.dealsByStatus[deal.statusSlug].push({ ...deal });
            }
        }
        await wrapper.vm.$nextTick();

        const cols = wrapper.findAllComponents({ name: 'KanbanColumn' });
        // Берём сделку из первой колонки (new) и эмулируем «added» в paid-колонке.
        const newCol = cols[0]; // new — sortOrder=1
        const wonCol = cols.find((c) => c.props('status').slug === 'won')!;
        const dealToMove = (newCol.props('deals') as { id: number; statusSlug: string }[])[0];

        // Эмуляция события vuedraggable@change → KanbanView.onColumnChange.
        await wonCol.vm.$emit('change', {
            added: { element: dealToMove, newIndex: 0 },
        });
        await wrapper.vm.$nextTick();

        // statusSlug сделки должен переключиться на 'won'.
        expect(dealToMove.statusSlug).toBe('won');
    });
});

// I3 regression: API reject → dealsByStatus пустые + fetchError=true (нет mock-fallback)
// Faithful-паттерн: auth + mock ДО mount, onMounted сам вызывает loadDeals.
describe('KanbanView I3 regression', () => {
    it('loadDeals reject оставляет dealsByStatus пустыми и выставляет fetchError', async () => {
        vi.spyOn(dealsApi, 'listDeals').mockRejectedValue(new Error('500'));
        setActivePinia(createPinia());
        const auth = useAuthStore();
        auth.user = { id: 1, tenant_id: 42, email: 'test@test.com' } as never;
        const wrapper = mount(KanbanView, {
            global: {
                plugins: [createVuetify()],
                stubs: { DealDetailDrawer: true, NewDealDialog: true },
            },
        });
        await flushPromises();

        const vm = wrapper.vm as unknown as {
            dealsByStatus: Record<string, MockDeal[]>;
            fetchError: boolean;
        };
        expect(vm.fetchError).toBe(true);
        // Все колонки пусты — нет mock-fallback
        const allDeals = Object.values(vm.dealsByStatus).flat();
        expect(allDeals.length).toBe(0);
    });
});

describe('KanbanView DnD persist (Sprint 1 C4)', () => {
    beforeEach(() => {
        vi.clearAllMocks();
    });

    it('onColumnChange triggers dealsApi.transitionDeals with [dealId] and target status', async () => {
        const transitionSpy = vi.spyOn(dealsApi, 'transitionDeals').mockResolvedValue({
            updated: 1,
            requested: 1,
            status: 'in_progress',
        });
        const wrapper = mount(KanbanView, {
            global: {
                plugins: [createPinia(), createVuetify()],
                stubs: { KanbanColumn: true, DealDetailDrawer: true, NewDealDialog: true },
            },
        });
        const auth = useAuthStore();
        auth.user = { id: 99, tenant_id: 7, email: 'demo@demo.local' } as never;
        await new Promise((r) => setTimeout(r, 30));

        const deal = {
            id: 42,
            statusSlug: 'new' as const,
            name: 'X',
            phone: '+79161234567',
            project: 'p',
            manager: { name: 'M', initials: 'M' },
            cost: 100,
            receivedMinutesAgo: 5,
        };
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        await (wrapper.vm as any).onColumnChange('in_progress', { added: { element: deal, newIndex: 0 } });

        expect(transitionSpy).toHaveBeenCalledWith({
            tenant_id: 7,
            ids: [42],
            status: 'in_progress',
        });
        expect(deal.statusSlug).toBe('in_progress');
    });

    it('onColumnChange reverts statusSlug + opens toast when API rejects', async () => {
        vi.spyOn(dealsApi, 'transitionDeals').mockRejectedValue(new Error('500'));
        const wrapper = mount(KanbanView, {
            global: {
                plugins: [createPinia(), createVuetify()],
                stubs: { KanbanColumn: true, DealDetailDrawer: true, NewDealDialog: true },
            },
        });
        const auth = useAuthStore();
        auth.user = { id: 99, tenant_id: 7, email: 'demo@demo.local' } as never;
        await new Promise((r) => setTimeout(r, 30));

        const deal = {
            id: 43,
            statusSlug: 'new' as const,
            name: 'Y',
            phone: '+79161234567',
            project: 'p',
            manager: { name: 'M', initials: 'M' },
            cost: 100,
            receivedMinutesAgo: 5,
        };
        // Имитируем vuedraggable mutation: карточка уже в target column до вызова onColumnChange.
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const vm = wrapper.vm as any;
        if (!vm.dealsByStatus.in_progress) vm.dealsByStatus.in_progress = [];
        vm.dealsByStatus.in_progress.push(deal);

        await vm.onColumnChange('in_progress', { added: { element: deal, newIndex: 0 } });

        // statusSlug rolled back
        expect(deal.statusSlug).toBe('new');
        // Card removed from target column (array-revert branch coverage)
        expect(vm.dealsByStatus.in_progress.findIndex((d: { id: number }) => d.id === 43)).toBe(-1);
        // Card restored to source column
        expect(vm.dealsByStatus.new.findIndex((d: { id: number }) => d.id === 43)).toBeGreaterThanOrEqual(0);
        // Toast shown
        expect(vm.transitionToastOpen).toBe(true);
        expect(vm.transitionToastText).toContain('Не удалось');
    });

    it('onColumnChange skips API call if no auth.user.tenant_id', async () => {
        const transitionSpy = vi.spyOn(dealsApi, 'transitionDeals').mockResolvedValue({
            updated: 1,
            requested: 1,
            status: 'in_progress',
        });
        const wrapper = mount(KanbanView, {
            global: {
                plugins: [createPinia(), createVuetify()],
                stubs: { KanbanColumn: true, DealDetailDrawer: true, NewDealDialog: true },
            },
        });
        const auth = useAuthStore();
        auth.user = null;
        await new Promise((r) => setTimeout(r, 30));

        const deal = {
            id: 44,
            statusSlug: 'new' as const,
            name: 'Z',
            phone: '+79161234567',
            project: 'p',
            manager: { name: 'M', initials: 'M' },
            cost: 100,
            receivedMinutesAgo: 5,
        };
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        await (wrapper.vm as any).onColumnChange('in_progress', { added: { element: deal, newIndex: 0 } });

        // Без auth — только optimistic local change, API не зовётся
        expect(transitionSpy).not.toHaveBeenCalled();
        expect(deal.statusSlug).toBe('in_progress');
    });
});
