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

import DeviceInfo from 'react-native-device-info';

import {ViewTypes} from 'app/constants';
import initialState from 'app/initial_state';

import {
    captureException,
    LOGGER_JAVASCRIPT_WARNING,
} from 'app/utils/sentry';

export function messageRetention(store) {
    return (next) => (action) => {
        if (action.type === 'persist/REHYDRATE') {
            const {app} = action.payload;
            const {entities, views} = action.payload;

            if (!entities || !views) {
                return next(action);
            }

            // When a new version of the app has been detected
            if (!app || !app.version || app.version !== DeviceInfo.getVersion() || app.build !== DeviceInfo.getBuildNumber()) {
                return next(resetStateForNewVersion(action));
            }

            // Keep only the last 60 messages for the last 5 viewed channels in each team
            // and apply data retention on those posts if applies
            let nextAction;
            try {
                nextAction = cleanupState(action);
            } catch (e) {
                // Sometimes, the payload is incomplete so log the error to Sentry and skip the cleanup
                console.warn(e); // eslint-disable-line no-console
                captureException(e, LOGGER_JAVASCRIPT_WARNING, store);
                nextAction = action;
            }

            return next(nextAction);
        } else if (action.type === ViewTypes.DATA_CLEANUP) {
            const nextAction = cleanupState(action, true);
            return next(nextAction);
        }

        return next(action);
    };
}

function resetStateForNewVersion(action) {
    const {payload} = action;
    const lastChannelForTeam = getLastChannelForTeam(payload);

    let general = initialState.entities.general;
    if (payload.entities.general) {
        general = payload.entities.general;
    }

    let teams = initialState.entities.teams;
    if (payload.entities.teams) {
        teams = {
            currentTeamId: payload.entities.teams.currentTeamId,
            teams: payload.entities.teams.teams,
            myMembers: payload.entities.teams.myMembers,
        };
    }

    let users = initialState.entities.users;
    if (payload.entities.users) {
        const currentUserId = payload.entities.users.currentUserId;
        if (currentUserId) {
            users = {
                currentUserId,
                profiles: {
                    [currentUserId]: payload.entities.users.profiles[currentUserId],
                },
            };
        }
    }

    let preferences = initialState.entities.preferences;
    if (payload.entities.preferences) {
        preferences = payload.entities.preferences;
    }

    let roles = initialState.entities.roles;
    if (payload.entities.roles) {
        roles = payload.entities.roles;
    }

    let search = initialState.entities.search;
    if (payload.entities.search && payload.entities.search.recent) {
        search = {
            recent: payload.entities.search.recent,
        };
    }

    let channelDrafts = initialState.views.channel.drafts;
    if (payload.views.channel && payload.views.channel.drafts) {
        channelDrafts = payload.views.channel.drafts;
    }

    let i18n = initialState.views.i18n;
    if (payload.views.i18n) {
        i18n = payload.views.i18n;
    }

    let lastTeamId = initialState.views.team.lastTeamId;
    if (payload.views.team && payload.views.team.lastTeamId) {
        lastTeamId = payload.views.team.lastTeamId;
    }

    const currentChannelId = lastChannelForTeam[lastTeamId] && lastChannelForTeam[lastTeamId].length ? lastChannelForTeam[lastTeamId][0] : '';
    let channels = initialState.entities.channels;
    if (payload.entities.channels && currentChannelId) {
        channels = {
            currentChannelId,
            channels: {
                [currentChannelId]: payload.entities.channels.channels[currentChannelId],
            },
            myMembers: {
                [currentChannelId]: payload.entities.channels.myMembers[currentChannelId],
            },
        };
    }

    let threadDrafts = initialState.views.thread.drafts;
    if (payload.views.thread && payload.views.thread.drafts) {
        threadDrafts = payload.views.thread.drafts;
    }

    let selectServer = initialState.views.selectServer;
    if (payload.views.selectServer) {
        selectServer = payload.views.selectServer;
    }

    let recentEmojis = initialState.views.recentEmojis;
    if (payload.views.recentEmojis) {
        recentEmojis = payload.views.recentEmojis;
    }

    const nextState = {
        app: {
            build: DeviceInfo.getBuildNumber(),
            version: DeviceInfo.getVersion(),
        },
        entities: {
            channels,
            general,
            teams,
            users,
            preferences,
            search,
            roles,
        },
        views: {
            channel: {
                drafts: channelDrafts,
            },
            i18n,
            team: {
                lastTeamId,
                lastChannelForTeam,
            },
            thread: {
                drafts: threadDrafts,
            },
            selectServer,
            recentEmojis,
        },
    };

    return {
        type: action.type,
        payload: nextState,
        error: action.error,
    };
}

function getLastChannelForTeam(payload) {
    const lastChannelForTeam = {...payload.views.team.lastChannelForTeam};
    const convertLastChannelForTeam = Object.values(lastChannelForTeam).some((value) => !Array.isArray(value));

    if (convertLastChannelForTeam) {
        Object.keys(lastChannelForTeam).forEach((id) => {
            lastChannelForTeam[id] = [lastChannelForTeam[id]];
        });
    }

    return lastChannelForTeam;
}

function cleanupState(action, keepCurrent = false) {
    const {payload: resetPayload} = resetStateForNewVersion(action);
    const {payload} = action;
    const {currentChannelId} = payload.entities.channels;

    const {lastChannelForTeam} = resetPayload.views.team;
    const nextEntities = {
        posts: {
            posts: {},
            postsInChannel: {},
            postsInThread: {},
            reactions: {},
            openGraph: payload.entities.posts.openGraph,
            selectedPostId: payload.entities.posts.selectedPostId,
            currentFocusedPostId: payload.entities.posts.currentFocusedPostId,
        },
        files: {
            files: {},
            fileIdsByPostId: {},
        },
    };

    let retentionPeriod = 0;
    if (resetPayload.entities.general && resetPayload.entities.general.dataRetentionPolicy &&
        resetPayload.entities.general.dataRetentionPolicy.message_deletion_enabled) {
        retentionPeriod = resetPayload.entities.general.dataRetentionPolicy.message_retention_cutoff;
    }

    const postIdsToKeep = Object.values(lastChannelForTeam).reduce((array, channelIds) => {
        const ids = channelIds.reduce((result, id) => {
            // we need to check that the channel id is not already included
            // the reason it can be included is cause at least one of the last channels viewed
            // in a team can be a DM or GM and the id can be duplicate
            if (!nextEntities.posts.postsInChannel[id] && payload.entities.posts.postsInChannel[id]) {
                let postIds;
                if (keepCurrent && currentChannelId === id) {
                    postIds = payload.entities.posts.postsInChannel[id];
                } else {
                    postIds = payload.entities.posts.postsInChannel[id].slice(0, 60);
                }
                nextEntities.posts.postsInChannel[id] = postIds;
                return result.concat(postIds);
            }

            return result;
        }, []);
        return array.concat(ids);
    }, []);

    let searchResults = [];
    let flaggedPosts = [];
    if (payload.entities.search) {
        if (payload.entities.search.results.length) {
            const {results} = payload.entities.search;
            searchResults = results;
            postIdsToKeep.push(...results);
        }

        if (payload.entities.search.flagged.length) {
            const {flagged} = payload.entities.search;
            flaggedPosts = flagged;
            postIdsToKeep.push(...flagged);
        }
    }

    postIdsToKeep.forEach((postId) => {
        const post = payload.entities.posts.posts[postId];

        if (post) {
            if (retentionPeriod && post.create_at < retentionPeriod) {
                const postsInChannel = nextEntities.posts.postsInChannel[post.channel_id] || [];
                const index = postsInChannel.indexOf(postId);
                if (index !== -1) {
                    postsInChannel.splice(index, 1);
                }
                return;
            }

            nextEntities.posts.posts[postId] = post;

            const reaction = payload.entities.posts.reactions[postId];
            if (reaction) {
                nextEntities.posts.reactions[postId] = reaction;
            }

            const fileIds = payload.entities.files.fileIdsByPostId[postId];
            if (fileIds) {
                nextEntities.files.fileIdsByPostId[postId] = fileIds;
                fileIds.forEach((fileId) => {
                    nextEntities.files.files[fileId] = payload.entities.files.files[fileId];
                });
            }

            const postsInThread = payload.entities.posts.postsInThread[postId];
            if (postsInThread) {
                nextEntities.posts.postsInThread[postId] = postsInThread;
            }
        } else {
            // If the post is not in the store we need to remove it from the postsInChannel
            const channelIds = Object.keys(nextEntities.posts.postsInChannel);
            for (let i = 0; i < channelIds.length; i++) {
                const channelId = channelIds[i];
                const posts = nextEntities.posts.postsInChannel[channelId];
                const index = posts.indexOf(postId);
                if (index !== -1) {
                    posts.splice(index, 1);
                    break;
                }
            }
        }
    });

    // remove any pending posts that hasn't failed
    if (payload.entities.posts && payload.entities.posts.pendingPostIds && payload.entities.posts.pendingPostIds.length) {
        const nextPendingPostIds = [...payload.entities.posts.pendingPostIds];
        payload.entities.posts.pendingPostIds.forEach((id) => {
            const posts = nextEntities.posts.posts;
            const post = posts[id];

            if (post) {
                const postsInChannel = [...nextEntities.posts.postsInChannel[post.channel_id]] || [];
                if (!post.failed) {
                    Reflect.deleteProperty(posts, id);
                    const index = postsInChannel.indexOf(id);
                    if (index !== -1) {
                        postsInChannel.splice(index, 1);
                        nextEntities.posts.postsInChannel[post.channel_id] = postsInChannel;
                    }
                    removePendingPost(nextPendingPostIds, id);
                }
            } else {
                removePendingPost(nextPendingPostIds, id);
            }
        });

        nextEntities.posts.pendingPostIds = nextPendingPostIds;
    }

    const nextState = {
        app: resetPayload.app,
        entities: {
            ...nextEntities,
            channels: payload.entities.channels,
            emojis: payload.entities.emojis,
            general: resetPayload.entities.general,
            preferences: resetPayload.entities.preferences,
            search: {
                ...resetPayload.entities.search,
                results: searchResults,
                flagged: flaggedPosts,
            },
            teams: resetPayload.entities.teams,
            users: payload.entities.users,
            roles: resetPayload.entities.roles,
        },
        views: {
            announcement: payload.views.announcement,
            ...resetPayload.views,
            channel: {
                ...resetPayload.views.channel,
                ...payload.views.channel,
            },
        },
    };

    nextState.errors = payload.errors;

    return {
        type: action.type,
        payload: nextState,
        error: action.error,
    };
}

function removePendingPost(pendingPostIds, id) {
    const pendingIndex = pendingPostIds.indexOf(id);
    if (pendingIndex !== -1) {
        pendingPostIds.splice(pendingIndex, 1);
    }
}
