// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

/* eslint-disable max-nested-callbacks */

import assert from 'assert';

import {ViewTypes} from 'app/constants';
import {
    cleanUpPostsInChannel,
    cleanUpState,
    getAllFromPostsInChannel,
    messageRetention,
} from 'app/store/middleware';

describe('messageRetention', () => {
    describe('should chain the same incoming action type', () => {
        const actions = [
            {
                type: 'persist/REHYDRATE',
                payload: {
                    views: {
                        team: {
                        },
                    },
                },
            },
            {
                type: ViewTypes.DATA_CLEANUP,
                payload: {
                    entities: {
                        channels: {
                        },
                        posts: {
                        },
                    },
                    views: {
                        team: {
                        },
                    },
                },
            },
            {
                type: 'other',
            },
        ];

        actions.forEach((action) => {
            it(`for action type ${action.type}`, () => {
                const store = {};
                const next = (a) => a;

                const nextAction = messageRetention(store)(next)(action);
                assert.equal(action.type, nextAction.type);
            });
        });
    });
});

describe('cleanUpState', () => {
    test('should remove post because of data retention', () => {
        const state = {
            entities: {
                channels: {
                    currentChannelId: 'channel1',
                },
                files: {
                    fileIdsByPostId: {},
                },
                general: {
                    dataRetentionPolicy: {
                        message_deletion_enabled: true,
                        message_retention_cutoff: 1000,
                    },
                },
                posts: {
                    posts: {
                        post1: {id: 'post1', channel_id: 'channel1', create_at: 1000},
                        post2: {id: 'post2', channel_id: 'channel1', create_at: 999},
                    },
                    postsInChannel: {
                        channel1: [
                            {order: ['post1', 'post2'], recent: true},
                        ],
                    },
                    postsInThread: {},
                    reactions: {},
                },
                search: {
                    results: ['post1', 'post2'],
                    flagged: ['post1', 'post2', 'post3'],
                },
            },
            views: {
                team: {
                    lastChannelForTeam: {
                        team1: ['channel1'],
                    },
                },
            },
        };

        const result = cleanUpState({payload: state});

        expect(result.payload.entities.posts.posts.post1).toBeDefined();
        expect(result.payload.entities.posts.posts.post2).toBeUndefined();
        expect(result.payload.entities.posts.postsInChannel.channel1).toEqual([{order: ['post1'], recent: true}]);
        expect(result.payload.entities.search.results).toEqual(['post1', 'post2']);
        expect(result.payload.entities.search.flagged).toEqual(['post1', 'post2', 'post3']);
    });

    test('should keep failed pending post', () => {
        const state = {
            entities: {
                channels: {
                    currentChannelId: 'channel1',
                },
                files: {
                    fileIdsByPostId: {},
                },
                posts: {
                    pendingPostIds: ['pending'],
                    posts: {
                        pending: {id: 'pending', pending_post_id: 'pending', channel_id: 'channel1', failed: true},
                        post1: {id: 'post1', channel_id: 'channel1'},
                        post2: {id: 'post2', channel_id: 'channel1'},
                    },
                    postsInChannel: {
                        channel1: [
                            {order: ['pending', 'post1', 'post2'], recent: true},
                        ],
                    },
                    postsInThread: {},
                    reactions: {},
                },
            },
            views: {
                team: {
                    lastChannelForTeam: {
                        team1: ['channel1'],
                    },
                },
            },
        };

        const result = cleanUpState({payload: state});

        expect(result.payload.entities.posts.pendingPostIds).toEqual(['pending']);
        expect(result.payload.entities.posts.posts.pending).toBeDefined();
        expect(result.payload.entities.posts.postsInChannel.channel1).toEqual([{order: ['pending', 'post1', 'post2'], recent: true}]);
    });

    test('should remove non-failed pending post', () => {
        const state = {
            entities: {
                channels: {
                    currentChannelId: 'channel1',
                },
                files: {
                    fileIdsByPostId: {},
                },
                posts: {
                    pendingPostIds: ['pending'],
                    posts: {
                        pending: {id: 'pending', pending_post_id: 'pending', channel_id: 'channel1'},
                        post1: {id: 'post1', channel_id: 'channel1'},
                        post2: {id: 'post2', channel_id: 'channel1'},
                    },
                    postsInChannel: {
                        channel1: [
                            {order: ['pending', 'post1', 'post2'], recent: true},
                        ],
                    },
                    postsInThread: {},
                    reactions: {},
                },
            },
            views: {
                team: {
                    lastChannelForTeam: {
                        team1: ['channel1'],
                    },
                },
            },
        };

        const result = cleanUpState({payload: state});

        expect(result.payload.entities.posts.pendingPostIds).toEqual([]);
        expect(result.payload.entities.posts.posts.pending).toBeUndefined();
        expect(result.payload.entities.posts.postsInChannel.channel1).toEqual([{order: ['post1', 'post2'], recent: true}]);
    });

    test('should remove non-existent pending post', () => {
        const state = {
            entities: {
                channels: {
                    currentChannelId: 'channel1',
                },
                files: {
                    fileIdsByPostId: {},
                },
                posts: {
                    pendingPostIds: ['pending'],
                    posts: {
                        post1: {id: 'post1', channel_id: 'channel1'},
                        post2: {id: 'post2', channel_id: 'channel1'},
                    },
                    postsInChannel: {
                        channel1: [
                            {order: ['post1', 'post2'], recent: true},
                        ],
                    },
                    postsInThread: {},
                    reactions: {},
                },
            },
            views: {
                team: {
                    lastChannelForTeam: {
                        team1: ['channel1'],
                    },
                },
            },
        };

        const result = cleanUpState({payload: state});

        expect(result.payload.entities.posts.pendingPostIds).toEqual([]);
        expect(result.payload.entities.posts.postsInChannel.channel1).toEqual([{order: ['post1', 'post2'], recent: true}]);
    });
});

describe('cleanUpPostsInChannel', () => {
    test('should only keep posts for recently viewed channels', () => {
        const postsInChannel = {
            channel1: [
                {order: ['a', 'b'], recent: true},
            ],
            channel2: [
                {order: ['c', 'd'], recent: true},
            ],
        };
        const lastChannelForTeam = {
            team1: ['channel1'],
        };

        const nextPostsInChannel = cleanUpPostsInChannel(postsInChannel, lastChannelForTeam);

        expect(nextPostsInChannel).toEqual({
            channel1: [
                {order: ['a', 'b'], recent: true},
            ],
        });
    });

    test('should keep posts for recently viewed channels across multiple teams', () => {
        const postsInChannel = {
            channel1: [
                {order: ['a', 'b'], recent: true},
            ],
            channel2: [
                {order: ['c', 'd'], recent: true},
            ],
        };
        const lastChannelForTeam = {
            team1: ['channel1'],
            team2: ['channel2'],
        };

        const nextPostsInChannel = cleanUpPostsInChannel(postsInChannel, lastChannelForTeam);

        expect(nextPostsInChannel).toEqual({
            channel1: [
                {order: ['a', 'b'], recent: true},
            ],
            channel2: [
                {order: ['c', 'd'], recent: true},
            ],
        });
    });

    test('should only keep the last X posts in each channel', () => {
        const postsInChannel = {
            channel1: [
                {order: ['a', 'b', 'c', 'd'], recent: true},
            ],
            channel2: [
                {order: ['e', 'f', 'g', 'h'], recent: true},
            ],
        };
        const lastChannelForTeam = {
            team1: ['channel1', 'channel2'],
        };

        const nextPostsInChannel = cleanUpPostsInChannel(postsInChannel, lastChannelForTeam, '', 2);

        expect(nextPostsInChannel).toEqual({
            channel1: [
                {order: ['a', 'b'], recent: true},
            ],
            channel2: [
                {order: ['e', 'f'], recent: true},
            ],
        });
    });

    test('should only keep the most recent posts in a channel', () => {
        const postsInChannel = {
            channel1: [
                {order: ['a', 'b', 'c', 'd'], recent: true},
                {order: ['e', 'f', 'g', 'h']},
            ],
        };
        const lastChannelForTeam = {
            team1: ['channel1'],
        };

        const nextPostsInChannel = cleanUpPostsInChannel(postsInChannel, lastChannelForTeam, '');

        expect(nextPostsInChannel).toEqual({
            channel1: [
                {order: ['a', 'b', 'c', 'd'], recent: true},
            ],
        });
    });

    test('should keep all posts in the current channel, if specified', () => {
        const postsInChannel = {
            channel1: [
                {order: ['a', 'b', 'c', 'd'], recent: true},
            ],
            channel2: [
                {order: ['e', 'f', 'g', 'h'], recent: true},
            ],
        };
        const lastChannelForTeam = {
            team1: ['channel1', 'channel2'],
        };

        const nextPostsInChannel = cleanUpPostsInChannel(postsInChannel, lastChannelForTeam, 'channel2', 2);

        expect(nextPostsInChannel).toEqual({
            channel1: [
                {order: ['a', 'b'], recent: true},
            ],
            channel2: [
                {order: ['e', 'f', 'g', 'h'], recent: true},
            ],
        });
    });

    test('should not error when a DM/GM channel appears on multiple teams', () => {
        const postsInChannel = {
            dmChannel: [
                {order: ['a', 'b', 'c', 'd'], recent: true},
            ],
        };
        const lastChannelForTeam = {
            team1: ['dmChannel'],
            team2: ['dmChannel'],
        };

        const nextPostsInChannel = cleanUpPostsInChannel(postsInChannel, lastChannelForTeam);

        expect(nextPostsInChannel).toEqual({
            dmChannel: [
                {order: ['a', 'b', 'c', 'd'], recent: true},
            ],
        });
    });

    test('should not error when a recent channel is missing', () => {
        const postsInChannel = {
            channel1: [
                {order: ['a', 'b', 'c', 'd'], recent: true},
            ],
        };
        const lastChannelForTeam = {
            team1: ['channel1', 'channel2'],
        };

        const nextPostsInChannel = cleanUpPostsInChannel(postsInChannel, lastChannelForTeam);

        expect(nextPostsInChannel).toEqual({
            channel1: [
                {order: ['a', 'b', 'c', 'd'], recent: true},
            ],
        });
    });
});

describe('getAllFromPostsInChannel', () => {
    test('should return every post ID', () => {
        const postsInChannel = {
            channel1: [
                {order: ['a', 'b']},
                {order: ['c', 'd']},
            ],
            channel2: [
                {order: ['e', 'f']},
            ],
            channel3: [
                {order: ['g', 'h']},
            ],
        };

        const postIds = getAllFromPostsInChannel(postsInChannel);

        expect(postIds).toMatchObject(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']);
    });
});
