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

// Мокаем api/admin до import'а view (loadSettings вызывается на mount).
vi.mock('../../resources/js/api/admin', () => ({
    listSystemSettings: vi.fn(() => Promise.resolve([])), // default — пустой массив
    updateSystemSetting: vi.fn(),
}));
vi.mock('../../resources/js/api/client', () => ({
    extractErrorMessage: vi.fn((_e, fb?: string) => fb ?? 'err'),
    extractValidationErrors: vi.fn(() => null),
    apiClient: {},
    ensureCsrfCookie: vi.fn(),
}));

import * as adminApi from '../../resources/js/api/admin';
import AdminSystemView from '../../resources/js/views/admin/AdminSystemView.vue';
import { ADMIN_SYSTEM_SETTINGS } from '../../resources/js/composables/mockAdmin';

const mountView = async () => {
    const router = createRouter({
        history: createMemoryHistory(),
        routes: [{ path: '/admin/system', component: AdminSystemView }],
    });
    await router.push('/admin/system');
    await router.isReady();
    const wrapper = mount(AdminSystemView, {
        global: {
            plugins: [createVuetify(), router],
            stubs: { SystemSettingEditDialog: true },
        },
    });
    await flushPromises();
    return wrapper;
};

describe('AdminSystemView.vue', () => {
    beforeEach(() => {
        vi.clearAllMocks();
        // По умолчанию backend возвращает те же 7 mock-настроек — view replace
        // settingsState на ту же форму, тесты структуры остаются валидными.
        vi.mocked(adminApi.listSystemSettings).mockResolvedValue(
            ADMIN_SYSTEM_SETTINGS.map((s) => ({
                key: s.key,
                value: s.value,
                type: s.type,
                description: s.description,
                updated_at: s.updated_at,
                updated_by: null,
            })),
        );
    });

    it('монтируется и содержит заголовок «Система»', async () => {
        const wrapper = await mountView();
        expect(wrapper.text()).toContain('Система');
    });

    it('показывает информационный alert про edit-flow + audit-log', async () => {
        const wrapper = await mountView();
        const text = wrapper.text();
        expect(text).toContain('Edit-flow');
        expect(text).toContain('saas_admin_audit_log');
    });

    it('перечисляет ключевые system_settings (rate-limit, retention, login_max_attempts)', async () => {
        const wrapper = await mountView();
        const text = wrapper.text();
        expect(text).toContain('webhook_rate_limit_rps');
        expect(text).toContain('login_max_attempts');
        expect(text).toContain('password_min_length');
        expect(text).toContain('webhook_log_retention_days');
        expect(text).toContain('maintenance_mode');
    });

    it('содержит type-chip для каждой строки (int/string/bool/json)', async () => {
        const wrapper = await mountView();
        const text = wrapper.text();
        expect(text).toContain('int');
        expect(text).toContain('bool');
    });

    it('число строк settings = 7 (mock count)', async () => {
        const wrapper = await mountView();
        const rows = wrapper.findAll('[data-testid="setting-row"]');
        expect(rows.length).toBe(7);
    });

    it('каждая строка имеет «Изменить» кнопку с уникальным data-testid', async () => {
        const wrapper = await mountView();
        const editBtns = wrapper.findAll('[data-testid$="-btn"]').filter((b) => b.text().includes('Изменить'));
        expect(editBtns.length).toBe(7);
    });

    it('click на Изменить открывает edit-dialog с правильным setting', async () => {
        const wrapper = await mountView();
        const vm = wrapper.vm as unknown as {
            editOpen: boolean;
            editSetting: { key: string } | null;
        };
        expect(vm.editOpen).toBe(false);
        await wrapper.find('[data-testid="edit-login_max_attempts-btn"]').trigger('click');
        await wrapper.vm.$nextTick();
        expect(vm.editOpen).toBe(true);
        expect(vm.editSetting?.key).toBe('login_max_attempts');
    });

    it('на mount вызывает listSystemSettings (реальный fetch с backend)', async () => {
        await mountView();
        await flushPromises();
        expect(adminApi.listSystemSettings).toHaveBeenCalledTimes(1);
    });

    it('кнопка Обновить триггерит loadSettings', async () => {
        const wrapper = await mountView();
        await flushPromises();
        vi.mocked(adminApi.listSystemSettings).mockClear();
        await wrapper.find('[data-testid="reload-btn"]').trigger('click');
        await flushPromises();
        expect(adminApi.listSystemSettings).toHaveBeenCalledTimes(1);
    });

    it('при сетевой ошибке показывает warning-banner + settingsState пустой', async () => {
        vi.mocked(adminApi.listSystemSettings).mockRejectedValueOnce(new Error('Network down'));
        const wrapper = await mountView();
        await flushPromises();
        const banner = wrapper.find('[data-testid="fetch-error-alert"]');
        expect(banner.exists()).toBe(true);
        // Пустой при ошибке — без mock-fallback
        const rows = wrapper.findAll('[data-testid="setting-row"]');
        expect(rows.length).toBe(0);
    });

    it('onSettingUpdated обновляет value и updated_at в settingsState', async () => {
        const wrapper = await mountView();
        const vm = wrapper.vm as unknown as {
            settingsState: Array<{ key: string; value: string; updated_at: string }>;
            onSettingUpdated: (p: { key: string; value: string; updated_at: string }) => void;
        };
        vm.onSettingUpdated({
            key: 'login_max_attempts',
            value: '7',
            updated_at: '2026-05-09T11:30:00',
        });
        await wrapper.vm.$nextTick();
        const row = vm.settingsState.find((s) => s.key === 'login_max_attempts');
        expect(row?.value).toBe('7');
        expect(row?.updated_at).toBe('2026-05-09T11:30:00');
    });

    it('G9: edit-кнопки имеют aria-label с ключом настройки', async () => {
        const wrapper = await mountView();
        const editBtns = wrapper.findAll('[data-testid^="edit-"]');
        expect(editBtns.length).toBeGreaterThan(0);
        for (const btn of editBtns) {
            const label = btn.attributes('aria-label') ?? '';
            expect(label).toMatch(/^Изменить настройку .+/);
        }
    });
});
