package ru.yandex.webmaster3.monitoring.solomon;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import lombok.RequiredArgsConstructor;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import ru.yandex.solomon_api.model.SolomonAlert;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.openapi.internal.ApiException;
import ru.yandex.webmaster3.monitoring.solomon.api.SolomonControlApiV2Service;
import ru.yandex.webmaster3.monitoring.solomon.dao.WebmasterTriggerPropertiesYDao;
import ru.yandex.webmaster3.monitoring.solomon.dao.WebmasterTriggersYDao;
import ru.yandex.webmaster3.monitoring.solomon.trigger.SolomonTriggerPropertiesSet;
import ru.yandex.webmaster3.monitoring.solomon.trigger.TriggerSubscriber;
import ru.yandex.webmaster3.monitoring.solomon.trigger.data.WebmasterTriggerInfo;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;
import ru.yandex.webmaster3.storage.monitoring.common.MonitoringDataState;
import ru.yandex.webmaster3.storage.monitoring.common.MonitoringDataType;
import ru.yandex.webmaster3.storage.monitoring.common.dao.MonitoringDataStateYDao;

/**
 * @author avhaliullin
 */

@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class WebmasterTriggersService {
    private static final Logger log = LoggerFactory.getLogger(WebmasterTriggersService.class);

    private final WebmasterTriggerPropertiesYDao webmasterTriggerPropertiesYDao;
    private final WebmasterTriggersYDao webmasterTriggersYDao;
    private final SolomonControlApiV2Service solomonControlApiV2Service;
    private final MonitoringDataStateYDao monitoringDataStateYDao;
    @Value("${webmaster3.monitoring.environment}")
    private String environmentType;

    public void shortenTriggerNames() {
        try {
            List<WebmasterTriggerInfo> triggers = listTriggers();
            for (WebmasterTriggerInfo triggerInfo : triggers) {
                String newId = triggerInfo.getId().replace("webmaster", "wmc")
                        .replace("monitoring", "mon")
                        .replace("data-age", "DA")
                        .replace("searchquery", "SQ")
                        .replace("clickhouse", "CH")
                        .replace("cassandra", "CS")
                        .replace("important-urls", "IU")
                        .replace("quality", "qlt")
                        .replace("indexing", "indx")
                        .replace("periodic", "per")
                        .replace("last", "lst")
                        .replaceAll("[^a-zA-Z0-9-_]", "");
                if (!newId.equals(triggerInfo.getId())) {
                    try {
                        solomonControlApiV2Service.deleteTrigger(triggerInfo.getId());
                    } catch (Exception e) {
                        log.error("Failed to delete trigger", e);
                    }
                    webmasterTriggersYDao.updateTrigger(triggerInfo.withId(newId));
                    webmasterTriggersYDao.deleteTrigger(triggerInfo.getId());
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void deleteTrigger(String id) {
        try {
            solomonControlApiV2Service.deleteTrigger(id);
            webmasterTriggersYDao.deleteTrigger(id);
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to delete trigger " + id,
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        } catch (ApiException e) {
            throw new WebmasterException("Failed to delete trigger " + id,
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(getClass(), null), e);
        }
    }

    public WebmasterTriggerInfo getTriggerInfo(String id) {
        try {
            return webmasterTriggersYDao.getTriggerInfo(id);
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to get trigger " + id,
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
    }

    public void updateTrigger(WebmasterTriggerInfo triggerInfo) {
        try {
            webmasterTriggersYDao.updateTrigger(triggerInfo);
            syncRemoteTriggers(Collections.singleton(triggerInfo), getCurrentSubscribers());
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to update trigger " + triggerInfo.getId(),
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
    }

    public List<WebmasterTriggerInfo> listTriggers() {
        try {
            return webmasterTriggersYDao.listAllTriggers();
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to list triggers",
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
    }

    @Scheduled(cron = "0 0 * * * *") // раз в час
    public void syncSubscribers() {
        // Commented out because is not used
/*
        Set<String> remoteSubscribers = getSubscribersStateRecord();
        Set<String> actualSubscribers = dutyService.listCurrentDuties();
        if (!remoteSubscribers.equals(actualSubscribers)) {
            log.info("Updating subscribers. Old state: " + remoteSubscribers + ", new state: " + actualSubscribers);
            if (actualSubscribers.isEmpty()) {
                throw new RuntimeException("There is no subscribers!");
            }
            syncRemoteTriggers(listTriggers(), actualSubscribers.stream()
                    .map(login -> new TriggerSubscriber(TriggerSubscriber.TriggerSubscriberType.GOLEM_SMS, login))
                    .collect(Collectors.toList())
            );
            updateSubscribersStateRecord(actualSubscribers);
        }
*/
    }

    public void syncRemoteTriggers() {
        shortenTriggerNames();
        List<WebmasterTriggerInfo> webmasterTriggers = listTriggers();
        syncRemoteTriggers(webmasterTriggers, getCurrentSubscribers());
    }

    public void syncRemoteTrigger(String triggerId) {
        try {
            WebmasterTriggerInfo triggerInfo = webmasterTriggersYDao.getTriggerInfo(triggerId);
            if (triggerInfo != null) {
                syncRemoteTriggers(Collections.singletonList(triggerInfo), getCurrentSubscribers());
            } else {
                log.info("Not found Trigger with id = {} in database", triggerId);
            }
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to sync trigger " + triggerId,
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
    }

    private void updateSubscribersStateRecord(Set<String> subscribers) {
        try {
            String subscribersRecord = String.join(",", subscribers);
            monitoringDataStateYDao.update(new MonitoringDataState(MonitoringDataType.SUBSCRIBERS_STATE, subscribersRecord, DateTime.now()));
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to update subscribers state record",
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
    }

    private Set<String> getSubscribersStateRecord() {
        try {
            MonitoringDataState dataState = monitoringDataStateYDao.getValue(MonitoringDataType.SUBSCRIBERS_STATE);
            if (dataState == null) {
                return Collections.emptySet();
            }
            String[] subscribersArr = dataState.getValue().split(",");
            return Stream.of(subscribersArr).collect(Collectors.toSet());
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to get subscribers state record",
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
    }

    private void syncRemoteTriggers(Collection<WebmasterTriggerInfo> triggers, List<TriggerSubscriber> subscribers) {
        try {
            SolomonTriggerPropertiesSet propertiesSet = SolomonTriggerPropertiesSet.createFromList(webmasterTriggerPropertiesYDao.listAllProperties());
            List<SolomonAlert> solomonAlerts = triggers.stream()
                    .map(trigger -> WebmasterTriggerUtil.buildSolomonAlert(environmentType, propertiesSet, trigger))
                    .collect(Collectors.toList());
            for (SolomonAlert solomonAlert : solomonAlerts) {
                solomonControlApiV2Service.createOrUpdate(solomonAlert);
            }
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to sync remote triggers",
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        } catch (ApiException e) {
            throw new WebmasterException("Failed to sync remote triggers",
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(getClass(), null), e);
        }
    }

    private List<TriggerSubscriber> getCurrentSubscribers() {
        return getSubscribersStateRecord().stream()
                .map(login -> new TriggerSubscriber(TriggerSubscriber.TriggerSubscriberType.GOLEM_SMS, login))
                .collect(Collectors.toList());
    }
}
