import {
    mount,
    type VueWrapper,
} from '@vue/test-utils';
import { router } from '@inertiajs/vue3';
import {
    nextTick,
} from 'vue';
import {
    afterEach,
    beforeEach,
    describe,
    expect,
    it,
    vi,
} from 'vitest';

vi.mock('@inertiajs/vue3', async (importOriginal) => {
    const actual = await importOriginal<typeof import('@inertiajs/vue3')>();

    return {
        ...actual,
        Head: {
            name: 'InertiaHeadStub',
            props: { title: String },
            template: '<span data-head />',
        },
    };
});

import BusinessFormPage from '@/components/business/BusinessFormPage.vue';
import type { BusinessFormPagePayload } from '@/types/business';

const formPage = (method: BusinessFormPagePayload['method'] = 'post'): BusinessFormPagePayload => ({
    mode: method === 'post' ? 'create' : 'edit',
    title: method === 'post' ? 'New fund' : 'Edit fund',
    description: 'Keep the fund identity and reporting profile up to date.',
    icon: 'folder',
    action: method === 'post' ? '/funds' : '/funds/fund-1',
    method,
    submitLabel: method === 'post' ? 'Create fund' : 'Save fund',
    cancelLink: { href: '/funds', inertia: true },
    schema: {
        schema: {
            values: {
                name: 'Demo Alpha Growth IV',
                currency: '',
                fund_id: '',
                notes: '',
            },
            fields: [],
        },
        sections: [
            {
                id: 'identity',
                title: 'Identity',
                description: 'Core identifiers used across reporting.',
                fields: [
                    { name: 'name', type: 'text', label: 'Name', required: true },
                    {
                        name: 'fund_id',
                        type: 'lookup',
                        label: 'Reference fund',
                        lookup: { url: '/lookups/funds' },
                    },
                    {
                        name: 'currency',
                        type: 'dropdown',
                        label: 'Currency',
                        options: [
                            { value: 'EUR', label: 'EUR' },
                            { value: 'USD', label: 'USD' },
                        ],
                    },
                ],
            },
            {
                id: 'reporting',
                title: 'Reporting',
                fields: [
                    { name: 'notes', type: 'textarea', label: 'Notes' },
                ],
            },
        ],
        aliases: [{ name: 'Alpha IV', comment: 'Short name' }],
    },
});

let wrapper: VueWrapper | null = null;

beforeEach(() => {
    document.body.innerHTML = '';
});

afterEach(() => {
    wrapper?.unmount();
    wrapper = null;
    document.body.innerHTML = '';
    vi.restoreAllMocks();
});

describe('BusinessFormPage', () => {
    it('submits only the transformed dynamic values and aliases through Inertia', async () => {
        const post = vi.spyOn(router, 'post').mockImplementation(() => undefined);

        wrapper = mount(BusinessFormPage, {
            attachTo: document.body,
            props: { formPage: formPage() },
        });

        await wrapper.get<HTMLInputElement>('input[name="name"]').setValue('Demo Alpha Growth V');
        await wrapper.get<HTMLTextAreaElement>('textarea[name="notes"]').setValue('First close expected in Q4.');
        await wrapper.get<HTMLInputElement>('input[name="alias_names[]"]').setValue('Alpha V');
        await wrapper.get('form').trigger('submit');

        expect(post).toHaveBeenCalledTimes(1);
        const [url, payload, options] = post.mock.calls[0] ?? [];

        expect(url).toBe('/funds');
        expect(payload).toEqual({
            name: 'Demo Alpha Growth V',
            currency: '',
            fund_id: '',
            notes: 'First close expected in Q4.',
            alias_names: ['Alpha V'],
            alias_comments: ['Short name'],
        });
        expect(payload).not.toHaveProperty('_dynamic');
        expect(options).toMatchObject({ preserveScroll: true });
    });

    it.each([
        ['put', 'put'],
        ['patch', 'patch'],
    ] as const)('uses the %s method for edit payloads', async (method, routerMethod) => {
        const request = vi.spyOn(router, routerMethod).mockImplementation(() => undefined);

        wrapper = mount(BusinessFormPage, {
            props: { formPage: formPage(method) },
        });
        await wrapper.get('form').trigger('submit');

        expect(request).toHaveBeenCalledWith(
            '/funds/fund-1',
            expect.objectContaining({ name: 'Demo Alpha Growth IV' }),
            expect.objectContaining({ preserveScroll: true }),
        );
    });

    it('method-spoofs multipart edits so Laravel receives dynamic image files', async () => {
        const page = formPage('put');
        page.schema.schema.values.logo = '';
        page.schema.sections[0]?.fields.push({
            name: 'logo',
            type: 'file',
            label: 'Logo',
            accept: 'image/*',
        });
        const post = vi.spyOn(router, 'post').mockImplementation(() => undefined);
        const put = vi.spyOn(router, 'put').mockImplementation(() => undefined);

        wrapper = mount(BusinessFormPage, {
            props: { formPage: page },
        });
        const file = new File(['logo'], 'logo.png', { type: 'image/png' });
        const input = wrapper.get<HTMLInputElement>('input[name="logo"]');

        Object.defineProperty(input.element, 'files', {
            configurable: true,
            value: [file],
        });
        await input.trigger('change');
        await wrapper.get('form').trigger('submit');

        expect(put).not.toHaveBeenCalled();
        expect(post).toHaveBeenCalledWith(
            '/funds/fund-1',
            expect.objectContaining({
                _method: 'put',
                logo: file,
            }),
            expect.objectContaining({
                preserveScroll: true,
                forceFormData: true,
            }),
        );
    });

    it('does not submit an unchanged dynamic image URL on edit', async () => {
        const page = formPage('put');
        page.schema.schema.values.logo = '';
        page.schema.sections[0]?.fields.push({
            name: 'logo',
            type: 'file',
            label: 'Logo',
            accept: 'image/*',
            previewUrl: '/storage/funds/existing-logo.png',
        });
        const put = vi.spyOn(router, 'put').mockImplementation(() => undefined);

        wrapper = mount(BusinessFormPage, {
            props: { formPage: page },
        });
        expect(wrapper.get<HTMLImageElement>('.record-form-image-preview img').attributes('src'))
            .toBe('/storage/funds/existing-logo.png');

        await wrapper.get('form').trigger('submit');

        expect(put).toHaveBeenCalledWith(
            '/funds/fund-1',
            expect.not.objectContaining({ logo: expect.anything() }),
            expect.objectContaining({ preserveScroll: true }),
        );
    });

    it('renders Laravel errors as text and focuses the first invalid field', async () => {
        vi.spyOn(router, 'post').mockImplementation((_url, _data, options) => {
            options?.onError?.({
                name: '<strong>The name is required.</strong>',
                currency: 'The currency is required.',
            });
        });

        wrapper = mount(BusinessFormPage, {
            attachTo: document.body,
            props: { formPage: formPage() },
        });
        await wrapper.get('form').trigger('submit');
        await nextTick();
        await nextTick();

        const name = wrapper.get<HTMLInputElement>('input[name="name"]');

        expect(wrapper.text()).toContain('<strong>The name is required.</strong>');
        expect(wrapper.find('.ui-form-field__error strong').exists()).toBe(false);
        expect(name.attributes('aria-invalid')).toBe('true');
        expect(document.activeElement).toBe(name.element);
    });

    it('focuses the alias field when Laravel reports a root alias conflict', async () => {
        vi.spyOn(router, 'post').mockImplementation((_url, _data, options) => {
            options?.onError?.({
                alias: 'This alias is already linked to another record.',
            });
        });

        wrapper = mount(BusinessFormPage, {
            attachTo: document.body,
            props: { formPage: formPage() },
        });
        await wrapper.get('form').trigger('submit');
        await nextTick();
        await nextTick();

        const alias = wrapper.get<HTMLInputElement>('input[name="alias_names[]"]');

        expect(alias.attributes('aria-invalid')).toBe('true');
        expect(document.activeElement).toBe(alias.element);
    });

    it('prefills an empty currency from selected fund metadata without overwriting a choice', async () => {
        const page = formPage();
        page.schema.schema.values.commitment_amount = '1250000';
        page.schema.sections[1]?.fields.push({
            name: 'commitment_amount',
            type: 'money',
            label: 'Commitment',
        });
        wrapper = mount(BusinessFormPage, {
            props: { formPage: page },
        });
        const lookup = wrapper.getComponent({ name: 'RecordAutocomplete' });

        lookup.vm.$emit('selected', {
            value: 'fund-1',
            label: 'Demo Alpha Growth IV',
            data: { currency: 'usd' },
        });
        await nextTick();

        const currency = wrapper.findAllComponents({ name: 'KviewDropdown' })
            .find((component) => component.props('name') === 'currency');
        const amount = wrapper.findAllComponents({ name: 'KviewMoneyInput' })
            .find((component) => component.props('name') === 'commitment_amount');

        expect(currency?.props('modelValue')).toBe('USD');
        expect(amount?.props('currency')).toBe('USD');

        currency?.vm.$emit('update:modelValue', 'EUR');
        await nextTick();
        lookup.vm.$emit('selected', {
            value: 'fund-2',
            label: 'Demo Beta Credit Opportunities',
            data: { currency: 'GBP' },
        });
        await nextTick();

        expect(currency?.props('modelValue')).toBe('EUR');
        expect(amount?.props('currency')).toBe('EUR');
    });
});
