import { Attachment, } from '@taylordb/shared';
import { createQueryBuilder, sum } from '../index.js';
import { SocketConnection } from '../socket-connection.js';
export const CustomersStatusOptions = ['Pending', 'Active'];
jest.mock('socket.io-client', () => {
    const mockSocket = {
        on: jest.fn(),
        emit: jest.fn(),
        once: jest.fn(),
    };
    return {
        io: jest.fn(() => mockSocket),
    };
});
describe('QueryBuilder', () => {
    let mockRawRequest;
    beforeEach(() => {
        mockRawRequest = jest.fn().mockResolvedValue({ execute: [] });
        SocketConnection.prototype.rawRequest = mockRawRequest;
    });
    // @ts-ignore
    const qb = createQueryBuilder({
        baseUrl: 'http://localhost',
        baseId: 'test-base-id',
        apiKey: 'test',
    });
    it('should compile a select query and execute', async () => {
        await qb
            .selectFrom('customers')
            .select(['firstName', 'lastName'])
            .where('firstName', '=', 'John')
            .orderBy('lastName', 'asc')
            .paginate(1, 10)
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'select',
            tableName: 'customers',
            fields: ['firstName', 'lastName'],
            filtersSet: {
                conjunction: 'and',
                filtersSet: [
                    {
                        field: 'firstName',
                        operator: '=',
                        value: 'John',
                    },
                ],
            },
            sorting: [{ field: 'lastName', direction: 'asc' }],
            pagination: { offset: 0, limit: 10 },
        });
    });
    it('should not include pagination property if not used', async () => {
        await qb
            .selectFrom('customers')
            .select(['firstName'])
            .where('firstName', '=', 'John')
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).not.toHaveProperty('pagination');
    });
    it('should apply limit to a select query', async () => {
        await qb.selectFrom('customers').select(['firstName']).limit(5).execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'select',
            tableName: 'customers',
            fields: ['firstName'],
            pagination: { limit: 5 },
        });
    });
    it('should apply offset to a select query', async () => {
        await qb.selectFrom('customers').select(['firstName']).offset(10).execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'select',
            tableName: 'customers',
            fields: ['firstName'],
            pagination: { offset: 10 },
        });
    });
    it('should apply limit and offset to a select query', async () => {
        await qb
            .selectFrom('customers')
            .select(['firstName'])
            .limit(5)
            .offset(10)
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'select',
            tableName: 'customers',
            fields: ['firstName'],
            pagination: { limit: 5, offset: 10 },
        });
    });
    it('should apply pagination with a different page', async () => {
        await qb
            .selectFrom('customers')
            .select(['firstName'])
            .paginate(3, 15)
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'select',
            tableName: 'customers',
            fields: ['firstName'],
            pagination: { limit: 15, offset: 30 },
        });
    });
    it('should count the records', async () => {
        mockRawRequest.mockResolvedValue([{ total: 123 }]);
        const count = await qb
            .selectFrom('customers')
            .where('firstName', '=', 'John')
            .count();
        expect(count).toBe(123);
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'pagination',
            tableName: 'customers',
            filtersSet: {
                conjunction: 'and',
                filtersSet: [
                    {
                        field: 'firstName',
                        operator: '=',
                        value: 'John',
                    },
                ],
            },
        });
    });
    it('should compile an insert query and execute', async () => {
        await qb
            .insertInto('customers')
            .values({
            firstName: 'Jane',
            lastName: 'Doe',
        })
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'create',
            tableName: 'customers',
            createdRecords: [{ firstName: 'Jane', lastName: 'Doe' }],
        });
    });
    it('should compile an update query and execute', async () => {
        await qb
            .update('customers')
            .set({ lastName: 'Smith' })
            .where('id', '=', 1)
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'update',
            tableName: 'customers',
            values: { lastName: 'Smith' },
            filtersSet: {
                conjunction: 'and',
                filtersSet: [
                    {
                        field: 'id',
                        operator: '=',
                        value: 1,
                    },
                ],
            },
        });
    });
    it('should compile a delete query and execute', async () => {
        await qb.deleteFrom('customers').where('id', '=', 1).execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'delete',
            tableName: 'customers',
            filtersSet: {
                conjunction: 'and',
                filtersSet: [
                    {
                        field: 'id',
                        operator: '=',
                        value: 1,
                    },
                ],
            },
        });
    });
    it('should compile a batch query and execute', async () => {
        await qb
            .batch([
            qb.selectFrom('customers').select(['firstName', 'lastName']),
            qb
                .insertInto('customers')
                .values({ firstName: 'John', lastName: 'Doe' }),
        ])
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata).toHaveLength(2);
        expect(variables.metadata[0]).toMatchObject({
            type: 'select',
            tableName: 'customers',
        });
        expect(variables.metadata[1]).toMatchObject({
            type: 'create',
            tableName: 'customers',
        });
    });
    it('should compile a metrics query and flatten results', async () => {
        // Mock response with nested structure (rawRequest extracts execute key, so return array directly)
        const mockNestedResponse = [
            [
                {
                    slug: 'firstName',
                    value: 'John',
                    count: 2,
                    aggregates: {
                        id: { sum: 100 },
                    },
                    children: [
                        {
                            slug: 'lastName',
                            value: 'Doe',
                            count: 1,
                            aggregates: {
                                id: { sum: 50 },
                            },
                        },
                    ],
                },
            ],
        ];
        // Mock to return the array directly (as rawRequest would after extracting from { execute: [...] })
        mockRawRequest.mockResolvedValueOnce(mockNestedResponse);
        const result = await qb
            .aggregateFrom('customers')
            .groupBy('firstName', 'asc')
            .groupBy('lastName', 'desc')
            .metrics({
            idSum: sum('id'),
        })
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'aggregation',
            tableName: 'customers',
            groupings: [
                { field: 'firstName', direction: 'asc' },
                { field: 'lastName', direction: 'desc' },
            ],
            aggregations: {
                id: ['sum'],
            },
        });
        // Verify flattened result
        expect(result).toEqual([
            {
                firstName: 'John',
                lastName: 'Doe',
                idSum: 50,
            },
        ]);
    });
    it('should flatten metrics results without groupBy', async () => {
        // Mock response with no groupBy (flat structure)
        // rawRequest extracts execute key, so return array directly
        const mockFlatResponse = [
            [
                {
                    count: 10,
                    aggregates: {
                        id: { sum: 500 },
                    },
                },
            ],
        ];
        mockRawRequest.mockResolvedValueOnce(mockFlatResponse);
        const result = await qb
            .aggregateFrom('customers')
            .metrics({
            idSum: sum('id'),
        })
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'aggregation',
            tableName: 'customers',
            aggregations: {
                id: ['sum'],
            },
        });
        // Verify flattened result (no groupBy fields)
        expect(result).toEqual([
            {
                idSum: 500,
            },
        ]);
    });
    it('should compile a cross-table query and execute', async () => {
        await qb
            .selectFrom('customers')
            .where('orders', 'hasAnyOf', qb => qb.where('amount', '>', 100))
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'select',
            tableName: 'customers',
            filtersSet: {
                conjunction: 'and',
                filtersSet: [
                    {
                        field: 'orders',
                        operator: 'hasAnyOf',
                        value: [
                            'cross-table',
                            {
                                conjunction: 'and',
                                filtersSet: [
                                    {
                                        field: 'amount',
                                        operator: '>',
                                        value: 100,
                                    },
                                ],
                            },
                        ],
                    },
                ],
            },
        });
    });
    it('should handle a successful transaction', async () => {
        const mockRawRequest = jest
            .fn()
            .mockResolvedValueOnce({
            startTransaction: 'test-transaction-id',
        })
            .mockResolvedValueOnce({ execute: [] })
            .mockResolvedValueOnce({ commitTransaction: true });
        SocketConnection.prototype.rawRequest = mockRawRequest;
        // @ts-ignore
        const qb = createQueryBuilder({
            baseUrl: 'http://localhost',
            baseId: 'test-base-id',
            apiKey: 'test',
        });
        await qb.transaction(txQb => {
            return txQb
                .insertInto('customers')
                .values({ firstName: 'John', lastName: 'Doe' })
                .execute();
        });
        expect(mockRawRequest).toHaveBeenCalledTimes(3);
        const startTransactionCall = mockRawRequest.mock.calls[0];
        expect(startTransactionCall[0]).toContain('startTransaction');
        const executeCall = mockRawRequest.mock.calls[1];
        expect(executeCall[3]).toBe('test-transaction-id');
        const commitTransactionCall = mockRawRequest.mock.calls[2];
        expect(commitTransactionCall[0]).toContain('commitTransaction');
        expect(commitTransactionCall[3]).toBe('test-transaction-id');
    });
    it('should handle a failed transaction and rollback', async () => {
        const transactionId = 'test-transaction-id-fail';
        const errorMessage = 'Something went wrong';
        const mockRawRequest = jest
            .fn()
            .mockResolvedValueOnce({ startTransaction: transactionId })
            .mockRejectedValueOnce(new Error(errorMessage))
            .mockResolvedValueOnce({ rollbackTransaction: true });
        SocketConnection.prototype.rawRequest = mockRawRequest;
        // @ts-ignore
        const qb = createQueryBuilder({
            baseUrl: 'http://localhost',
            baseId: 'test-base-id',
            apiKey: 'test',
        });
        await expect(qb.transaction(txQb => {
            return txQb
                .insertInto('customers')
                .values({ firstName: 'John', lastName: 'Doe' })
                .execute();
        })).rejects.toThrow(errorMessage);
        expect(mockRawRequest).toHaveBeenCalledTimes(3);
        const startTransactionCall = mockRawRequest.mock.calls[0];
        expect(startTransactionCall[0]).toContain('startTransaction');
        const executeCall = mockRawRequest.mock.calls[1];
        expect(executeCall[3]).toBe(transactionId);
        const rollbackTransactionCall = mockRawRequest.mock.calls[2];
        expect(rollbackTransactionCall[0]).toContain('rollbackTransaction');
        expect(rollbackTransactionCall[3]).toBe(transactionId);
    });
    it('should not have transaction method within a transaction', async () => {
        const mockRawRequest = jest
            .fn()
            .mockResolvedValueOnce({
            startTransaction: 'test-transaction-id-no-nested',
        })
            .mockResolvedValueOnce({ commitTransaction: true });
        SocketConnection.prototype.rawRequest = mockRawRequest;
        // @ts-ignore
        const qb = createQueryBuilder({
            baseUrl: 'http://localhost',
            baseId: 'test-base-id',
            apiKey: 'test',
        });
        await qb.transaction(async (txQb) => {
            expect(txQb).not.toHaveProperty('transaction');
        });
        expect(mockRawRequest).toHaveBeenCalledTimes(2);
    });
    it('should compile an insert query with a number array for a link field', async () => {
        await qb
            .insertInto('customers')
            .values({
            firstName: 'Jane',
            lastName: 'Doe',
            orders: [1, 2, 3],
        })
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'create',
            tableName: 'customers',
            createdRecords: [
                {
                    firstName: 'Jane',
                    lastName: 'Doe',
                    orders: [1, 2, 3],
                },
            ],
        });
    });
    it('should compile an update query with a number array for a link field', async () => {
        await qb
            .update('customers')
            .set({
            lastName: 'Smith',
            orders: [1, 2, 3],
        })
            .where('id', '=', 1)
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'update',
            tableName: 'customers',
            values: {
                lastName: 'Smith',
                orders: [1, 2, 3],
            },
        });
    });
    it('should compile an insert query with attachments and execute', async () => {
        // Mock uploadAttachments to return a mock Attachment
        const mockAttachment = new Attachment({
            collectionName: 'string',
            fileInformation: {
                path: 'test-path.png',
                mimetype: 'image/png',
                size: 12345,
            },
        });
        // @ts-ignore
        qb.uploadAttachments = jest.fn().mockResolvedValue([mockAttachment]);
        await qb
            .insertInto('customers')
            .values({
            firstName: 'Jane',
            lastName: 'Doe',
            avatar: await qb.uploadAttachments([
                { file: new Blob(['']), name: 'test.png' },
            ]),
        })
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'create',
            tableName: 'customers',
            createdRecords: [
                {
                    firstName: 'Jane',
                    lastName: 'Doe',
                    avatar: [
                        {
                            url: 'test-path.png',
                            fileType: 'image/png',
                            size: 12345,
                        },
                    ],
                },
            ],
        });
    });
    it('should compile an update query with attachments and execute', async () => {
        // Mock uploadAttachments to return a mock Attachment
        const mockAttachment = new Attachment({
            collectionName: 'string',
            fileInformation: {
                path: 'test-path.png',
                mimetype: 'image/png',
                size: 12345,
            },
        });
        // @ts-ignore
        qb.uploadAttachments = jest.fn().mockResolvedValue([mockAttachment]);
        await qb
            .update('customers')
            .set({
            lastName: 'Smith',
            avatar: await qb.uploadAttachments([
                { file: new Blob(['']), name: 'test.png' },
            ]),
        })
            .where('id', '=', 1)
            .execute();
        expect(mockRawRequest).toHaveBeenCalledTimes(1);
        const variables = mockRawRequest.mock.calls[0][1];
        expect(variables.metadata[0]).toMatchObject({
            type: 'update',
            tableName: 'customers',
            values: {
                lastName: 'Smith',
                avatar: [
                    {
                        url: 'test-path.png',
                        fileType: 'image/png',
                        size: 12345,
                    },
                ],
            },
        });
    });
    it('should upload attachments and return Attachment instances', async () => {
        const mockUploadResponse = [
            {
                collectionName: 'string',
                fileInformation: {
                    fieldname: 'files',
                    originalname: 'Designer.png',
                    encoding: '7bit',
                    mimetype: 'image/png',
                    destination: '/var/bamboo/storage/bamboo-media-uploader-main/files/d2330358-ae38-40ef-b127-bd497456d488',
                    filename: '31529ce5-5ef2-428d-bb63-aaf8d3e97635.png',
                    path: 'files/d2330358-ae38-40ef-b127-bd497456d488/31529ce5-5ef2-428d-bb63-aaf8d3e97635.png',
                    size: 1483249,
                    format: 'png',
                    width: 1080,
                    height: 1080,
                },
                metadata: { thumbnails: [], clips: [] },
                baseId: 'fdfdfd',
                storageAdaptor: 'LOCAL',
                _id: '691ec20b164dcec7a1a981f6',
                __v: 0,
            },
        ];
        global.fetch = jest.fn().mockResolvedValue({
            ok: true,
            json: jest.fn().mockResolvedValue(mockUploadResponse),
        });
        // @ts-ignore
        const qb = createQueryBuilder({
            baseUrl: 'http://localhost',
            baseId: 'test-base-id',
            apiKey: 'test-api-key',
        });
        const files = [{ file: new Blob(['']), name: 'test.png' }];
        const attachments = await qb.uploadAttachments(files);
        expect(fetch).toHaveBeenCalledWith('https://media.taylordb.ai/media-collection', expect.objectContaining({
            method: 'POST',
            headers: {
                Authorization: 'Bearer test-api-key',
                baseId: 'test-base-id',
            },
        }));
        expect(attachments).toHaveLength(1);
        expect(attachments[0]).toBeInstanceOf(Attachment);
        expect(attachments[0].fileInformation.originalname).toBe('Designer.png');
        expect(attachments[0].toColumnValue()).toEqual({
            url: 'files/d2330358-ae38-40ef-b127-bd497456d488/31529ce5-5ef2-428d-bb63-aaf8d3e97635.png',
            fileType: 'image/png',
            size: 1483249,
        });
    });
});
//# sourceMappingURL=query-builder.spec.js.map