jest.mock('shared/supabase/init');
jest.mock('shared/utils');
jest.mock('shared/profiles/parse-photos');
jest.mock('shared/supabase/users');
jest.mock('shared/supabase/utils');
jest.mock('common/util/try-catch');
jest.mock('shared/analytics');
jest.mock('common/discord/core');
jest.mock('common/util/time');

import { createProfile } from "api/create-profile";
import * as supabaseInit from "shared/supabase/init";
import * as sharedUtils from "shared/utils";
import * as supabaseUsers from "shared/supabase/users";
import * as supabaseUtils from "shared/supabase/utils";
import { tryCatch } from "common/util/try-catch";
import { removePinnedUrlFromPhotoUrls } from "shared/profiles/parse-photos";
import * as sharedAnalytics from "shared/analytics";
import { sendDiscordMessage } from "common/discord/core";
import { AuthedUser } from "api/helpers/endpoint";

describe('createProfile', () => {
    let mockPg = {} as any;
    beforeEach(() => {
        jest.resetAllMocks();
        mockPg = {
            oneOrNone: jest.fn(),
            one: jest.fn()
        };

        (supabaseInit.createSupabaseDirectClient as jest.Mock)
            .mockReturnValue(mockPg);
    });
    afterEach(() => {
        jest.restoreAllMocks();
    });

    describe('when given valid input', () => {
        it('should successfully create a profile', async () => {
            const mockBody = {
                city: "mockCity",
                gender: "mockGender",
                looking_for_matches: true,
                photo_urls: ["mockPhotoUrl1"],
                pinned_url: "mockPinnedUrl",
                pref_gender: ["mockPrefGender"],
                pref_relation_styles: ["mockPrefRelationStyles"],
                visibility: 'public' as "public" | "member",
                wants_kids_strength: 2,
            };
            const mockAuth = {uid: '321'} as AuthedUser;
            const mockReq = {} as any;
            const mockNProfiles = 10
            const mockData = {
                age: 30,
                city: "mockCity"
            };
            const mockUser = {
                createdTime: Date.now(),
                name: "mockName",
                username: "mockUserName"
            };

            (tryCatch as jest.Mock).mockResolvedValueOnce({data: false, error: null});
            (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
            (tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null});

            const results: any = await createProfile(mockBody, mockAuth, mockReq);

            expect(results.result).toEqual(mockData);
            expect(tryCatch).toBeCalledTimes(2);
            expect(mockPg.oneOrNone).toBeCalledTimes(1);
            expect(mockPg.oneOrNone).toBeCalledWith(
                expect.stringContaining('select id from profiles where user_id = $1'),
                [mockAuth.uid]
            );
            expect(removePinnedUrlFromPhotoUrls).toBeCalledTimes(1);
            expect(removePinnedUrlFromPhotoUrls).toBeCalledWith(mockBody);
            expect(sharedUtils.getUser).toBeCalledTimes(1);
            expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid);
            expect(supabaseUsers.updateUser).toBeCalledTimes(1);
            expect(supabaseUsers.updateUser).toBeCalledWith(
                expect.any(Object),
                mockAuth.uid,
                {avatarUrl: mockBody.pinned_url}
            );
            expect(supabaseUtils.insert).toBeCalledTimes(1);
            expect(supabaseUtils.insert).toBeCalledWith(
                expect.any(Object),
                'profiles',
                expect.objectContaining({user_id: mockAuth.uid})
            );
            
            (mockPg.one as jest.Mock).mockReturnValue(mockNProfiles);

            await results.continue();

            expect(sharedAnalytics.track).toBeCalledTimes(1);
            expect(sharedAnalytics.track).toBeCalledWith(
                mockAuth.uid,
                'create profile',
                {username: mockUser.username}
            );
            expect(sendDiscordMessage).toBeCalledTimes(1);
            expect(sendDiscordMessage).toBeCalledWith(
                expect.stringContaining(mockUser.name && mockUser.username),
                'members'
            );
        });

        it('should successfully create milestone profile', async () => {
            const mockBody = {
                city: "mockCity",
                gender: "mockGender",
                looking_for_matches: true,
                photo_urls: ["mockPhotoUrl1"],
                pinned_url: "mockPinnedUrl",
                pref_gender: ["mockPrefGender"],
                pref_relation_styles: ["mockPrefRelationStyles"],
                visibility: 'public' as "public" | "member",
                wants_kids_strength: 2,
            };
            const mockAuth = {uid: '321'} as AuthedUser;
            const mockReq = {} as any;
            const mockNProfiles = 15
            const mockData = {
                age: 30,
                city: "mockCity"
            };
            const mockUser = {
                createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
                name: "mockName",
                username: "mockUserName"
            };

            (tryCatch as jest.Mock).mockResolvedValueOnce({data: false, error: null});
            (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
            (tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null});

            const results: any = await createProfile(mockBody, mockAuth, mockReq);

            expect(results.result).toEqual(mockData);

            (mockPg.one as jest.Mock).mockReturnValue(mockNProfiles);

            await results.continue();

            expect(mockPg.one).toBeCalledTimes(1);
            expect(mockPg.one).toBeCalledWith(
                expect.stringContaining('SELECT count(*) FROM profiles'),
                [],
                expect.any(Function)
            );
            expect(sendDiscordMessage).toBeCalledTimes(2);
            expect(sendDiscordMessage).toHaveBeenNthCalledWith(
                2,
                expect.stringContaining(String(mockNProfiles)),
                'general'
            );

        });
    });
    describe('when an error occurs', () => {
        it('should throw if it failed to track create profile', async () => {
            const mockBody = {
                city: "mockCity",
                gender: "mockGender",
                looking_for_matches: true,
                photo_urls: ["mockPhotoUrl1"],
                pinned_url: "mockPinnedUrl",
                pref_gender: ["mockPrefGender"],
                pref_relation_styles: ["mockPrefRelationStyles"],
                visibility: 'public' as "public" | "member",
                wants_kids_strength: 2,
            };
            const mockAuth = {uid: '321'} as AuthedUser;
            const mockReq = {} as any;
            const mockData = {
                age: 30,
                city: "mockCity"
            };
            const mockUser = {
                createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
                name: "mockName",
                username: "mockUserName"
            };

            (tryCatch as jest.Mock).mockResolvedValueOnce({data: false, error: null});
            (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
            (tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null});

            const results: any = await createProfile(mockBody, mockAuth, mockReq);
            
            const errorSpy = jest.spyOn(console , 'error').mockImplementation(() => {});

            (sharedAnalytics.track as jest.Mock).mockRejectedValue(new Error('Track error'));
            
            await results.continue();

            expect(errorSpy).toBeCalledWith(
                'Failed to track create profile',
                expect.objectContaining({name: 'Error'})
            );
        });

        it('should throw if it failed to send discord new profile', async () => {
            const mockBody = {
                city: "mockCity",
                gender: "mockGender",
                looking_for_matches: true,
                photo_urls: ["mockPhotoUrl1"],
                pinned_url: "mockPinnedUrl",
                pref_gender: ["mockPrefGender"],
                pref_relation_styles: ["mockPrefRelationStyles"],
                visibility: 'public' as "public" | "member",
                wants_kids_strength: 2,
            };
            const mockAuth = {uid: '321'} as AuthedUser;
            const mockReq = {} as any;
            const mockData = {
                age: 30,
                city: "mockCity"
            };
            const mockUser = {
                createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
                name: "mockName",
                username: "mockUserName"
            };

            (tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null});
            (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
            (tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null});

            const results: any = await createProfile(mockBody, mockAuth, mockReq);

            expect(results.result).toEqual(mockData);
            
            const errorSpy = jest.spyOn(console , 'error').mockImplementation(() => {});

            (sendDiscordMessage as jest.Mock).mockRejectedValue(new Error('Sending error'));

            await results.continue();

            expect(errorSpy).toBeCalledWith(
                'Failed to send discord new profile',
                expect.objectContaining({name: 'Error'})
            );
        });

        it('should throw if it failed to send discord user milestone', async () => {
            const mockBody = {
                city: "mockCity",
                gender: "mockGender",
                looking_for_matches: true,
                photo_urls: ["mockPhotoUrl1"],
                pinned_url: "mockPinnedUrl",
                pref_gender: ["mockPrefGender"],
                pref_relation_styles: ["mockPrefRelationStyles"],
                visibility: 'public' as "public" | "member",
                wants_kids_strength: 2,
            };
            const mockAuth = {uid: '321'} as AuthedUser;
            const mockReq = {} as any;
            const mockNProfiles = 15
            const mockData = {
                age: 30,
                city: "mockCity"
            };
            const mockUser = {
                createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
                name: "mockName",
                username: "mockUserName"
            };

            (tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null});
            (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
            (tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null});

            const results: any = await createProfile(mockBody, mockAuth, mockReq);

            expect(results.result).toEqual(mockData);
            
            const errorSpy = jest.spyOn(console , 'error').mockImplementation(() => {});

            (sendDiscordMessage as jest.Mock)
                .mockResolvedValueOnce(null)
                .mockRejectedValueOnce(new Error('Discord error'));
            (mockPg.one as jest.Mock).mockReturnValue(mockNProfiles);

            await results.continue();

            expect(sendDiscordMessage).toBeCalledTimes(2);
            expect(sendDiscordMessage).toHaveBeenNthCalledWith(
                2,
                expect.stringContaining(String(mockNProfiles)),
                'general'
            );
            expect(errorSpy).toBeCalledWith(
                'Failed to send discord user milestone',
                expect.objectContaining({name: 'Error'})
            );
        });

        it('should throw if the user already exists', async () => {
            const mockBody = {
                city: "mockCity",
                gender: "mockGender",
                looking_for_matches: true,
                photo_urls: ["mockPhotoUrl1"],
                pinned_url: "mockPinnedUrl",
                pref_gender: ["mockPrefGender"],
                pref_relation_styles: ["mockPrefRelationStyles"],
                visibility: 'public' as "public" | "member",
                wants_kids_strength: 2,
            };
            const mockAuth = {uid: '321'} as AuthedUser;
            const mockReq = {} as any;

            (tryCatch as jest.Mock).mockResolvedValueOnce({data: true, error: null});

            await expect(createProfile(mockBody, mockAuth, mockReq))
                .rejects
                .toThrowError('User already exists');
        });

        it('should throw if unable to find the account', async () => {
            const mockBody = {
                city: "mockCity",
                gender: "mockGender",
                looking_for_matches: true,
                photo_urls: ["mockPhotoUrl1"],
                pinned_url: "mockPinnedUrl",
                pref_gender: ["mockPrefGender"],
                pref_relation_styles: ["mockPrefRelationStyles"],
                visibility: 'public' as "public" | "member",
                wants_kids_strength: 2,
            };
            const mockAuth = {uid: '321'} as AuthedUser;
            const mockReq = {} as any;

            (tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null});
            (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);

            await expect(createProfile(mockBody, mockAuth, mockReq))
                .rejects
                .toThrowError('Your account was not found');
        });

        it('should throw if anything unexpected happens when creating the user', async () => {
            const mockBody = {
                city: "mockCity",
                gender: "mockGender",
                looking_for_matches: true,
                photo_urls: ["mockPhotoUrl1"],
                pinned_url: "mockPinnedUrl",
                pref_gender: ["mockPrefGender"],
                pref_relation_styles: ["mockPrefRelationStyles"],
                visibility: 'public' as "public" | "member",
                wants_kids_strength: 2,
            };
            const mockAuth = {uid: '321'} as AuthedUser;
            const mockReq = {} as any;
            const mockUser = {
                createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
                name: "mockName",
                username: "mockUserName"
            };

            (tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null});
            (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
            (tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: Error});

            await expect(createProfile(mockBody, mockAuth, mockReq))
                .rejects
                .toThrowError('Error creating user');
        });
    });
});