import { fromJS, Map } from 'immutable';

// Action constants
const CONFIRM_OPERATION = 'transaction/CONFIRM_OPERATION';
const HIDE_CONFIRM = 'transaction/HIDE_CONFIRM';
export const BROADCAST_OPERATION = 'transaction/BROADCAST_OPERATION';
export const UPDATE_AUTHORITIES = 'transaction/UPDATE_AUTHORITIES';
const ERROR = 'transaction/ERROR'; // Has a watcher in SagaShared
const DELETE_ERROR = 'transaction/DELETE_ERROR';
const DISMISS_ERROR = 'transaction/DISMISS_ERROR';
const SET = 'transaction/SET';
const REMOVE = 'transaction/REMOVE';
// Saga-related
export const RECOVER_ACCOUNT = 'transaction/RECOVER_ACCOUNT';
const defaultState = fromJS({
    operations: [],
    status: { key: '', error: false, busy: false },
    errors: {
        bandwidthError: false,
    },
});

export default function reducer(state = defaultState, action) {
    const { payload } = action;

    switch (action.type) {
        case CONFIRM_OPERATION: {
            const operation = fromJS(payload.operation);
            const { confirm } = payload;
            const { warning } = payload;
            return state.merge({
                show_confirm_modal: true,
                confirmBroadcastOperation: operation,
                confirmErrorCallback: payload.errorCallback,
                confirm,
                warning,
            });
        }

        case HIDE_CONFIRM:
            return state.merge({
                show_confirm_modal: false,
                confirmBroadcastOperation: undefined,
                confirm: undefined,
            });

        case BROADCAST_OPERATION:
            // See TransactionSaga.js
            return state;

        case UPDATE_AUTHORITIES:
            return state;

        case ERROR: {
            const { operations, error, errorCallback } = payload;

            let errorStr = error.toString();
            let errorKey = 'Transaction broadcast error.';
            for (const [type, operation] of operations) {
                switch (type) {
                    case 'transfer':
                        if (/get_balance/.test(errorStr)) {
                            errorKey = 'Insufficient balance.';
                        } else {
                            for (
                                let ei = 0;
                                ei < error.data.stack.length;
                                ei += 1
                            ) {
                                const errorStackItem = error.data.stack[ei];

                                if (
                                    errorStackItem.data.name === operation.to &&
                                    errorStackItem.data.what === 'unknown key'
                                ) {
                                    errorKey = 'Unknown recipient';
                                    break;
                                }
                            }
                        }
                        break;
                    case 'withdraw_vesting':
                        if (
                            /Account registered by another account requires 10x account creation fee worth of Hive Power/.test(
                                errorStr
                            )
                        )
                            errorKey =
                                'Account requires 10x the account creation fee in Hive Power (approximately 30 HP) before it can power down.';
                        break;
                    default:
                        break;
                }
                if (state.hasIn(['TransactionError', type + '_listener'])) {
                    state = state.setIn(
                        ['TransactionError', type],
                        fromJS({ key: errorKey, exception: errorStr })
                    );
                } else {
                    if (error.message) {
                        // TODO: This reformatting could be better, in most cases, errorKey and errorString end up being similar if not identical.
                        // Depends on FC_ASSERT formatting
                        // https://github.com/steemit/steemit.com/issues/222
                        const err_lines = error.message.split('\n');
                        if (err_lines.length > 2) {
                            errorKey = err_lines[1];
                            const txt = errorKey.split(': ');
                            if (
                                txt.length &&
                                txt[txt.length - 1].trim() !== ''
                            ) {
                                errorKey = errorStr = txt[txt.length - 1];
                            } else
                                errorStr = `Transaction failed: ${err_lines[1]}`;
                        }
                    }
                    // TODO: This would perhaps be better expressed as a Case, Switch statement.
                    // TODO: The precise reason for why this clipping needs to happen is unclear.
                    if (errorStr.length > 200)
                        errorStr = errorStr.substring(0, 200);
                    // Catch for unknown key better error handling
                    if (/unknown key: /.test(errorKey)) {
                        errorKey = "Hive account doesn't exist.";
                        errorStr =
                            "Transaction failed: Hive account doesn't exist.";
                    }
                    // Catch for invalid active authority
                    if (/Missing Active Authority /.test(errorKey)) {
                        errorKey = 'Not your valid active key.';
                        errorStr =
                            'Transaction failed: Not your valid active key.';
                    }
                    // TODO: refactor this so that the keys are consistent and sane, i.e. do not include user name in error key.
                    state = state.update('errors', (errors) => {
                        return errors
                            ? errors.set(errorKey, errorStr)
                            : Map({ [errorKey]: errorStr });
                    });
                    // Sane error key for the bandwidth error.
                    if (
                        errorKey.includes('bandwidth') ||
                        errorStr.includes('bandwidth') ||
                        errorStr.includes('RC') // Error key for HF-20 insufficient RC error, #3001.
                    ) {
                        state = state.setIn(['errors', 'bandwidthError'], true);
                    }
                }
            }

            if (errorCallback) {
                errorCallback(errorKey);
            } else {
                throw new Error(
                    'PANIC: no callback registered to handle error ' + errorKey
                );
            }

            return state;
        }

        case DELETE_ERROR:
            return state.deleteIn(['errors', payload.key]);

        case DISMISS_ERROR:
            return state.setIn(['errors', payload.key], false);

        case SET:
            return state.setIn(
                Array.isArray(payload.key) ? payload.key : [payload.key],
                fromJS(payload.value)
            );

        case REMOVE:
            return state.removeIn(
                Array.isArray(payload.key) ? payload.key : [payload.key]
            );

        default:
            return state;
    }
}

// Action creators
export const confirmOperation = (payload) => ({
    type: CONFIRM_OPERATION,
    payload,
});

export const hideConfirm = (payload) => ({
    type: HIDE_CONFIRM,
    payload,
});

export const broadcastOperation = (payload) => ({
    type: BROADCAST_OPERATION,
    payload,
});

export const updateAuthorities = (payload) => ({
    type: UPDATE_AUTHORITIES,
    payload,
});

export const error = (payload) => ({
    type: ERROR,
    payload,
});

export const deleteError = (payload) => ({
    type: DELETE_ERROR,
    payload,
});

export const dismissError = (payload) => ({
    type: DISMISS_ERROR,
    payload,
});

export const set = (payload) => ({
    type: SET,
    payload,
});

export const remove = (payload) => ({
    type: REMOVE,
    payload,
});

export const recoverAccount = (payload) => ({
    type: RECOVER_ACCOUNT,
    payload,
});
