import sinon from 'sinon';

import { assert } from 'chai';
import { OutlookClient, OutlookClientFactory } from '../../lib/client/outlook-client.js';
import { Email } from '../../lib/client/email-client.js';

describe('OutlookCilent', () => {
    let client: OutlookClient, mock: sinon.SinonMock;

    beforeEach(() => {
        client = new OutlookClient("http://abc/", "secret");
        mock = sinon.mock(client);
    });

    describe('getUnreadEmails', () => {
        function setupFolders(folders: any[]) {
            mock.expects('_callApi')
                .withArgs(
                    'get',
                    'http://abc/v1.0/me/mailFolders?$select=id,displayName',
                    'secret')
                .returns(Promise.resolve({
                    value: folders
                }));
        }

        function setupEmails(emails: any[]) {
            mock.expects('_callApi')
                .withArgs(
                    'get',
                    'http://abc/v1.0/me/messages?$select=subject,body,bodyPreview,categories,from,parentFolderId&$filter=isRead eq false',
                    'secret')
                .returns(Promise.resolve({
                    value: emails
                }));
        }

        it('returns empty list when there are no messages', async () => {
            setupFolders([]);
            setupEmails([]);

            const emails = await client.getUnreadEmails();

            mock.verify();
            assert.deepEqual(emails, []);
        });

        it('returns email when there is a message', async () => {
            setupFolders([
                {
                    id: 'abc',
                    displayName: 'inbox'
                }
            ]);

            setupEmails([
                {
                    id: '123',
                    parentFolderId: 'abc',
                    bodyPreview: 'preview text',
                    subject: 'the subject',
                    from: {
                        emailAddress: {
                            name: 'spammer',
                            address: 'sender@example.com'
                        }
                    },
                    body: {
                        content: 'the body'
                    }
                }
            ]);

            const emails = await client.getUnreadEmails();

            const email = new Email();
            email.id = '123';
            email.from = 'spammer <sender@example.com>';
            email.labels = ['inbox'];
            email.snippet = 'preview text';
            email.subject = 'the subject';
            email.body = 'the body';

            mock.verify();
            assert.deepEqual(emails, [email]);
        });
    });

    describe('deleteEmails', () => {
        function setupDelete(id: string, promise: Promise<any>) {
            return mock.expects('_callApi')
                .withArgs(
                    'delete',
                    `http://abc/v1.0/me/messages/${id}`,
                    'secret')
                .returns(promise);
        }

        it('throws when fails', (done) => {
            setupDelete('abc', Promise.reject(Error('test')));

            client.deleteEmails([{ id: 'abc' }] as any)
                .then(() => {
                    assert.fail('deleteEmails should throw');
                }).catch(err => {
                    assert.match(err.message, /Failed to delete messages: Error: test/);
                    done();
                });

            mock.verify();
        });

        it('delets email', async () => {
            setupDelete('123', Promise.resolve());

            await client.deleteEmails([{ id: '123' }] as any);

            mock.verify();
        });
    });

    describe('archiveEmails', () => {
        it('moves email to archive', async () => {
            mock.expects('_callApi')
                .withArgs(
                    'post',
                    'http://abc/v1.0/me/messages/123/move',
                    'secret',
                    { destinationId: 'archive' })
                .returns(Promise.resolve());

            await client.archiveEmails([{ id: '123' }] as any);

            mock.verify();
        });

        it('throws when fails', async () => {
            mock.expects('_callApi').returns(Promise.reject(Error('test')));

            try {
                await client.archiveEmails([{ id: '123' }] as any);
                assert.fail('should throw');
            } catch (err: any) {
                assert.match(err.message, /Failed to archive messages/);
            }
        });
    });

    describe('markAsReadEmails', () => {
        it('patches email as read', async () => {
            mock.expects('_callApi')
                .withArgs(
                    'patch',
                    'http://abc/v1.0/me/messages/123',
                    'secret',
                    { isRead: true })
                .returns(Promise.resolve());

            await client.markAsReadEmails([{ id: '123' }] as any);

            mock.verify();
        });

        it('throws when fails', async () => {
            mock.expects('_callApi').returns(Promise.reject(Error('test')));

            try {
                await client.markAsReadEmails([{ id: '123' }] as any);
                assert.fail('should throw');
            } catch (err: any) {
                assert.match(err.message, /Failed to mark messages as read/);
            }
        });
    });
});

describe('OutlookClientFactory', () => {
    describe('multi-account file names', () => {
        it('uses default file names when no account specified', () => {
            const factory = new OutlookClientFactory({} as any);
            assert.equal((factory as any)._credentialsFile, 'outlook.credentials.json');
            assert.equal((factory as any)._tokenFile, 'outlook.token.json');
        });

        it('uses default file names for "default" account', () => {
            const factory = new OutlookClientFactory({} as any, 'default');
            assert.equal((factory as any)._credentialsFile, 'outlook.credentials.json');
            assert.equal((factory as any)._tokenFile, 'outlook.token.json');
        });

        it('uses account-specific file names for named account', () => {
            const factory = new OutlookClientFactory({} as any, 'work');
            assert.equal((factory as any)._credentialsFile, 'outlook.credentials.work.json');
            assert.equal((factory as any)._tokenFile, 'outlook.token.work.json');
        });
    });
});
