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

// Мокаем api/admin до import'а компонента — иначе реальный axios вызывает
// сетевые запросы в jsdom-runtime.
vi.mock('../../resources/js/api/admin', () => ({
    impersonationInit: vi.fn(),
    impersonationVerify: vi.fn(),
    impersonationEnd: vi.fn(),
}));

// extractValidationErrors / extractErrorMessage используют axios.isAxiosError,
// который в jsdom возвращает false для plain Error → мокаем helper'ы тоже
// (паттерн из auth-store.spec.ts).
vi.mock('../../resources/js/api/client', () => ({
    extractValidationErrors: vi.fn(() => null),
    extractErrorMessage: vi.fn((_e, fallback?: string) => fallback ?? 'err'),
    apiClient: {},
    ensureCsrfCookie: vi.fn(),
}));

import * as adminApi from '../../resources/js/api/admin';
import ImpersonationDialog from '../../resources/js/components/admin/ImpersonationDialog.vue';
import { MOCK_TENANTS } from '../../resources/js/composables/mockTenants';

/**
 * v-dialog требует layout-injection (v-app/v-layout) — в Vitest без auto-import
 * это невозможно. Stub'им VDialog как passthrough div, чтобы slot рендерился.
 */
const factory = (props: { modelValue: boolean; tenant: (typeof MOCK_TENANTS)[number] | null }) =>
    mount(ImpersonationDialog, {
        props: { ...props, requestedBy: 1 },
        global: {
            plugins: [createVuetify()],
            stubs: {
                VDialog: {
                    template: '<div class="dialog-stub" v-if="modelValue"><slot /></div>',
                    props: ['modelValue'],
                },
                VTooltip: {
                    template: '<div><slot name="activator" :props="{}" /></div>',
                },
            },
        },
    });

const sampleTenant = MOCK_TENANTS[0]; // Окна Москва ООО

describe('ImpersonationDialog.vue', () => {
    beforeEach(() => {
        vi.clearAllMocks();
    });

    afterEach(() => {
        vi.unstubAllEnvs();
    });

    it('не рендерит content когда modelValue=false', () => {
        const wrapper = factory({ modelValue: false, tenant: sampleTenant });
        expect(wrapper.find('.dialog-stub').exists()).toBe(false);
    });

    it('step 1 — заголовок «Войти как клиент» + tenant info + textarea reason', () => {
        const wrapper = factory({ modelValue: true, tenant: sampleTenant });
        const text = wrapper.text();
        expect(text).toContain('Войти как клиент');
        expect(text).toContain(sampleTenant.name);
        expect(text).toContain(sampleTenant.code);
        expect(text).toContain('ИНН ' + sampleTenant.inn);
        expect(wrapper.find('[data-testid="reason-input"]').exists()).toBe(true);
        // submit-init disabled при пустом reason
        expect(wrapper.find('[data-testid="submit-init-btn"]').exists()).toBe(true);
    });

    it('reason < 30 chars — submit disabled, отображается remaining counter', async () => {
        const wrapper = factory({ modelValue: true, tenant: sampleTenant });
        const textarea = wrapper.find('[data-testid="reason-input"] textarea');
        await textarea.setValue('коротко');
        await wrapper.vm.$nextTick();
        // Hint показывает сколько осталось до 30 chars
        expect(wrapper.text()).toMatch(/Ещё\s+\d+\s+символов/);
    });

    it('успешный init → step verify → показывает email + dev-code banner', async () => {
        vi.mocked(adminApi.impersonationInit).mockResolvedValue({
            token_id: 42,
            expires_at: '2026-05-09T12:00:00Z',
            sent_to_email: 'admin@okna-moscow.ru',
            _dev_plain_code: '123456',
        });

        const wrapper = factory({ modelValue: true, tenant: sampleTenant });
        const textarea = wrapper.find('[data-testid="reason-input"] textarea');
        await textarea.setValue('Тикет SUP-12453: клиент сообщил, что в карточке сделки не сохраняется коммент.');
        await wrapper.find('[data-testid="submit-init-btn"]').trigger('click');
        await flushPromises();

        expect(adminApi.impersonationInit).toHaveBeenCalledWith({
            tenant_id: sampleTenant.id,
            requested_by: 1,
            reason: expect.stringContaining('Тикет SUP-12453'),
        });

        // step 2 → email + dev-code visible
        const text = wrapper.text();
        expect(text).toContain('Код отправлен на email клиента');
        expect(text).toContain('admin@okna-moscow.ru');
        expect(wrapper.find('[data-testid="dev-code-banner"]').exists()).toBe(true);
        expect(wrapper.find('[data-testid="dev-code-banner"]').text()).toContain('123456');
        expect(wrapper.find('[data-testid="code-input"]').exists()).toBe(true);
    });

    it('verify success → step active → кнопка «Завершить сессию»', async () => {
        vi.mocked(adminApi.impersonationInit).mockResolvedValue({
            token_id: 42,
            expires_at: '2026-05-09T12:00:00Z',
            sent_to_email: 'admin@okna-moscow.ru',
            _dev_plain_code: '111222',
        });
        vi.mocked(adminApi.impersonationVerify).mockResolvedValue({
            token_id: 42,
            tenant_id: sampleTenant.id,
            used_at: '2026-05-09T11:30:00Z',
            message: 'OK',
            machine_token: 'test-machine-token-abc123',
            expires_at: '2026-05-09T12:30:00Z',
        });

        const wrapper = factory({ modelValue: true, tenant: sampleTenant });
        // Step 1 → 2
        await wrapper.find('[data-testid="reason-input"] textarea').setValue('A'.repeat(35));
        await wrapper.find('[data-testid="submit-init-btn"]').trigger('click');
        await flushPromises();
        // Step 2 → 3
        await wrapper.find('[data-testid="code-input"] input').setValue('111222');
        await wrapper.find('[data-testid="submit-verify-btn"]').trigger('click');
        await flushPromises();

        expect(adminApi.impersonationVerify).toHaveBeenCalledWith({ token_id: 42, code: '111222' });
        expect(wrapper.text()).toContain('Impersonation активен');
        expect(wrapper.find('[data-testid="submit-end-btn"]').exists()).toBe(true);
    });

    it('verify с не-6-цифрами не вызывает API + показывает ошибку', async () => {
        vi.mocked(adminApi.impersonationInit).mockResolvedValue({
            token_id: 42,
            expires_at: '2026-05-09T12:00:00Z',
            sent_to_email: 'admin@example.ru',
            _dev_plain_code: '999999',
        });

        const wrapper = factory({ modelValue: true, tenant: sampleTenant });
        await wrapper.find('[data-testid="reason-input"] textarea').setValue('A'.repeat(35));
        await wrapper.find('[data-testid="submit-init-btn"]').trigger('click');
        await flushPromises();

        await wrapper.find('[data-testid="code-input"] input').setValue('12345'); // 5 цифр
        await wrapper.find('[data-testid="submit-verify-btn"]').trigger('click');
        await flushPromises();

        expect(adminApi.impersonationVerify).not.toHaveBeenCalled();
        expect(wrapper.text()).toContain('Введите 6 цифр');
    });

    it('end success → step done → кнопка «Закрыть»', async () => {
        vi.mocked(adminApi.impersonationInit).mockResolvedValue({
            token_id: 42,
            expires_at: '2026-05-09T12:00:00Z',
            sent_to_email: 'admin@example.ru',
            _dev_plain_code: '777777',
        });
        vi.mocked(adminApi.impersonationVerify).mockResolvedValue({
            token_id: 42,
            tenant_id: sampleTenant.id,
            used_at: '2026-05-09T11:30:00Z',
            message: 'OK',
            machine_token: 'test-machine-token-xyz789',
            expires_at: '2026-05-09T12:30:00Z',
        });
        vi.mocked(adminApi.impersonationEnd).mockResolvedValue({
            token_id: 42,
            session_ended_at: '2026-05-09T12:00:00Z',
            message: 'Завершён',
        });

        const wrapper = factory({ modelValue: true, tenant: sampleTenant });
        await wrapper.find('[data-testid="reason-input"] textarea').setValue('A'.repeat(35));
        await wrapper.find('[data-testid="submit-init-btn"]').trigger('click');
        await flushPromises();
        await wrapper.find('[data-testid="code-input"] input').setValue('777777');
        await wrapper.find('[data-testid="submit-verify-btn"]').trigger('click');
        await flushPromises();
        await wrapper.find('[data-testid="submit-end-btn"]').trigger('click');
        await flushPromises();

        expect(adminApi.impersonationEnd).toHaveBeenCalledWith(42);
        expect(wrapper.text()).toContain('Сессия impersonation завершена');
        expect(wrapper.find('[data-testid="close-btn"]').exists()).toBe(true);
    });

    it('Отмена на step reason эмитит update:modelValue=false', async () => {
        const wrapper = factory({ modelValue: true, tenant: sampleTenant });
        await wrapper.find('[data-testid="cancel-btn"]').trigger('click');
        const events = wrapper.emitted('update:modelValue');
        expect(events).toBeDefined();
        expect(events?.[0]).toEqual([false]);
    });

    it('I4 — dev-code-banner НЕ рендерится когда import.meta.env.DEV=false (prod)', async () => {
        vi.stubEnv('DEV', false);

        vi.mocked(adminApi.impersonationInit).mockResolvedValue({
            token_id: 42,
            expires_at: '2026-05-09T12:00:00Z',
            sent_to_email: 'admin@okna-moscow.ru',
            _dev_plain_code: '123456',
        });

        const wrapper = factory({ modelValue: true, tenant: sampleTenant });
        await wrapper
            .find('[data-testid="reason-input"] textarea')
            .setValue('Тикет SUP-12453: клиент сообщил, что в карточке сделки не сохраняется коммент.');
        await wrapper.find('[data-testid="submit-init-btn"]').trigger('click');
        await flushPromises();

        // step verify достигнут — devPlainCode='123456' есть, но DEV=false → баннер не рендерится
        expect(wrapper.text()).toContain('Код отправлен на email клиента');
        expect(wrapper.find('[data-testid="dev-code-banner"]').exists()).toBe(false);
    });
});
