import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import { createVuetify } from 'vuetify';
import axios from 'axios';

vi.mock('axios');

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

// Косяк 04 / вариант C: диалог при открытии тянет реквизиты. По умолчанию —
// light-complete, чтобы существующие тесты сразу попадали на шаг проекта.
vi.mock('../../resources/js/api/requisites', () => ({
    getRequisites: vi.fn().mockResolvedValue({
        subject_type: 'individual',
        contact_name: 'Иван',
        contact_phone: '+79161234567',
        inn: null,
    }),
    updateRequisites: vi.fn().mockResolvedValue({}),
}));

import { apiClient } from '../../resources/js/api/client';
import NewProjectDialog from '../../resources/js/views/projects/NewProjectDialog.vue';
import type { Project } from '../../resources/js/stores/projectsStore';

// VDialog в JSDOM не рендерит в teleport-цели; стаб делает <slot/> доступным
// внутри корня для wrapper.text() / find().
const factory = (
    props: { modelValue: boolean; mode?: 'create' | 'edit'; project?: Project | null } = {
        modelValue: true,
        mode: 'create',
    },
) =>
    mount(NewProjectDialog, {
        props,
        global: {
            plugins: [createVuetify()],
            stubs: {
                VDialog: {
                    template: '<div class="dialog-stub" v-if="modelValue"><slot /></div>',
                    props: ['modelValue'],
                },
            },
        },
    });

beforeEach(() => {
    setActivePinia(createPinia());
    vi.clearAllMocks();
});

describe('NewProjectDialog', () => {
    it('renders 3 tabs: Сайт / Звонок / СМС', async () => {
        const wrapper = factory();
        await flushPromises();
        const text = wrapper.text();
        expect(text).toContain('Сайт');
        expect(text).toContain('Звонок');
        expect(text).toContain('СМС');
    });

    it('shows hint «Как увеличить количество сделок» with explanatory tooltip', async () => {
        const wrapper = factory();
        await flushPromises();
        expect(wrapper.text()).toContain('Как увеличить количество сделок');
        const tips = wrapper.findAllComponents({ name: 'VTooltip' });
        const boost = tips.find((t) =>
            String(t.props('text')).includes('распределяется на нескольких поставщиков'),
        );
        expect(boost).toBeTruthy();
        expect(String(boost?.props('text'))).toContain('увеличьте лимит');
    });

    it('switching to SMS tab shows sms_senders field', async () => {
        const wrapper = factory();
        await flushPromises();
        const tabs = wrapper.findComponent({ name: 'VTabs' });
        if (tabs.exists()) {
            tabs.vm.$emit('update:modelValue', 'sms');
        }
        await flushPromises();
        expect(wrapper.text()).toMatch(/Отправители|sms_senders/i);
    });

    it('validation: empty site domain does not POST (button stays available, axios.post not called by default)', async () => {
        const wrapper = factory();
        await flushPromises();
        const btn = wrapper.find('[data-testid="submit-btn"]');
        expect(btn.exists()).toBe(true);
        // Не нажимаем — проверяем, что данные формы по умолчанию пустые и POST ещё не вызван.
        expect((axios.post as ReturnType<typeof vi.fn>).mock?.calls?.length ?? 0).toBe(0);
    });

    it('требует название: пустое имя блокирует POST и показывает ошибку', async () => {
        const wrapper = factory();
        await flushPromises();
        const vm = wrapper.vm as unknown as {
            confirmVsyaRf: () => void;
            submit: () => Promise<void>;
            form: { name: string; signal_identifier: string };
        };
        vm.confirmVsyaRf(); // проходим гейт обязательного региона
        vm.form.signal_identifier = 'okna-konkurent.ru'; // источник заполнен
        vm.form.name = ''; // имя пустое
        await vm.submit();
        await flushPromises();
        expect(apiClient.post).not.toHaveBeenCalled();
        expect(wrapper.text()).toContain('Введите название проекта');
    });

    it('требует источник: пустой домен сайта блокирует POST и показывает ошибку', async () => {
        const wrapper = factory();
        await flushPromises();
        const vm = wrapper.vm as unknown as {
            confirmVsyaRf: () => void;
            submit: () => Promise<void>;
            form: { name: string; signal_identifier: string };
        };
        vm.confirmVsyaRf();
        vm.form.name = 'Мой проект';
        vm.form.signal_identifier = ''; // домен пустой (signal_type='site' по умолчанию)
        await vm.submit();
        await flushPromises();
        expect(apiClient.post).not.toHaveBeenCalled();
        expect(wrapper.text()).toContain('Введите домен сайта в вашей нише');
    });

    it.skip('submits valid site project to POST /api/projects', async () => {
        // TODO: полная проверка submit требует rendering Vuetify-формы и заполнения
        // v-text-field/v-combobox/v-btn-toggle — нестабильно в JSDOM. Покрытие
        // делается через Histoire story + e2e (Playwright) после Plan 5 closure.
    });

    it.skip('emits saved event after successful POST', async () => {
        // TODO: см. предыдущий skip — те же причины.
    });

    it('renders regions autocomplete with 89 selectable subjects (excluding "Вся РФ" sentinel)', async () => {
        const wrapper = factory();
        await flushPromises();
        const autocomplete = wrapper.findComponent({ name: 'VAutocomplete' });
        expect(autocomplete.exists()).toBe(true);
        expect(autocomplete.props('items')).toHaveLength(89);
        expect((autocomplete.props('items') as Array<{ code: number }>).every((r) => r.code !== 0)).toBe(true);
    });

    it('sends regions array in POST payload', async () => {
        const wrapper = factory();
        await flushPromises();
        const autocomplete = wrapper.findComponent({ name: 'VAutocomplete' });
        autocomplete.vm.$emit('update:model-value', [82, 83]);
        await flushPromises();
        const vm = wrapper.vm as unknown as { form: { name: string; signal_identifier: string } };
        vm.form.name = 'Проект';
        vm.form.signal_identifier = 'okna-konkurent.ru';
        await wrapper.find('[data-testid="submit-btn"]').trigger('click');
        await flushPromises();
        expect(apiClient.post).toHaveBeenCalledWith('/api/projects', expect.objectContaining({ regions: [82, 83] }));
    });

    it('409 balance_insufficient → открывает диалог перегрузки; save-blocked пере-сабмитит с force_save_blocked', async () => {
        // Spec C §6.2 (Task 1.10): первый POST падает 409, второй (после выбора
        // «Сохранить и приостановить») уходит с force_save_blocked=true.
        (apiClient.post as ReturnType<typeof vi.fn>).mockRejectedValueOnce({
            response: {
                status: 409,
                data: {
                    error: 'balance_insufficient',
                    current_balance_rub: '1000.00',
                    current_capacity_leads: 30,
                    would_be_required_leads: 50,
                    deficit_leads: 20,
                },
            },
        });

        const wrapper = factory();
        await flushPromises();
        // Проходим гейт обязательного региона через подтверждённую «Вся РФ» + валидные поля.
        const vm = wrapper.vm as unknown as {
            confirmVsyaRf: () => void;
            submit: () => Promise<void>;
            form: { name: string; signal_identifier: string };
        };
        vm.confirmVsyaRf();
        vm.form.name = 'Проект';
        vm.form.signal_identifier = 'okna-konkurent.ru';
        await vm.submit();
        await flushPromises();

        const overload = wrapper.find('[data-testid="overload-dialog"]');
        expect(overload.exists()).toBe(true);
        expect(overload.text()).toContain('20');

        await wrapper.find('[data-testid="overload-save-blocked"]').trigger('click');
        await flushPromises();

        expect(apiClient.post).toHaveBeenCalledTimes(2);
        expect(apiClient.post).toHaveBeenLastCalledWith(
            '/api/projects',
            expect.objectContaining({ force_save_blocked: true }),
        );
        expect(wrapper.emitted('saved')).toBeTruthy();
    });

    it('показывает баннер «когда пойдут лиды» в режиме создания', async () => {
        const wrapper = factory({ modelValue: true, mode: 'create' });
        await flushPromises();
        const banner = wrapper.find('[data-testid="np-lead-banner"]');
        expect(banner.exists()).toBe(true);
        expect(banner.text()).toContain('Первые лиды пойдут с');
    });

    it('скрывает баннер в режиме редактирования', async () => {
        const wrapper = factory({ modelValue: true, mode: 'edit' });
        await flushPromises();
        expect(wrapper.find('[data-testid="np-lead-banner"]').exists()).toBe(false);
    });

    it('M: правка site-проекта не шлёт sms_senders/sms_keyword (без молчаливого 422)', async () => {
        const project = {
            id: 7,
            name: 'Сайт-проект',
            signal_type: 'site',
            signal_identifier: 'okna.ru',
            sms_senders: null,
            sms_keyword: null,
            daily_limit_target: 50,
            delivered_today: 0,
            is_active: true,
            regions: [77],
            delivery_days_mask: 127,
            sync_status: 'ok',
        } as unknown as Project;
        const wrapper = factory({ modelValue: true, mode: 'edit', project });
        await flushPromises();
        const vm = wrapper.vm as unknown as { submit: () => Promise<void> };
        await vm.submit();
        await flushPromises();

        expect(apiClient.patch).toHaveBeenCalledTimes(1);
        const body = (apiClient.patch as ReturnType<typeof vi.fn>).mock.calls[0][1] as Record<string, unknown>;
        expect(body).not.toHaveProperty('sms_senders');
        expect(body).not.toHaveProperty('sms_keyword');
    });
});
