package ru.yandex.webmaster.common.host;

import java.io.IOException;
import java.util.List;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Required;

import ru.yandex.misc.db.q.SqlCondition;
import ru.yandex.misc.db.q.SqlLimits;
import ru.yandex.webmaster.common.host.dao.TblHostEventConditions;
import ru.yandex.webmaster.common.host.dao.TblHostEventDao;
import ru.yandex.wmconsole.data.info.BriefHostInfo;
import ru.yandex.wmconsole.util.PageUtils;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.InternalProblem;

/**
 * @author aherman
 */
public class HostEventService {
    private TblHostEventDao tblHostEventDao;

    private final ObjectMapper OM = new ObjectMapper()
            .setSerializationInclusion(JsonInclude.Include.NON_NULL)
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

    public void hostIsAdded(BriefHostInfo briefHostInfo, long userId, String yaDomain, String userIp, String frontendIp,
            String yandexUid) throws InternalException
    {
        AddHostEventData addHostEventData = new AddHostEventData(userIp, frontendIp, yaDomain, yandexUid);
        String rawData = dataToString(addHostEventData);
        HostEvent hostEvent =
                new HostEvent(-1, briefHostInfo.getId(), null, HostEventType.HOST_ADDED, userId, userId, rawData);
        tblHostEventDao.save(hostEvent);
    }

    public void hostIsVerified(long hostId, long forUserId, long byUserId) throws InternalException {
        HostEvent hostEvent = new HostEvent(-1, hostId, DateTime.now(), HostEventType.HOST_VERIFIED, byUserId, forUserId, null);
        tblHostEventDao.save(hostEvent);
    }

    public void hostRightsIsDelegated(long hostId, long forUserId, long byUserId, String yandexUid, boolean mayRedelegate)
            throws InternalException
    {
        DelegateRightsEventData data = new DelegateRightsEventData(yandexUid, mayRedelegate);
        String rawData = dataToString(data);
        HostEvent hostEvent =
                new HostEvent(-1, hostId, DateTime.now(), HostEventType.HOST_VERIFICATION_DELEGATED,
                        byUserId, forUserId, rawData);
        tblHostEventDao.save(hostEvent);
    }

    public void hostRightsRovoked(long hostId, long forUserId, long byUserId) throws InternalException {
        HostEvent hostEvent =
                new HostEvent(-1, hostId, DateTime.now(), HostEventType.HOST_VERIFICATION_REVOKED,
                        byUserId, forUserId, null);
        tblHostEventDao.save(hostEvent);
    }

    public void userHostRetryEvent(long hostId, long forUserId) throws InternalException {
        HostEvent hostEvent =
                new HostEvent(-1, hostId, DateTime.now(), HostEventType.WEBMASTER3_USER_HOST_RETRY_EVENT, 0l, forUserId, null);
        tblHostEventDao.save(hostEvent);
    }

    String dataToString(Object object) throws InternalException {
        if (object == null) {
            throw new NullPointerException("Object must not be null");
        }
        String rawData;
        try {
            rawData = getObjectName(object) + OM.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            throw new InternalException(InternalProblem.PROCESSING_ERROR, "Unable to save additional data", e);
        }
        return rawData;
    }

    private static String getObjectName(Object object) {
        return getObjectName(object.getClass());
    }

    private static String getObjectName(Class<?> clazz) {
        return clazz.getSimpleName();
    }

    public <T> T getEventData(HostEvent hostEvent, Class<T> clazz) throws InternalException {
        String rawData = hostEvent.getRawData();
        if (rawData == null) {
            return null;
        }
        int braceStart = rawData.indexOf('{');
        if (braceStart < 0) {
            return null;
        }

        String expectedName = getObjectName(clazz);
        String actualDataName = rawData.substring(0, braceStart);
        if (!expectedName.equals(actualDataName)) {
            throw new IllegalArgumentException("Unexpected data: expected=" + expectedName + " actual=" + actualDataName);
        }
        try {
            return OM.readValue(rawData.substring(braceStart), clazz);
        } catch (IOException e) {
            throw new InternalException(InternalProblem.PROCESSING_ERROR, "Unable to parse data", e);
        }
    }

    public List<HostEvent> findHostEvents(BriefHostInfo briefHostInfo, Set<HostEventType> eventTypes,
            Long eventForUserId, PageUtils.Pager page)
            throws InternalException
    {
        SqlLimits limits = SqlLimits.range(page.toRangeStart(), page.getPageSize());
        SqlCondition c = TblHostEventConditions.hostId(briefHostInfo);
        if (!eventTypes.isEmpty()) {
            c = c.and(TblHostEventConditions.eventTypeIn(eventTypes));
        }
        if (eventForUserId != null) {
            c = c.and(TblHostEventConditions.eventFroUserId(eventForUserId));
        }

        return tblHostEventDao.getEvents(c, limits, TblHostEventDao.ORDER_BY_EVENT_ID.invert());
    }

    public int countHostEvents(BriefHostInfo briefHostInfo, Set<HostEventType> eventTypes, Long eventForUserId) throws InternalException {
        SqlCondition c = TblHostEventConditions.hostId(briefHostInfo);
        if (!eventTypes.isEmpty()) {
            c = c.and(TblHostEventConditions.eventTypeIn(eventTypes));
        }
        if (eventForUserId != null) {
            c = c.and(TblHostEventConditions.eventFroUserId(eventForUserId));
        }
        return tblHostEventDao.countEvents(c);
    }

    @Required
    public void setTblHostEventDao(TblHostEventDao tblHostEventDao) {
        this.tblHostEventDao = tblHostEventDao;
    }
}
