import { flushPromises, mount, type VueWrapper } from '@vue/test-utils';
import {
    defineComponent,
    h,
    nextTick,
    ref,
} from 'vue';
import {
    afterEach,
    beforeEach,
    describe,
    expect,
    it,
    vi,
} from 'vitest';
import { KviewToastProvider } from '@kview/ui-core/vue';

const routerReload = vi.hoisted(() => vi.fn());

vi.mock('@inertiajs/vue3', () => ({
    router: {
        reload: routerReload,
    },
}));

import ApplicationSettingsDialog from '@/components/application/ApplicationSettingsDialog.vue';
import type { SettingsPayload, SettingsTabId } from '@/types/settings';

interface SettingsDialogHandle {
    close: (restoreFocus?: boolean) => void;
    show: (tab?: SettingsTabId, trigger?: HTMLElement | null) => void;
}

const payload = (activeTab: SettingsTabId = 'preferences'): SettingsPayload => ({
    activeTab,
    tabs: [
        { id: 'user', label: 'Account', icon: 'user', group: 'Personal' },
        { id: 'preferences', label: 'Preferences', icon: 'sliders', group: 'Personal' },
        { id: 'workspace', label: 'Workspace', icon: 'building', group: 'Workspace' },
        { id: 'integrations', label: 'Integrations', icon: 'spreadsheet', group: 'Workspace' },
        { id: 'system', label: 'System', icon: 'status', group: 'Operations' },
    ],
    returnUrl: '/',
    dataUrl: '/settings',
    account: {
        name: 'Ada Lovelace',
        email: 'ada@example.com',
        avatarUrl: '/settings/avatar?v=1',
        hasAvatar: true,
        profileUrl: '/settings/profile',
        passwordUrl: '/settings/password',
    },
    preferences: {
        updateUrl: '/preferences',
        values: {
            targetCurrency: 'EUR',
            itemsPerPage: '25',
            amountScaling: 'unit',
            decimalSeparator: '.',
            dateFormatView: 'Y-m-d',
            dateFormatList: 'Y-m-d',
        },
        options: {
            targetCurrencies: [{ value: 'EUR', label: 'EUR' }],
            itemsPerPage: [{ value: '25', label: '25' }],
            amountScalings: [{ value: 'unit', label: 'Unit' }],
            decimalSeparators: [{ value: '.', label: '.' }],
            dateFormats: [{ value: 'Y-m-d', label: 'Y-m-d', meta: '2026-07-18' }],
        },
    },
    workspace: {
        label: 'kView INVESTOR by kView',
        signedInAs: 'ada@example.com',
    },
    integrations: {
        excelTokenUrl: '/api/protocol-token/create',
    },
    system: {
        application: [{ label: 'Application', value: 'kView INVESTOR' }],
        database: [{ label: 'PostgreSQL', value: '16' }],
    },
});

const jsonResponse = (body: unknown, status = 200): Response => ({
    ok: status >= 200 && status < 300,
    status,
    json: async () => body,
}) as Response;

const flush = async (): Promise<void> => {
    await flushPromises();
    await nextTick();
    await new Promise((resolve) => setTimeout(resolve, 0));
    await nextTick();
};

const wrappers: VueWrapper[] = [];
let dialogHandle: SettingsDialogHandle | null = null;

const mountDialog = (): VueWrapper => {
    const Harness = defineComponent({
        name: 'ApplicationSettingsHarness',
        setup() {
            const settings = ref<SettingsDialogHandle | null>(null);

            return () => h(KviewToastProvider, null, {
                default: () => h(ApplicationSettingsDialog, {
                    ref: (value) => {
                        settings.value = value as SettingsDialogHandle | null;
                        dialogHandle = settings.value;
                    },
                    dataUrl: '/settings',
                    excelPending: false,
                }),
            });
        },
    });
    const wrapper = mount(Harness, { attachTo: document.body });

    wrappers.push(wrapper);

    return wrapper;
};

beforeEach(() => {
    document.body.innerHTML = '';
    dialogHandle = null;
    routerReload.mockReset();

    class ResizeObserverStub {
        observe(): void {}

        unobserve(): void {}

        disconnect(): void {}
    }

    Object.defineProperty(window, 'ResizeObserver', {
        configurable: true,
        value: ResizeObserverStub,
    });
    Object.defineProperty(window, 'matchMedia', {
        configurable: true,
        value: vi.fn(() => ({
            matches: false,
            media: '(max-width: 900px)',
            addEventListener: vi.fn(),
            removeEventListener: vi.fn(),
        })),
    });

    if (!window.PointerEvent) {
        Object.defineProperty(window, 'PointerEvent', {
            configurable: true,
            value: MouseEvent,
        });
    }

    Element.prototype.scrollIntoView = vi.fn();
    HTMLElement.prototype.scrollTo = vi.fn();
});

afterEach(() => {
    wrappers.splice(0).forEach((wrapper) => wrapper.unmount());
    document.body.innerHTML = '';
    vi.unstubAllGlobals();
    vi.restoreAllMocks();
});

describe('ApplicationSettingsDialog', () => {
    it('loads the Vue settings surface with correct ARIA, focus and tab switching', async () => {
        const fetchMock = vi.fn().mockResolvedValue(jsonResponse(payload()));
        vi.stubGlobal('fetch', fetchMock);
        const trigger = document.createElement('button');
        trigger.type = 'button';
        trigger.textContent = 'Open Settings';
        document.body.append(trigger);
        trigger.focus();
        const wrapper = mountDialog();

        dialogHandle?.show('preferences', trigger);
        await flush();

        expect(fetchMock).toHaveBeenCalledWith(
            expect.stringMatching(/\/settings\?tab=preferences$/),
            expect.objectContaining({
                credentials: 'same-origin',
                headers: expect.objectContaining({
                    Accept: 'application/json',
                    'X-KView-Investor-Settings-Modal': 'true',
                }),
            }),
        );

        const dialog = document.body.querySelector<HTMLElement>('#application-settings-dialog');
        const preferences = document.body.querySelector<HTMLButtonElement>(
            '[data-settings-vue-tab="preferences"]',
        );

        expect(dialog?.getAttribute('role')).toBe('dialog');
        expect(dialog?.getAttribute('aria-modal')).toBe('true');
        expect(preferences?.getAttribute('aria-selected')).toBe('true');
        expect(document.activeElement).toBe(preferences);

        document.body.querySelector<HTMLButtonElement>(
            '[data-settings-vue-tab="integrations"]',
        )?.click();
        await nextTick();
        document.body.querySelector<HTMLButtonElement>('.settings-panel .btn-primary')?.click();
        await nextTick();

        expect(wrapper.findComponent(ApplicationSettingsDialog).emitted('excel')).toHaveLength(1);

        document.dispatchEvent(new KeyboardEvent('keydown', {
            key: 'Escape',
            bubbles: true,
            cancelable: true,
        }));
        await flush();

        expect(document.body.querySelector('#application-settings-dialog')).toBeNull();
        expect(document.activeElement).toBe(trigger);
    });

    it('keeps validation inline, focuses the first invalid field and refreshes shared props on success', async () => {
        const fetchMock = vi.fn()
            .mockResolvedValueOnce(jsonResponse(payload()))
            .mockResolvedValueOnce(jsonResponse({
                message: 'The given data was invalid.',
                errors: {
                    target_currency: ['The dashboard currency is required.'],
                },
            }, 422))
            .mockResolvedValueOnce(jsonResponse({
                redirect: '/settings?tab=preferences',
                message: 'Settings updated successfully.',
            }))
            .mockResolvedValueOnce(jsonResponse(payload()));
        vi.stubGlobal('fetch', fetchMock);
        mountDialog();

        dialogHandle?.show('preferences');
        await flush();

        document.body.querySelector<HTMLButtonElement>(
            '.settings-form button[type="submit"]',
        )?.click();
        await flush();

        expect(document.body.textContent).toContain('The dashboard currency is required.');
        expect(document.activeElement?.id).toBe('target_currency');

        const firstMutation = fetchMock.mock.calls[1];
        const firstOptions = firstMutation?.[1] as RequestInit;
        const firstData = firstOptions.body as FormData;

        expect(firstMutation?.[0]).toBe('/preferences');
        expect(firstOptions.method).toBe('POST');
        expect((firstOptions.headers as Headers).get('X-KView-Investor-Settings-Modal')).toBe('true');
        expect(firstData.get('_method')).toBe('PUT');
        expect(firstData.get('target_currency')).toBe('EUR');

        document.body.querySelector<HTMLButtonElement>(
            '.settings-form button[type="submit"]',
        )?.click();
        await flush();

        expect(document.body.textContent).not.toContain('The dashboard currency is required.');
        expect(document.body.querySelector('.ui-toast--success')?.textContent).toContain(
            'Settings updated successfully.',
        );
        expect(routerReload).toHaveBeenCalledWith({
            only: ['auth', 'preferences'],
        });
    });
});
