import React from 'react';
import {act} from 'react-dom/test-utils';
import TestDriver from '@airtable/blocks-testing';
import {
    render,
    screen,
    waitFor,
    getByRole,
    getAllByRole,
    getByText,
    getNodeText,
} from '@testing-library/react';
import recordListFixture from './fixtures/simple_record_list';
import TodoApp from '../frontend/todo-app';
import userEvent from '@testing-library/user-event';

async function openAsync(table, view, field) {
    act(() => {
        const input = screen.getByLabelText('Table');
        const option = screen.getByText(table);

        userEvent.selectOptions(input, [option]);
    });

    act(() => {
        const input = screen.getByLabelText('View');
        const option = screen.getByText(view);

        userEvent.selectOptions(input, [option]);
    });

    act(() => {
        const input = screen.getByLabelText('Field');
        const option = screen.getByText(field);

        userEvent.selectOptions(input, [option]);
    });

    return waitFor(() => screen.getByRole('button', {name: 'Add'}));
}

function getItems() {
    return screen.queryAllByRole('checkbox').map(checkbox => {
        const container = checkbox.parentNode;
        const deleteButton = getByRole(container, 'button');
        const link = getByText(container, container.textContent.trim());

        return {container, checkbox, deleteButton, link};
    });
}

function readItems() {
    return getItems().map(item => ({
        checked: item.checkbox.checked,
        text: item.container.textContent.trim(),
    }));
}

describe('TodoApp', () => {
    let mutations;
    let addMutation = mutation => mutations.push(mutation);
    let testDriver;

    beforeEach(() => {
        testDriver = new TestDriver(recordListFixture);
        mutations = [];
        testDriver.watch('mutation', addMutation);

        render(
            <testDriver.Container>
                <TodoApp />
            </testDriver.Container>,
        );
    });

    afterEach(() => {
        testDriver.unwatch('mutations', addMutation);
    });

    it('renders a list of records (user with "write" permissions)', async () => {
        await openAsync('Groceries', 'Grid view', 'Purchased');

        const items = readItems();

        expect(items.length).toBe(3);

        expect(items).toEqual([
            {checked: false, text: 'carrots'},
            {checked: true, text: 'baby carrots'},
            {checked: false, text: 'elderly carrots'},
        ]);
    });

    // This test cannot be fully expressed using the capabilities currently
    // available in the SDK.
    it('renders a list of records (user without "write" permissions)', async () => {
        testDriver.simulatePermissionCheck(mutation => {
            return mutation.type === 'setMultipleGlobalConfigPaths';
        });

        await openAsync('Groceries', 'Grid view', 'Purchased');

        expect(screen.getByRole('button', {name: 'Add'}).disabled).toBe(true);

        const items = getItems().map(item => ({
            checked: item.checkbox.checked,
            text: item.container.textContent.trim(),
            checkboxDisabled: item.checkbox.disabled,
            deleteButtonDisabled: item.deleteButton.disabled,
        }));

        expect(items.length).toBe(3);

        expect(items).toEqual([
            {checked: false, text: 'carrots', checkboxDisabled: true, deleteButtonDisabled: true},
            {
                checked: true,
                text: 'baby carrots',
                checkboxDisabled: true,
                deleteButtonDisabled: true,
            },
            {
                checked: false,
                text: 'elderly carrots',
                checkboxDisabled: true,
                deleteButtonDisabled: true,
            },
        ]);
    });

    it('gracefully handles the deletion of fields', async () => {
        await openAsync('Groceries', 'Grid view', 'Purchased');

        await act(() => testDriver.deleteFieldAsync('tblTable1', 'fldPurchased'));

        const items = readItems();

        expect(items).toEqual([]);
    });

    it('gracefully handles the deletion of tables', async () => {
        await openAsync('Groceries', 'Grid view', 'Purchased');

        act(() => {
            testDriver.deleteTable('tblTable1');
        });

        const items = readItems();

        expect(items).toEqual([]);

        const options = getAllByRole(screen.getByLabelText('Table'), 'option');

        expect(options.map(getNodeText)).toEqual(['Pick a table...', 'Porcelain dolls']);

        expect(options[0].selected).toBe(true);
        expect(screen.queryByLabelText('View')).toBe(null);
        expect(screen.queryByLabelText('Field')).toBe(null);
    });

    it('gracefully handles the deletion of views', async () => {
        await openAsync('Groceries', 'Grid view', 'Purchased');

        await act(() => testDriver.deleteViewAsync('tblTable1', 'viwGridView'));

        const items = readItems();

        expect(items).toEqual([]);

        const tableOptions = getAllByRole(screen.getByLabelText('Table'), 'option');

        expect(tableOptions.map(getNodeText)).toEqual([
            'Pick a table...',
            'Groceries',
            'Porcelain dolls',
        ]);

        expect(tableOptions[1].selected).toBe(true);

        const viewOptions = getAllByRole(screen.getByLabelText('View'), 'option');
        expect(viewOptions.map(getNodeText)).toEqual(['Pick a view...', 'Another grid view']);
        expect(viewOptions[0].selected).toBe(true);

        const fieldOptions = getAllByRole(screen.getByLabelText('Field'), 'option');
        expect(fieldOptions.map(getNodeText)).toEqual([
            "Pick a 'done' field...",
            'Name',
            'Purchased',
        ]);
        expect(fieldOptions[2].selected).toBe(true);
    });

    it('allows records to be created without a name', async () => {
        await openAsync('Groceries', 'Grid view', 'Purchased');

        const initialCount = readItems().length;

        userEvent.click(screen.getByRole('button', {name: 'Add'}));

        const items = readItems();

        expect(items.length).toBe(initialCount + 1);
        expect(items.pop()).toEqual({
            checked: false,
            text: 'Unnamed record',
        });

        await waitFor(() => expect(mutations.length).not.toBe(0));
        expect(mutations).toEqual(
            expect.arrayContaining([
                {
                    type: 'createMultipleRecords',
                    tableId: 'tblTable1',
                    records: [
                        {
                            id: expect.anything(),
                            cellValuesByFieldId: {
                                fldName: '',
                            },
                        },
                    ],
                },
            ]),
        );
    });

    it('allows multiple records to be created with a name', async () => {
        await openAsync('Groceries', 'Grid view', 'Purchased');

        const initialCount = readItems().length;

        userEvent.type(screen.getByRole('textbox'), 'brash teenaged carrots');
        userEvent.click(screen.getByRole('button', {name: 'Add'}));

        let items = readItems();

        expect(items.length).toBe(initialCount + 1);
        expect(items.pop()).toEqual({
            checked: false,
            text: 'brash teenaged carrots',
        });

        await waitFor(() => expect(mutations.length).not.toBe(0));
        expect(mutations).toEqual(
            expect.arrayContaining([
                {
                    type: 'createMultipleRecords',
                    tableId: 'tblTable1',
                    records: [
                        {
                            id: expect.anything(),
                            cellValuesByFieldId: {
                                fldName: 'brash teenaged carrots',
                            },
                        },
                    ],
                },
            ]),
        );

        mutations.length = 0;

        userEvent.type(screen.getByRole('textbox'), 'parsnips');
        userEvent.click(screen.getByRole('button', {name: 'Add'}));

        items = readItems();

        expect(items.length).toBe(initialCount + 2);

        expect(items.pop()).toEqual({
            checked: false,
            text: 'parsnips',
        });

        await waitFor(() => expect(mutations.length).not.toBe(0));
        expect(mutations).toEqual(
            expect.arrayContaining([
                {
                    type: 'createMultipleRecords',
                    tableId: 'tblTable1',
                    records: [
                        {
                            id: expect.anything(),
                            cellValuesByFieldId: {
                                fldName: 'parsnips',
                            },
                        },
                    ],
                },
            ]),
        );
    });

    it('allows records to be destroyed', async () => {
        await openAsync('Groceries', 'Grid view', 'Purchased');

        userEvent.click(getItems()[1].deleteButton);

        const items = readItems();

        expect(items).toEqual([
            {checked: false, text: 'carrots'},
            {checked: false, text: 'elderly carrots'},
        ]);

        await waitFor(() => expect(mutations.length).not.toBe(0));
        expect(mutations).toEqual(
            expect.arrayContaining([
                {
                    type: 'deleteMultipleRecords',
                    tableId: 'tblTable1',
                    recordIds: ['recb'],
                },
            ]),
        );
    });

    it('allows records to be marked as "complete"', async () => {
        await openAsync('Groceries', 'Grid view', 'Purchased');

        userEvent.click(getItems()[0].checkbox);

        const items = readItems();

        expect(items[0]).toEqual({checked: true, text: 'carrots'});

        await waitFor(() => expect(mutations.length).not.toBe(0));
        expect(mutations).toEqual(
            expect.arrayContaining([
                {
                    type: 'setMultipleRecordsCellValues',
                    tableId: 'tblTable1',
                    records: [
                        {
                            id: 'reca',
                            cellValuesByFieldId: {
                                fldPurchased: true,
                            },
                        },
                    ],
                },
            ]),
        );
    });

    it('allows records to be marked as "incomplete"', async () => {
        await openAsync('Groceries', 'Grid view', 'Purchased');

        userEvent.click(getItems()[1].checkbox);

        const items = readItems();

        expect(items[1]).toEqual({checked: false, text: 'baby carrots'});

        await waitFor(() => expect(mutations.length).not.toBe(0));
        expect(mutations).toEqual(
            expect.arrayContaining([
                {
                    type: 'setMultipleRecordsCellValues',
                    tableId: 'tblTable1',
                    records: [
                        {
                            id: 'recb',
                            cellValuesByFieldId: {
                                fldPurchased: false,
                            },
                        },
                    ],
                },
            ]),
        );
    });

    it('expands records upon click', async () => {
        await openAsync('Groceries', 'Grid view', 'Purchased');
        const recordIds = [];
        testDriver.watch('expandRecord', ({recordId}) => recordIds.push(recordId));

        userEvent.click(getItems()[0].link);
        await waitFor(() => expect(recordIds.length).not.toBe(0));

        expect(recordIds).toEqual(['reca']);
    });
});