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

import {combineReducers} from 'redux';
import {AdminTypes, UserTypes} from 'action_types';
import {Stats} from '../../constants';
import PluginState from '../../constants/plugins';

import {GenericAction} from 'types/actions';
import {ClusterInfo, AnalyticsRow} from 'types/admin';
import {Audit} from 'types/audits';
import {Compliance} from 'types/compliance';
import {AdminConfig, EnvironmentConfig} from 'types/config';
import {MixedUnlinkedGroupRedux} from 'types/groups';
import {PluginRedux, PluginStatusRedux} from 'types/plugins';
import {SamlCertificateStatus, SamlMetadataResponse} from 'types/saml';
import {Team} from 'types/teams';
import {UserAccessToken, UserProfile} from 'types/users';
import {Dictionary, RelationOneToOne} from 'types/utilities';

function logs(state: string[] = [], action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_LOGS: {
        return action.data;
    }
    case UserTypes.LOGOUT_SUCCESS:
        return [];

    default:
        return state;
    }
}

function audits(state: Dictionary<Audit> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_AUDITS: {
        const nextState = {...state};
        for (const audit of action.data) {
            nextState[audit.id] = audit;
        }
        return nextState;
    }
    case UserTypes.LOGOUT_SUCCESS:
        return {};

    default:
        return state;
    }
}

function config(state: Partial<AdminConfig> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_CONFIG: {
        return action.data;
    }
    case AdminTypes.ENABLED_PLUGIN: {
        const nextPluginSettings = {...state.PluginSettings!};
        const nextPluginStates = {...nextPluginSettings.PluginStates};
        nextPluginStates[action.data] = {Enable: true};
        nextPluginSettings.PluginStates = nextPluginStates;
        return {...state, PluginSettings: nextPluginSettings};
    }
    case AdminTypes.DISABLED_PLUGIN: {
        const nextPluginSettings = {...state.PluginSettings!};
        const nextPluginStates = {...nextPluginSettings.PluginStates};
        nextPluginStates[action.data] = {Enable: false};
        nextPluginSettings.PluginStates = nextPluginStates;
        return {...state, PluginSettings: nextPluginSettings};
    }
    case UserTypes.LOGOUT_SUCCESS:
        return {};

    default:
        return state;
    }
}

function environmentConfig(state: Partial<EnvironmentConfig> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_ENVIRONMENT_CONFIG: {
        return action.data;
    }
    case UserTypes.LOGOUT_SUCCESS:
        return {};

    default:
        return state;
    }
}

function complianceReports(state: Dictionary<Compliance> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_COMPLIANCE_REPORT: {
        const nextState = {...state};
        nextState[action.data.id] = action.data;
        return nextState;
    }
    case AdminTypes.RECEIVED_COMPLIANCE_REPORTS: {
        const nextState = {...state};
        for (const report of action.data) {
            nextState[report.id] = report;
        }
        return nextState;
    }
    case UserTypes.LOGOUT_SUCCESS:
        return {};

    default:
        return state;
    }
}

function clusterInfo(state: ClusterInfo[] = [], action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_CLUSTER_STATUS: {
        return action.data;
    }
    case UserTypes.LOGOUT_SUCCESS:
        return [];

    default:
        return state;
    }
}

function samlCertStatus(state: Partial<SamlCertificateStatus> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_SAML_CERT_STATUS: {
        return action.data;
    }
    case UserTypes.LOGOUT_SUCCESS:
        return {};

    default:
        return state;
    }
}

export function convertAnalyticsRowsToStats(data: AnalyticsRow[], name: string): Dictionary<number | AnalyticsRow[]> {
    const stats: any = {};
    const clonedData = [...data];

    if (name === 'post_counts_day') {
        clonedData.reverse();
        stats[Stats.POST_PER_DAY] = clonedData;
        return stats;
    }

    if (name === 'bot_post_counts_day') {
        clonedData.reverse();
        stats[Stats.BOT_POST_PER_DAY] = clonedData;
        return stats;
    }

    if (name === 'user_counts_with_posts_day') {
        clonedData.reverse();
        stats[Stats.USERS_WITH_POSTS_PER_DAY] = clonedData;
        return stats;
    }

    clonedData.forEach((row) => {
        let key;
        switch (row.name) {
        case 'channel_open_count':
            key = Stats.TOTAL_PUBLIC_CHANNELS;
            break;
        case 'channel_private_count':
            key = Stats.TOTAL_PRIVATE_GROUPS;
            break;
        case 'post_count':
            key = Stats.TOTAL_POSTS;
            break;
        case 'unique_user_count':
            key = Stats.TOTAL_USERS;
            break;
        case 'inactive_user_count':
            key = Stats.TOTAL_INACTIVE_USERS;
            break;
        case 'team_count':
            key = Stats.TOTAL_TEAMS;
            break;
        case 'total_websocket_connections':
            key = Stats.TOTAL_WEBSOCKET_CONNECTIONS;
            break;
        case 'total_master_db_connections':
            key = Stats.TOTAL_MASTER_DB_CONNECTIONS;
            break;
        case 'total_read_db_connections':
            key = Stats.TOTAL_READ_DB_CONNECTIONS;
            break;
        case 'daily_active_users':
            key = Stats.DAILY_ACTIVE_USERS;
            break;
        case 'monthly_active_users':
            key = Stats.MONTHLY_ACTIVE_USERS;
            break;
        case 'file_post_count':
            key = Stats.TOTAL_FILE_POSTS;
            break;
        case 'hashtag_post_count':
            key = Stats.TOTAL_HASHTAG_POSTS;
            break;
        case 'incoming_webhook_count':
            key = Stats.TOTAL_IHOOKS;
            break;
        case 'outgoing_webhook_count':
            key = Stats.TOTAL_OHOOKS;
            break;
        case 'command_count':
            key = Stats.TOTAL_COMMANDS;
            break;
        case 'session_count':
            key = Stats.TOTAL_SESSIONS;
            break;
        case 'registered_users':
            key = Stats.REGISTERED_USERS;
            break;
        }

        if (key) {
            stats[key] = row.value;
        }
    });

    return stats;
}

function analytics(state: Dictionary<number | AnalyticsRow[]> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_SYSTEM_ANALYTICS: {
        const stats = convertAnalyticsRowsToStats(action.data, action.name);
        return {...state, ...stats};
    }
    case UserTypes.LOGOUT_SUCCESS:
        return {};

    default:
        return state;
    }
}

function teamAnalytics(state: RelationOneToOne<Team, Dictionary<number | AnalyticsRow[]>> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_TEAM_ANALYTICS: {
        const nextState = {...state};
        const stats = convertAnalyticsRowsToStats(action.data, action.name);
        const analyticsForTeam = {...(nextState[action.teamId] || {}), ...stats};
        nextState[action.teamId] = analyticsForTeam;
        return nextState;
    }
    case UserTypes.LOGOUT_SUCCESS:
        return {};

    default:
        return state;
    }
}

function userAccessTokens(state: Dictionary<UserAccessToken> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_USER_ACCESS_TOKEN: {
        return {...state, [action.data.id]: action.data};
    }
    case AdminTypes.RECEIVED_USER_ACCESS_TOKENS_FOR_USER: {
        const nextState: any = {};

        for (const uat of action.data) {
            nextState[uat.id] = uat;
        }

        return {...state, ...nextState};
    }
    case AdminTypes.RECEIVED_USER_ACCESS_TOKENS: {
        const nextState: any = {};

        for (const uat of action.data) {
            nextState[uat.id] = uat;
        }

        return {...state, ...nextState};
    }
    case UserTypes.REVOKED_USER_ACCESS_TOKEN: {
        const nextState = {...state};
        Reflect.deleteProperty(nextState, action.data);
        return {...nextState};
    }
    case UserTypes.ENABLED_USER_ACCESS_TOKEN: {
        const token = {...state[action.data], is_active: true};
        return {...state, [action.data]: token};
    }
    case UserTypes.DISABLED_USER_ACCESS_TOKEN: {
        const token = {...state[action.data], is_active: false};
        return {...state, [action.data]: token};
    }
    case UserTypes.LOGOUT_SUCCESS:
        return {};
    default:
        return state;
    }
}

function userAccessTokensByUser(state: RelationOneToOne<UserProfile, Dictionary<UserAccessToken>> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_USER_ACCESS_TOKEN: { // UserAccessToken
        const nextUserState: UserAccessToken | Dictionary<UserAccessToken> = {...(state[action.data.user_id] || {})};
        nextUserState[action.data.id] = action.data;

        return {...state, [action.data.user_id]: nextUserState};
    }
    case AdminTypes.RECEIVED_USER_ACCESS_TOKENS_FOR_USER: { // UserAccessToken[]
        const nextUserState = {...(state[action.userId] || {})};

        for (const uat of action.data) {
            nextUserState[uat.id] = uat;
        }

        return {...state, [action.userId]: nextUserState};
    }
    case AdminTypes.RECEIVED_USER_ACCESS_TOKENS: { // UserAccessToken[]
        const nextUserState: any = {};

        for (const uat of action.data) {
            nextUserState[uat.user_id] = nextUserState[uat.user_id] || {};
            nextUserState[uat.user_id][uat.id] = uat;
        }

        return {...state, ...nextUserState};
    }
    case UserTypes.REVOKED_USER_ACCESS_TOKEN: {
        const userIds = Object.keys(state);
        for (let i = 0; i < userIds.length; i++) {
            const userId = userIds[i];
            if (state[userId] && state[userId][action.data]) {
                const nextUserState = {...state[userId]};
                Reflect.deleteProperty(nextUserState, action.data);
                return {...state, [userId]: nextUserState};
            }
        }

        return state;
    }
    case UserTypes.ENABLED_USER_ACCESS_TOKEN: {
        const userIds = Object.keys(state);
        for (let i = 0; i < userIds.length; i++) {
            const userId = userIds[i];
            if (state[userId] && state[userId][action.data]) {
                const nextUserState = {...state[userId]};
                const token = {...nextUserState[action.data], is_active: true};
                nextUserState[token.id] = token;
                return {...state, [userId]: nextUserState};
            }
        }

        return state;
    }
    case UserTypes.DISABLED_USER_ACCESS_TOKEN: {
        const userIds = Object.keys(state);
        for (let i = 0; i < userIds.length; i++) {
            const userId = userIds[i];
            if (state[userId] && state[userId][action.data]) {
                const nextUserState = {...state[userId]};
                const token = {...nextUserState[action.data], is_active: false};
                nextUserState[token.id] = token;
                return {...state, [userId]: nextUserState};
            }
        }

        return state;
    }
    case UserTypes.LOGOUT_SUCCESS:
        return {};

    default:
        return state;
    }
}

function plugins(state: Dictionary<PluginRedux> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_PLUGINS: {
        const nextState = {...state};
        const activePlugins = action.data.active;
        for (const plugin of activePlugins) {
            nextState[plugin.id] = {...plugin, active: true};
        }

        const inactivePlugins = action.data.inactive;
        for (const plugin of inactivePlugins) {
            nextState[plugin.id] = {...plugin, active: false};
        }
        return nextState;
    }
    case AdminTypes.REMOVED_PLUGIN: {
        const nextState = {...state};
        Reflect.deleteProperty(nextState, action.data);
        return nextState;
    }
    case AdminTypes.ENABLED_PLUGIN: {
        const nextState = {...state};
        const plugin = nextState[action.data];
        if (plugin && !plugin.active) {
            nextState[action.data] = {...plugin, active: true};
            return nextState;
        }
        return state;
    }
    case AdminTypes.DISABLED_PLUGIN: {
        const nextState = {...state};
        const plugin = nextState[action.data];
        if (plugin && plugin.active) {
            nextState[action.data] = {...plugin, active: false};
            return nextState;
        }
        return state;
    }
    case UserTypes.LOGOUT_SUCCESS:
        return {};

    default:
        return state;
    }
}

function pluginStatuses(state: Dictionary<PluginStatusRedux> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_PLUGIN_STATUSES: {
        const nextState: any = {};

        for (const plugin of (action.data || [])) {
            const id = plugin.plugin_id;

            // The plugin may be in different states across the cluster. Pick the highest one to
            // surface an error.
            const pluginState = Math.max((nextState[id] && nextState[id].state) || 0, plugin.state);

            const instances = [
                ...((nextState[id] && nextState[id].instances) || []),
                {
                    cluster_id: plugin.cluster_id,
                    version: plugin.version,
                    state: plugin.state,
                },
            ];

            nextState[id] = {
                id,
                name: (nextState[id] && nextState[id].name) || plugin.name,
                description: (nextState[id] && nextState[id].description) || plugin.description,
                version: (nextState[id] && nextState[id].version) || plugin.version,
                active: pluginState > 0,
                state: pluginState,
                instances,
            };
        }

        return nextState;
    }

    case AdminTypes.ENABLE_PLUGIN_REQUEST: {
        const pluginId = action.data;
        if (!state[pluginId]) {
            return state;
        }

        return {
            ...state,
            [pluginId]: {
                ...state[pluginId],
                state: PluginState.PLUGIN_STATE_STARTING,
            },
        };
    }

    case AdminTypes.DISABLE_PLUGIN_REQUEST: {
        const pluginId = action.data;
        if (!state[pluginId]) {
            return state;
        }

        return {
            ...state,
            [pluginId]: {
                ...state[pluginId],
                state: PluginState.PLUGIN_STATE_STOPPING,
            },
        };
    }

    case AdminTypes.REMOVED_PLUGIN: {
        const pluginId = action.data;
        if (!state[pluginId]) {
            return state;
        }

        const nextState = {...state};
        Reflect.deleteProperty(nextState, pluginId);

        return nextState;
    }

    case UserTypes.LOGOUT_SUCCESS:
        return {};

    default:
        return state;
    }
}

function ldapGroupsCount(state = 0, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_LDAP_GROUPS:
        return action.data.count;
    case UserTypes.LOGOUT_SUCCESS:
        return 0;
    default:
        return state;
    }
}

function ldapGroups(state: Dictionary<MixedUnlinkedGroupRedux> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_LDAP_GROUPS: {
        const nextState: any = {};
        for (const group of action.data.groups) {
            nextState[group.primary_key] = group;
        }
        return nextState;
    }
    case AdminTypes.LINKED_LDAP_GROUP: {
        const nextState = {...state};
        if (nextState[action.data.primary_key]) {
            nextState[action.data.primary_key] = action.data;
        }
        return nextState;
    }
    case AdminTypes.UNLINKED_LDAP_GROUP: {
        const nextState = {...state};
        if (nextState[action.data]) {
            nextState[action.data] = {
                ...nextState[action.data],
                mattermost_group_id: undefined,
                has_syncables: undefined,
                failed: false,
            };
        }
        return nextState;
    }
    case AdminTypes.LINK_LDAP_GROUP_FAILURE: {
        const nextState = {...state};
        if (nextState[action.data]) {
            nextState[action.data] = {
                ...nextState[action.data],
                failed: true,
            };
        }
        return nextState;
    }
    case AdminTypes.UNLINK_LDAP_GROUP_FAILURE: {
        const nextState = {...state};
        if (nextState[action.data]) {
            nextState[action.data] = {
                ...nextState[action.data],
                failed: true,
            };
        }
        return nextState;
    }
    case UserTypes.LOGOUT_SUCCESS:
        return {};

    default:
        return state;
    }
}

function samlMetadataResponse(state: Partial<SamlMetadataResponse> = {}, action: GenericAction) {
    switch (action.type) {
    case AdminTypes.RECEIVED_SAML_METADATA_RESPONSE: {
        return action.data;
    }
    default:
        return state;
    }
}

export default combineReducers({

    // array of strings each representing a log entry
    logs,

    // object where every key is an audit id and has an object with audit details
    audits,

    // object representing the server configuration
    config,

    // object representing which fields of the server configuration were set through the environment config
    environmentConfig,

    // object where every key is a report id and has an object with report details
    complianceReports,

    // array of cluster status data
    clusterInfo,

    // object with certificate type as keys and boolean statuses as values
    samlCertStatus,

    // object with analytic categories as types and numbers as values
    analytics,

    // object with team ids as keys and analytics objects as values
    teamAnalytics,

    // object with user ids as keys and objects, with token ids as keys, and
    // user access tokens as values without actual token
    userAccessTokensByUser,

    // object with token ids as keys, and user access tokens as values without actual token
    userAccessTokens,

    // object with plugin ids as keys and objects representing plugin manifests as values
    plugins,

    // object with plugin ids as keys and objects representing plugin statuses across the cluster
    pluginStatuses,

    // object representing the ldap groups
    ldapGroups,

    // total ldap groups
    ldapGroupsCount,

    // object representing the metadata response obtained from the IdP
    samlMetadataResponse,
});
