import { describe, it, expect, beforeEach, vi } from 'vitest';
import { mount, config } from '@vue/test-utils';
import { createPinia, setActivePinia } from 'pinia';
import { createVuetify } from 'vuetify';
import axios from 'axios';
import ProjectDetailsDrawer from '../../resources/js/components/projects/ProjectDetailsDrawer.vue';
import type { Project } from '../../resources/js/stores/projectsStore';
import { useProjectsStore } from '../../resources/js/stores/projectsStore';

vi.mock('axios');

// VAutocomplete (added in regions field task) requires Vuetify defaults provide context.
// Register Vuetify plugin globally for all mount() calls in this file.
config.global.plugins = [createVuetify()];

const sampleProject: Project = {
    id: 42,
    name: 'Натяжные потолки',
    signal_type: 'call',
    signal_identifier: '79161112233',
    daily_limit_target: 30,
    delivered_today: 0,
    is_active: true,
    region_mask: 0,
    region_mode: 'include',
    regions: [],
    delivery_days_mask: 31, // Mon-Fri
    sync_status: 'pending',
};

describe('ProjectDetailsDrawer', () => {
    beforeEach(() => setActivePinia(createPinia()));

    it('does not have .open class when project=null', () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: null } });
        const aside = wrapper.find('aside.project-details-drawer');
        expect(aside.exists()).toBe(true);
        expect(aside.classes()).not.toContain('open');
    });

    it('renders project name + daily_limit_target + delivery_days', () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
        const aside = wrapper.find('aside.project-details-drawer');
        expect(aside.classes()).toContain('open');
        expect(wrapper.text()).toContain('Натяжные потолки');
        const nameInput = wrapper.get('input[data-testid="pdd-name"]');
        expect((nameInput.element as HTMLInputElement).value).toBe('Натяжные потолки');
        const limitInput = wrapper.get('input[data-testid="pdd-limit"]');
        expect((limitInput.element as HTMLInputElement).value).toBe('30');
        // Days mask 31 = bits 0..4 = Mon..Fri (5 days active)
        const dayBtns = wrapper.findAll('button[data-testid^="pdd-day-"]');
        expect(dayBtns.length).toBe(7);
        const activeBtns = dayBtns.filter((b) => b.classes().includes('active'));
        expect(activeBtns.length).toBe(5);
    });

    it('emits close on X button click', async () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
        await wrapper.get('[data-testid="pdd-close"]').trigger('click');
        expect(wrapper.emitted('close')).toHaveLength(1);
    });

    it('emits close on Cancel button click', async () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
        await wrapper.get('[data-testid="pdd-cancel"]').trigger('click');
        expect(wrapper.emitted('close')).toHaveLength(1);
    });

    it('emits close on ESC keydown when project is set', async () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject }, attachTo: document.body });
        document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
        await wrapper.vm.$nextTick();
        expect(wrapper.emitted('close')).toHaveLength(1);
        wrapper.unmount();
    });

    it('does NOT emit close on ESC when project=null', async () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: null }, attachTo: document.body });
        document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
        await wrapper.vm.$nextTick();
        expect(wrapper.emitted('close')).toBeUndefined();
        wrapper.unmount();
    });

    it('reseeds form when project.id changes', async () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
        const nameInputBefore = wrapper.get('input[data-testid="pdd-name"]').element as HTMLInputElement;
        expect(nameInputBefore.value).toBe('Натяжные потолки');

        const other: Project = { ...sampleProject, id: 99, name: 'Окна СПб', daily_limit_target: 50 };
        await wrapper.setProps({ project: other });

        const nameInputAfter = wrapper.get('input[data-testid="pdd-name"]').element as HTMLInputElement;
        expect(nameInputAfter.value).toBe('Окна СПб');
        expect((wrapper.get('input[data-testid="pdd-limit"]').element as HTMLInputElement).value).toBe('50');
    });

    it('Save: PATCH /api/projects/{id} + emits saved on 200', async () => {
        (axios.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ data: { data: sampleProject } });
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });

        await wrapper.get('[data-testid="pdd-name"]').setValue('Новое имя');
        await wrapper.get('[data-testid="pdd-save"]').trigger('click');
        await wrapper.vm.$nextTick();
        await wrapper.vm.$nextTick();

        expect(axios.patch).toHaveBeenCalledWith(
            '/api/projects/42',
            expect.objectContaining({ name: 'Новое имя', daily_limit_target: 30 }),
        );
        expect(wrapper.emitted('saved')).toHaveLength(1);
    });

    it('Save: 422 → errors reactive проставляется, no saved emit', async () => {
        (axios.patch as unknown as ReturnType<typeof vi.fn>).mockRejectedValueOnce({
            response: { status: 422, data: { errors: { name: ['Имя обязательно'] } } },
        });
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });

        await wrapper.get('[data-testid="pdd-save"]').trigger('click');
        await wrapper.vm.$nextTick();
        await wrapper.vm.$nextTick();

        expect(wrapper.emitted('saved')).toBeUndefined();
        expect(wrapper.text()).toContain('Имя обязательно');
    });

    it('Save: 409 balance_insufficient → показывает блок, no saved emit', async () => {
        (axios.patch as unknown as ReturnType<typeof vi.fn>).mockRejectedValueOnce({
            response: {
                status: 409,
                data: {
                    error: 'balance_insufficient',
                    current_balance_rub: '5000.00',
                    current_capacity_leads: 10,
                    would_be_required_leads: 50,
                    deficit_leads: 40,
                },
            },
        });
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });

        await wrapper.get('[data-testid="pdd-save"]').trigger('click');
        await wrapper.vm.$nextTick();
        await wrapper.vm.$nextTick();

        expect(wrapper.emitted('saved')).toBeUndefined();
        expect(wrapper.text().toLowerCase()).toContain('баланс');
    });

    it('Pause button calls store.toggleActive', async () => {
        (axios.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
            data: { data: { ...sampleProject, is_active: false } },
        });
        (axios.get as unknown as ReturnType<typeof vi.fn> | undefined)?.mockResolvedValue?.({
            data: { data: [], meta: { total: 0 } },
        });
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
        const store = useProjectsStore();
        const spy = vi.spyOn(store, 'toggleActive').mockResolvedValueOnce(undefined);

        await wrapper.get('[data-testid="pdd-pause"]').trigger('click');
        expect(spy).toHaveBeenCalledWith(sampleProject);
    });

    it('Pause button label is "Возобновить" when is_active=false', () => {
        const paused: Project = { ...sampleProject, is_active: false };
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: paused } });
        expect(wrapper.get('[data-testid="pdd-pause"]').text()).toContain('Возобновить');
    });

    it('Pause button label is "Приостановить" when is_active=true', () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
        expect(wrapper.get('[data-testid="pdd-pause"]').text()).toContain('Приостановить');
    });

    it('Delete: confirm=true → del + close emit', async () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
        const store = useProjectsStore();
        const spy = vi.spyOn(store, 'del').mockResolvedValueOnce(undefined);
        vi.stubGlobal('confirm', () => true);

        await wrapper.get('[data-testid="pdd-delete"]').trigger('click');
        await wrapper.vm.$nextTick();
        expect(spy).toHaveBeenCalledWith(42);
        expect(wrapper.emitted('close')).toBeDefined();

        vi.unstubAllGlobals();
    });

    it('Delete: confirm=false → no del, no close', async () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
        const store = useProjectsStore();
        const spy = vi.spyOn(store, 'del').mockResolvedValueOnce(undefined);
        vi.stubGlobal('confirm', () => false);

        await wrapper.get('[data-testid="pdd-delete"]').trigger('click');
        expect(spy).not.toHaveBeenCalled();
        expect(wrapper.emitted('close')).toBeUndefined();

        vi.unstubAllGlobals();
    });

    it('Delete: 422 errors.project → drawer не закрывается, текст показан', async () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
        const store = useProjectsStore();
        const message =
            'Мы уже начали сбор лидов по этому проекту на завтра. Пока поставьте на паузу — мы увидим это сегодня в 18:00 и завтра не будем запускать сбор лидов по этому проекту. Удалить можно будет послезавтра.';
        vi.spyOn(store, 'del').mockRejectedValueOnce({
            response: { status: 422, data: { errors: { project: [message] } } },
        });
        vi.stubGlobal('confirm', () => true);

        await wrapper.get('[data-testid="pdd-delete"]').trigger('click');
        await wrapper.vm.$nextTick();
        await wrapper.vm.$nextTick();

        expect(wrapper.emitted('close')).toBeUndefined();
        expect(wrapper.text()).toContain('Мы уже начали сбор лидов');

        vi.unstubAllGlobals();
    });

    it('Save: 422 errors.project → текст показан', async () => {
        // Сброс предыдущих очередей mock'ов, чтобы наш reject точно был первым в queue.
        vi.mocked(axios.patch).mockReset();
        (axios.patch as unknown as ReturnType<typeof vi.fn>).mockRejectedValueOnce({
            response: {
                status: 422,
                data: {
                    errors: {
                        project: [
                            'Мы уже начали сбор лидов по этому проекту на завтра. Изменить источник можно будет послезавтра.',
                        ],
                    },
                },
            },
        });
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });

        await wrapper.get('[data-testid="pdd-save"]').trigger('click');
        await wrapper.vm.$nextTick();
        await wrapper.vm.$nextTick();

        expect(wrapper.emitted('saved')).toBeUndefined();
        expect(wrapper.text()).toContain('Изменить источник можно будет послезавтра');
    });

    it('renders region chips for project.regions = [1, 2]', async () => {
        const withRegions: Project = { ...sampleProject, regions: [1, 2] };
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: withRegions } });
        await wrapper.vm.$nextTick();
        const text = wrapper.text();
        expect(text).toContain('Адыгея'); // code 1
        expect(text).toContain('Алтай'); // code 2 (Республика Алтай)
    });

    it('selecting regions adds to regions array (no bitmask conversion)', async () => {
        (axios.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ data: { data: sampleProject } });
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: sampleProject } });
        const autocomplete = wrapper.getComponent({ name: 'VAutocomplete' });
        await autocomplete.vm.$emit('update:model-value', [82, 83]);
        await wrapper.vm.$nextTick();
        await wrapper.get('[data-testid="pdd-save"]').trigger('click');
        await wrapper.vm.$nextTick();
        await wrapper.vm.$nextTick();
        expect(axios.patch).toHaveBeenCalledWith('/api/projects/42', expect.objectContaining({ regions: [82, 83] }));
    });

    it('clearing all regions sets regions=[] on save', async () => {
        (axios.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ data: { data: sampleProject } });
        const withRegions: Project = { ...sampleProject, regions: [82, 83] };
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: withRegions } });
        const autocomplete = wrapper.getComponent({ name: 'VAutocomplete' });
        await autocomplete.vm.$emit('update:model-value', []);
        await wrapper.vm.$nextTick();
        await wrapper.get('[data-testid="pdd-save"]').trigger('click');
        await wrapper.vm.$nextTick();
        await wrapper.vm.$nextTick();
        expect(axios.patch).toHaveBeenCalledWith('/api/projects/42', expect.objectContaining({ regions: [] }));
    });

    it('region autocomplete has closable-chips so a single region can be removed', () => {
        const withRegions: Project = { ...sampleProject, regions: [1, 2] };
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: withRegions } });
        const autocomplete = wrapper.getComponent({ name: 'VAutocomplete' });
        expect(autocomplete.props('closableChips')).toBe(true);
    });

    // --- Эпик 3 (редизайн): замок заменён на редактируемые поля + объявление + подтверждение ---

    it('source_locked НЕ блокирует поле источника (можно редактировать)', () => {
        const locked: Project = {
            ...sampleProject,
            signal_type: 'call',
            source_locked: true,
            source_unlock_at: '2026-06-27T21:00:00+03:00',
            source_unlock_projected: true,
        };
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: locked } });
        const src = wrapper.get('input[data-testid="pdd-signal-identifier"]');
        expect((src.element as HTMLInputElement).disabled).toBe(false);
    });

    it('показывает объявление о вступлении изменений, не блокируя поле лимита', async () => {
        const locked: Project = {
            ...sampleProject,
            source_locked: true,
            source_unlock_at: '2026-06-27T21:00:00+03:00',
        };
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: locked } });
        // Меняем лимит — появляется объявление «вступит в силу позже».
        await wrapper.get('[data-testid="pdd-limit"]').setValue('20');
        expect(wrapper.find('[data-testid="pdd-applies-from-banner"]').exists()).toBe(true);
        // Поле лимита НЕ заблокировано.
        expect((wrapper.get('input[data-testid="pdd-limit"]').element as HTMLInputElement).disabled).toBe(false);
    });

    it('объявление о вступлении НЕ показывается на незалоченном проекте', async () => {
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: { ...sampleProject, source_locked: false } } });
        await wrapper.get('[data-testid="pdd-limit"]').setValue('20');
        expect(wrapper.find('[data-testid="pdd-applies-from-banner"]').exists()).toBe(false);
    });

    it('смена источника на залоченном проекте требует подтверждения (save только после confirm)', async () => {
        vi.mocked(axios.patch).mockReset();
        (axios.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValue({ data: { data: sampleProject } });
        const locked: Project = {
            ...sampleProject,
            signal_type: 'call',
            signal_identifier: '79161112233',
            source_locked: true,
            source_unlock_at: '2026-06-27T21:00:00+03:00',
        };
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: locked } });

        // Меняем источник и жмём Сохранить → должен открыться диалог подтверждения, PATCH ещё нет.
        await wrapper.get('[data-testid="pdd-signal-identifier"]').setValue('79990001122');
        await wrapper.get('[data-testid="pdd-save"]').trigger('click');
        await wrapper.vm.$nextTick();
        expect(wrapper.find('[data-testid="pdd-source-change-confirm"]').exists()).toBe(true);
        expect(axios.patch).not.toHaveBeenCalled();

        // Подтверждаем → PATCH уходит с новым источником.
        await wrapper.get('[data-testid="pdd-source-confirm-yes"]').trigger('click');
        await wrapper.vm.$nextTick();
        await wrapper.vm.$nextTick();
        expect(axios.patch).toHaveBeenCalledWith(
            '/api/projects/42',
            expect.objectContaining({ signal_identifier: '79990001122' }),
        );
    });

    it('диалог подтверждения берёт текст правила из source_change_message (единый источник)', async () => {
        vi.mocked(axios.patch).mockReset();
        (axios.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValue({ data: { data: sampleProject } });
        const locked: Project = {
            ...sampleProject,
            signal_type: 'call',
            signal_identifier: '79161112233',
            source_locked: true,
            source_change_message: 'Лиды по старому источнику придут до 27 июня, дальше — по новому.',
        };
        const wrapper = mount(ProjectDetailsDrawer, { props: { project: locked } });
        await wrapper.get('[data-testid="pdd-signal-identifier"]').setValue('79990001122');
        await wrapper.get('[data-testid="pdd-save"]').trigger('click');
        await wrapper.vm.$nextTick();
        const dialog = wrapper.find('[data-testid="pdd-source-change-confirm"]');
        expect(dialog.exists()).toBe(true);
        expect(dialog.text()).toContain('27 июня'); // дата из API-текста, не из JS-хардкода
    });

    it('смена источника на незалоченном проекте сохраняется без подтверждения', async () => {
        vi.mocked(axios.patch).mockReset();
        (axios.patch as unknown as ReturnType<typeof vi.fn>).mockResolvedValue({ data: { data: sampleProject } });
        const wrapper = mount(ProjectDetailsDrawer, {
            props: { project: { ...sampleProject, signal_type: 'call', source_locked: false } },
        });
        await wrapper.get('[data-testid="pdd-signal-identifier"]').setValue('79990001122');
        await wrapper.get('[data-testid="pdd-save"]').trigger('click');
        await wrapper.vm.$nextTick();
        await wrapper.vm.$nextTick();
        expect(wrapper.find('[data-testid="pdd-source-change-confirm"]').exists()).toBe(false);
        expect(axios.patch).toHaveBeenCalled();
    });
});
