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

// Мокаем API-слой reminders, чтобы реальный Pinia-store работал поверх spy'ев.
const createReminderMock = vi.fn();
const updateReminderMock = vi.fn();

vi.mock('../../resources/js/api/reminders', () => ({
    listReminders: vi.fn(),
    createReminder: (...args: unknown[]) => createReminderMock(...args),
    updateReminder: (...args: unknown[]) => updateReminderMock(...args),
    completeReminder: vi.fn(),
    deleteReminder: vi.fn(),
}));

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

import ReminderDialog from '../../resources/js/components/reminders/ReminderDialog.vue';
import type { ApiReminder } from '../../resources/js/api/reminders';

const mockReminder = (overrides: Partial<ApiReminder> = {}): ApiReminder => ({
    id: 42,
    deal_id: 7,
    text: 'Перезвонить клиенту',
    remind_at: '2026-06-01T10:30:00.000Z',
    completed_at: null,
    is_sent: false,
    sent_at: null,
    created_at: '2026-05-01T09:00:00.000Z',
    created_by: 1,
    assignee_id: null,
    creator_name: 'Иван Петров',
    ...overrides,
});

// VDialog в JSDOM teleport'ится — стаб делает <slot/> рендеримым inline.
const factory = (props: {
    modelValue: boolean;
    dealId?: number | null;
    reminder?: ApiReminder | null;
}) =>
    mount(ReminderDialog, {
        props,
        global: {
            plugins: [createVuetify()],
            stubs: {
                VDialog: {
                    template: '<div class="dialog-stub" v-if="modelValue"><slot /></div>',
                    props: ['modelValue'],
                },
            },
        },
    });

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

describe('ReminderDialog.vue', () => {
    describe('rendering', () => {
        it('не рендерит content при modelValue=false', () => {
            const wrapper = factory({ modelValue: false, dealId: 7 });
            expect(wrapper.find('.dialog-stub').exists()).toBe(false);
        });

        it('рендерит title "Новое напоминание" в create режиме', async () => {
            const wrapper = factory({ modelValue: true, dealId: 7, reminder: null });
            await flushPromises();
            expect(wrapper.text()).toContain('Новое напоминание');
            expect(wrapper.text()).not.toContain('Редактировать напоминание');
        });

        it('рендерит title "Редактировать напоминание" в edit режиме', async () => {
            const wrapper = factory({ modelValue: true, reminder: mockReminder() });
            await flushPromises();
            expect(wrapper.text()).toContain('Редактировать напоминание');
        });

        it('кнопка submit подписана "Создать" в create режиме', async () => {
            const wrapper = factory({ modelValue: true, dealId: 7 });
            await flushPromises();
            const submitBtn = wrapper.find('[data-testid="reminder-submit"]');
            expect(submitBtn.exists()).toBe(true);
            expect(submitBtn.text()).toContain('Создать');
        });

        it('кнопка submit подписана "Сохранить" в edit режиме', async () => {
            const wrapper = factory({ modelValue: true, reminder: mockReminder() });
            await flushPromises();
            const submitBtn = wrapper.find('[data-testid="reminder-submit"]');
            expect(submitBtn.text()).toContain('Сохранить');
        });

        it('рендерит обязательные поля textarea + datetime-input', async () => {
            const wrapper = factory({ modelValue: true, dealId: 7 });
            await flushPromises();
            expect(wrapper.find('[data-testid="reminder-text"]').exists()).toBe(true);
            expect(wrapper.find('[data-testid="reminder-at"]').exists()).toBe(true);
        });
    });

    describe('watch(dialogOpen) — populate / reset', () => {
        it('open + edit → text и remindAt берутся из props.reminder', async () => {
            const reminder = mockReminder({
                text: 'Тестовое описание',
                remind_at: '2026-06-15T14:25:00.000Z',
            });
            const wrapper = factory({ modelValue: true, reminder });
            await flushPromises();
            const vm = wrapper.vm as unknown as { text: string; remindAt: string };
            expect(vm.text).toBe('Тестовое описание');
            // remind_at был ISO; превратился в YYYY-MM-DDThh:mm local-format.
            expect(vm.remindAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/);
        });

        it('open + create → text сброшен, remindAt = default (1 час от now)', async () => {
            const wrapper = factory({ modelValue: true, dealId: 7, reminder: null });
            await flushPromises();
            const vm = wrapper.vm as unknown as { text: string; remindAt: string };
            expect(vm.text).toBe('');
            expect(vm.remindAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/);
            // Should be ~1 час впереди (allow tolerance ±5 минут).
            const parsed = new Date(vm.remindAt);
            const now = Date.now();
            const diffMin = (parsed.getTime() - now) / 60_000;
            expect(diffMin).toBeGreaterThan(55);
            expect(diffMin).toBeLessThan(65);
        });

        it('перезакрытие сбрасывает error и populates заново', async () => {
            const wrapper = factory({ modelValue: false, dealId: 7 });
            // Open via v-model.
            await wrapper.setProps({ modelValue: true });
            await flushPromises();
            const vm = wrapper.vm as unknown as { text: string; remindAt: string; error: string | null };
            expect(vm.error).toBeNull();
            expect(vm.text).toBe('');
            expect(vm.remindAt).not.toBe('');
        });

        it('reminder с remind_at=null → default используется', async () => {
            const wrapper = factory({
                modelValue: true,
                reminder: mockReminder({ remind_at: null, text: null }),
            });
            await flushPromises();
            const vm = wrapper.vm as unknown as { text: string; remindAt: string };
            expect(vm.text).toBe('');
            expect(vm.remindAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/);
        });
    });

    describe('submit — create mode', () => {
        it('успешный create → store.create вызывается + emit saved + закрытие', async () => {
            const created = mockReminder({ id: 100 });
            createReminderMock.mockResolvedValue(created);

            const wrapper = factory({ modelValue: true, dealId: 7, reminder: null });
            const vm = wrapper.vm as unknown as { text: string; remindAt: string };
            vm.text = '  Перезвонить  ';
            vm.remindAt = '2026-06-01T12:30';
            await flushPromises();

            await wrapper.find('[data-testid="reminder-submit"]').trigger('click');
            await flushPromises();

            expect(createReminderMock).toHaveBeenCalledTimes(1);
            const payload = createReminderMock.mock.calls[0]![0] as {
                deal_id: number;
                text: string | null;
                remind_at: string;
            };
            expect(payload.deal_id).toBe(7);
            expect(payload.text).toBe('Перезвонить'); // trimmed
            expect(payload.remind_at).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);

            const saved = wrapper.emitted('saved');
            expect(saved).toBeDefined();
            expect((saved![0] as unknown[])[0]).toEqual(created);

            // dialogOpen → false через update:modelValue.
            const closeEmits = wrapper.emitted('update:modelValue');
            expect(closeEmits).toBeDefined();
            expect(closeEmits!.some((e) => e[0] === false)).toBe(true);
        });

        it('text пустой после trim → передаёт null', async () => {
            createReminderMock.mockResolvedValue(mockReminder({ id: 101 }));

            const wrapper = factory({ modelValue: true, dealId: 7, reminder: null });
            const vm = wrapper.vm as unknown as { text: string; remindAt: string };
            vm.text = '   ';
            vm.remindAt = '2026-06-01T12:30';
            await flushPromises();

            await wrapper.find('[data-testid="reminder-submit"]').trigger('click');
            await flushPromises();

            const payload = createReminderMock.mock.calls[0]![0] as { text: string | null };
            expect(payload.text).toBeNull();
        });

        it('create без dealId → error "Не указан deal_id"', async () => {
            const wrapper = factory({ modelValue: true, dealId: null, reminder: null });
            const vm = wrapper.vm as unknown as { text: string; remindAt: string };
            vm.remindAt = '2026-06-01T12:30';
            await flushPromises();

            await wrapper.find('[data-testid="reminder-submit"]').trigger('click');
            await flushPromises();

            expect(createReminderMock).not.toHaveBeenCalled();
            expect(wrapper.text()).toContain('Не указан deal_id для создания.');
            expect(wrapper.emitted('saved')).toBeUndefined();
        });

        it('create API rejected → store returns null → error message', async () => {
            createReminderMock.mockRejectedValue(new Error('Network down'));

            const wrapper = factory({ modelValue: true, dealId: 7, reminder: null });
            const vm = wrapper.vm as unknown as { text: string; remindAt: string };
            vm.remindAt = '2026-06-01T12:30';
            await flushPromises();

            await wrapper.find('[data-testid="reminder-submit"]').trigger('click');
            await flushPromises();

            expect(wrapper.text()).toContain('Не удалось сохранить. Попробуйте позже.');
            expect(wrapper.emitted('saved')).toBeUndefined();
        });
    });

    describe('submit — edit mode', () => {
        it('успешный update → store.update вызывается с reminder.id + payload + emit saved', async () => {
            const original = mockReminder({ id: 55, text: 'Старое описание' });
            const updated = mockReminder({ id: 55, text: 'Новое описание' });
            updateReminderMock.mockResolvedValue(updated);

            const wrapper = factory({ modelValue: true, reminder: original });
            await flushPromises();

            const vm = wrapper.vm as unknown as { text: string; remindAt: string };
            vm.text = 'Новое описание';
            vm.remindAt = '2026-07-10T09:00';
            await flushPromises();

            await wrapper.find('[data-testid="reminder-submit"]').trigger('click');
            await flushPromises();

            expect(updateReminderMock).toHaveBeenCalledTimes(1);
            const [id, payload] = updateReminderMock.mock.calls[0] as [
                number,
                { text: string | null; remind_at: string },
            ];
            expect(id).toBe(55);
            expect(payload.text).toBe('Новое описание');
            expect(payload.remind_at).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);

            expect(createReminderMock).not.toHaveBeenCalled();

            const saved = wrapper.emitted('saved');
            expect(saved).toBeDefined();
            expect((saved![0] as unknown[])[0]).toEqual(updated);
        });

        it('update API rejected → error + не emit saved', async () => {
            updateReminderMock.mockRejectedValue(new Error('500'));

            const wrapper = factory({ modelValue: true, reminder: mockReminder() });
            await flushPromises();

            await wrapper.find('[data-testid="reminder-submit"]').trigger('click');
            await flushPromises();

            expect(wrapper.text()).toContain('Не удалось сохранить. Попробуйте позже.');
            expect(wrapper.emitted('saved')).toBeUndefined();
        });
    });

    describe('submit — общие пути', () => {
        it('пустой remindAt → error "Укажите дату и время"', async () => {
            const wrapper = factory({ modelValue: true, dealId: 7, reminder: null });
            const vm = wrapper.vm as unknown as { remindAt: string };
            vm.remindAt = '';
            await flushPromises();

            await wrapper.find('[data-testid="reminder-submit"]').trigger('click');
            await flushPromises();

            expect(createReminderMock).not.toHaveBeenCalled();
            expect(updateReminderMock).not.toHaveBeenCalled();
            expect(wrapper.text()).toContain('Укажите дату и время напоминания.');
        });

        it('submit вызывается также через клик по кнопке (v-form @submit интегрирован)', async () => {
            createReminderMock.mockResolvedValue(mockReminder({ id: 200 }));

            const wrapper = factory({ modelValue: true, dealId: 7, reminder: null });
            const vm = wrapper.vm as unknown as { remindAt: string };
            vm.remindAt = '2026-06-01T12:30';
            await flushPromises();

            await wrapper.find('[data-testid="reminder-submit"]').trigger('click');
            await flushPromises();

            expect(createReminderMock).toHaveBeenCalled();
        });
    });

    describe('cancel', () => {
        it('cancel закрывает dialog (emit update:modelValue=false)', async () => {
            const wrapper = factory({ modelValue: true, dealId: 7 });
            await flushPromises();

            // Cancel btn — vanilla v-btn, ищем по тексту "Отмена".
            const buttons = wrapper.findAll('button');
            const cancelBtn = buttons.find((b) => b.text().includes('Отмена'));
            expect(cancelBtn).toBeDefined();
            await cancelBtn!.trigger('click');
            await flushPromises();

            const closeEmits = wrapper.emitted('update:modelValue');
            expect(closeEmits).toBeDefined();
            expect(closeEmits!.some((e) => e[0] === false)).toBe(true);

            // Не должен звать API.
            expect(createReminderMock).not.toHaveBeenCalled();
            expect(updateReminderMock).not.toHaveBeenCalled();
        });
    });
});
