import _ from 'lodash';
import { Promise } from 'rsvp';
import { ActionTypes } from 'constants/Common';
import { SERVICES_WITH_LICENSES } from 'constants/Services';
import Url from 'lib/Url';
import { i18n } from 'lib/i18n';
import getErrors from 'lib/getErrors';

import subscriptionApi from 'api/subscription';
import orgApi from 'api/org';
import servicesApi from 'api/services';

import AuthStore from 'stores/Auth';
import SubscriptionStore from 'stores/Subscription';
import ModalActions from 'actions/Modal';
import SubscriptionAppActions from 'actions/App/Subscription';
import PaymentForm from 'components/Balance/Forms/MakePayment';

import dispatcher from 'dispatcher';

export default {
    toggleService,
    trackServiceStatus,
    stopServiceStatusTracking,
    makePayment,
    createContract,
    activatePromoCode,
    toPayment,
    toDetails,
    toAgreement,
    getPersons,
};

function handleSubscriptionChange(response) {
    const errors = getErrors(response);

    if (!errors.isEmpty()) {
        return { errors, originalError: response };
    }

    dispatcher.dispatch(ActionTypes.RECEIVE_MIXED_DATA, response);
    SubscriptionAppActions.showDetails();
}

function toggleService(id, enable, params) {
    return subscriptionApi.toggleService(id, enable, params)
        .then(response => {
            const errors = getErrors(response);

            if (!errors.isEmpty()) {
                return { errors };
            }

            dispatcher.dispatch(ActionTypes.RECEIVE_MIXED_DATA, response);

            if (enable) {
                // при отключении сервиса (enable = false) он гарантированно отключится
                trackServiceStatus(id);
            }

            return response;
        });
}

function pickServices(data) {
    const orgId = AuthStore.getOrganizationId();

    return _.get(data, `organizations.${orgId}.services`, []);
}

const serviceStatusTrackingMap = {};
let serviceStatusTrackingIterationCount = 0;
let isServiceStatusTrackingRunning;
let nextServiceStatusTrackingTick;

const TRACKABLE_SERVICES = [
    'tracker',
    'wiki',
    'forms',
    'calendar',
    'disk',
    'mail',
    'staff',
    'yamb',
];

const SERVICE_STATUS_TRACKING_EARLY_TIMEOUT = 1000;
const SERVICE_STATUS_TRACKING_LATER_TIMEOUT = 10000;

function trackServiceStatus(serviceSlug, targetStatus = null) {
    // вызов этого метода складывает переданный сервис в общий индекс
    // обновляющихся сервисов
    if (serviceSlug !== undefined) {
        serviceStatusTrackingMap[serviceSlug] = targetStatus;
    }

    // метод может вызываться несколько раз и из разных мест, но если
    // рекурсивный опрос статуса сервисов уже начался, вызов этого метода
    // остановится на добавлении переданного сервиса в очередь
    // и не начнёт новый параллельный опрос
    if (isServiceStatusTrackingRunning) {
        return;
    }

    performServiceStatusTracking(serviceSlug);
}

function stopServiceStatusTracking() {
    isServiceStatusTrackingRunning = false;
    clearTimeout(nextServiceStatusTrackingTick);
}

/**
 * Возвращает находится ли сервис в процессе включения или выключения
 * @param {Object|undefined} serviceData
 * @returns {Boolean}
 */
function isServiceReady(serviceData) {
    // если данных нет значит сервис выключен и не находится в статусе включения
    // если данные есть то смотрим на признак ready
    return !serviceData || _.get(serviceData, 'ready', false);
}

function performServiceStatusTracking(serviceSlug) {
    if (serviceSlug === undefined) {
        AuthStore.getServices().forEach(service => {
            if (!isServiceReady(service) && TRACKABLE_SERVICES.indexOf(service.slug) !== -1) {
                // 'ready' === false означает, что сервис обновляется, но
                // не указывает, отключается он или включается, поэтому
                // вносим его в индекс с неопределённым конечным статусом null
                serviceStatusTrackingMap[service.slug] = null;
            }
        });
    }

    // заканчиваем, когда индекс обновляющихся сервисов опустеет
    if (Object.keys(serviceStatusTrackingMap).length === 0) {
        isServiceStatusTrackingRunning = false;

        return;
    }

    isServiceStatusTrackingRunning = true;

    orgApi.read({ id: AuthStore.getOrganizationId() })
        .then(response => {
            const receivedServices = pickServices(response);
            const receivedServiceMap = {};
            let updated;
            let requiresPricing;

            receivedServices.forEach(service => {
                const isUpdatingTrackableService =
                    !isServiceReady(service) &&
                    TRACKABLE_SERVICES.indexOf(service.slug) !== -1;

                if (isUpdatingTrackableService && !(service.slug in serviceStatusTrackingMap)) {
                    // если среди сервисов из ответа появился обновляющийся сервис
                    // не из очереди, добавляем его в очередь
                    serviceStatusTrackingMap[service.slug] = null;
                    updated = true;
                }
                if (service.slug in serviceStatusTrackingMap && isServiceReady(service)) {
                    // сервис включён, убираем из очереди
                    delete serviceStatusTrackingMap[service.slug];
                    updated = true;
                }
                receivedServiceMap[service.slug] = service;

                // запрашиваем pricing только для включенных сервисов с лицензией
                if (updated && SERVICES_WITH_LICENSES.indexOf(service.slug) !== -1) {
                    requiresPricing = true;
                }
            });

            Object.keys(serviceStatusTrackingMap).forEach(trackingServiceSlug => {
                if (!receivedServiceMap[trackingServiceSlug]) {
                    // сервис отключён, убираем из очереди
                    delete serviceStatusTrackingMap[trackingServiceSlug];
                    updated = true;
                }
            });

            if (updated) {
                // если все или часть сервисов обновились, обновляем информацию
                // связанную с подключением новых сервисов
                Promise.all([
                    servicesApi.readMenu(),
                    requiresPricing ? subscriptionApi.getPricing() : null,
                    requiresPricing ? subscriptionApi.getState() : null,
                ])
                    .then(updates => {
                        dispatcher.dispatch(
                            ActionTypes.RECEIVE_MIXED_DATA,
                            _.merge({}, updates[0], updates[1], updates[2], response)
                        );
                    });
            }

            if (Object.keys(serviceStatusTrackingMap).length === 0) {
                stopServiceStatusTracking();
            } else {
                let timeout;

                // после нескольких итераций опроса увеличиваем таймаут
                // между итерациями
                if (serviceStatusTrackingIterationCount++ < 20) {
                    timeout = SERVICE_STATUS_TRACKING_EARLY_TIMEOUT;
                } else {
                    timeout = SERVICE_STATUS_TRACKING_LATER_TIMEOUT;
                }
                nextServiceStatusTrackingTick = setTimeout(performServiceStatusTracking, timeout);
            }
        });
}

function makePayment(data) {
    // если контракт уже есть, то сразу переключаем тариф
    // после подтверждения, без заполнения форм
    if (SubscriptionStore.hasContract()) {
        return subscriptionApi.pay(_.assign({ return_path: location.href }, data))
            .then(response => {
                const errors = getErrors(response);

                if (!errors.isEmpty()) {
                    return { errors };
                }

                return response;
            });
    }

    return toAgreement('balance');
}

function activatePromoCode(data) {
    return subscriptionApi.activatePromoCode(data)
        .then(handleSubscriptionChange);
}

function createContract(data) {
    return subscriptionApi.createContract(data)
        .then(handleSubscriptionChange);
}

function getPersons(data) {
    return subscriptionApi.getPersons(data)
        .then(handleSubscriptionChange);
}

function toPayment() {
    ModalActions.open({
        title: i18n('subscription.balance.payment.title'),
        component: PaymentForm,
        props: {
            mod: 'popup',
            onSubmit() {
                ModalActions.close();
            },
            onCancel() {
                ModalActions.close();
            },
        },
    });
}

function toAgreement(source) {
    const path = `balance/contract${source ? `?source=${source}` : ''}`;

    return Promise.resolve().then(() => {
        Url.open(Url.getPath(path));
    });
}

function jumpTo(path) {
    Url.open(Url.getLocation(path));
}

function toDetails() {
    const retpath = Url.getQueryParam('retpath');

    if (retpath) {
        return jumpTo(retpath);
    }

    const [source, section] = Url.getQueryParam('source').split('.');

    if (['tracker', 'disk'].includes(source)) {
        return jumpTo(`/portal/services/${source}?from=agreement&section=${section}`);
    }

    jumpTo('/portal/balance');
}
