package ru.yandex.webmaster3.storage.searchquery;

import com.datastax.driver.core.utils.UUIDs;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.Instant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.searchquery.*;
import ru.yandex.webmaster3.core.searchquery.history.QueryGroupState;
import ru.yandex.webmaster3.storage.searchquery.dao.*;

import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * @author aherman
 */
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class QueryGroupService {
    private final QueryGroupYDao queryGroupYDao;
    private final QueriesYDao queriesYDao;
    private final QueriesGroupsRelationYDao queriesGroupsRelationYDao;
    private final QueryGroupStateYDao queryGroupStateYDao;
    private final QueryGroupLimitsYDao queryGroupLimitsYDao;

    public QueryGroup addGroup(WebmasterHostId hostId, String name, List<QueryFilter> queryFilter) {
        UUID groupUUID = UUIDs.timeBased();
        Instant now = Instant.now();
        queryGroupYDao.addGroup(hostId, groupUUID, name, queryFilter, now);
        return new QueryGroup(new QueryGroupId(hostId, groupUUID), name, false, queryFilter, now, now);
    }

    public QueryGroup getGroup(QueryGroupId groupId) {
        if (groupId.isSpecial()) {
            return fromSpecialGroup(groupId);
        }
        return queryGroupYDao.getGroup(groupId);
    }

    public int getGroupCount(WebmasterHostId hostId) {
        return queryGroupYDao.getGroupCount(hostId);
    }

    public boolean isGroupLimitReached(WebmasterHostId hostId) {
        return getGroupCount(hostId) >= QueryGroup.GROUP_LIMIT;
    }

    public QueryGroup getGroupByName(WebmasterHostId hostId, String groupName) {
        List<QueryGroup> groups = queryGroupYDao.listGroups(hostId);
        for (QueryGroup group : groups) {
            if (group.getName().equals(groupName)) {
                return group;
            }
        }
        return null;
    }

    public List<QueryGroup> listGroups(WebmasterHostId hostId) {
        return queryGroupYDao.listGroups(hostId);
    }

    public QueryGroup changeGroupFilter(QueryGroupId queryGroupId, List<QueryFilter> queryFilter) {
        if (queryGroupId.isSpecial()) {
            throw new WebmasterException("Special group modification is restricted",
                    new WebmasterErrorResponse.IllegalParameterValueResponse(this.getClass(), "groupId", queryGroupId.getGroupId().toString()));
        }
        saveGroupState(queryGroupId);
        queryGroupYDao.changeFilter(queryGroupId, queryFilter);
        return getGroup(queryGroupId);
    }

    public QueryGroup changeGroupName(QueryGroupId queryGroupId, String name) {
        if (queryGroupId.isSpecial()) {
            throw new WebmasterException("Special group modification is restricted",
                    new WebmasterErrorResponse.IllegalParameterValueResponse(this.getClass(), "groupId", queryGroupId.getGroupId().toString()));
        }

        saveGroupState(queryGroupId);
        queryGroupYDao.changeName(queryGroupId, name);
        return getGroup(queryGroupId);
    }

    public void deleteGroup(QueryGroupId queryGroupId) {
        if (queryGroupId.isSpecial()) {
            throw new WebmasterException("Special group modification is restricted",
                    new WebmasterErrorResponse.IllegalParameterValueResponse(this.getClass(), "groupId", queryGroupId.getGroupId().toString()));
        }

        queryGroupYDao.deleteGroup(queryGroupId);
        queryGroupStateYDao.delete(queryGroupId);

        //clear unused queries
        Set<QueryId> queries = queriesGroupsRelationYDao.getQueries(queryGroupId);
        queriesGroupsRelationYDao.removeGroup(queryGroupId);

        Set<QueryId> toRemove = queriesGroupsRelationYDao.getNonExistentQueries(queryGroupId.getHostId(), queries);
        queriesYDao.delete(queryGroupId.getHostId(), toRemove);
    }

    public List<Query> getQueries(WebmasterHostId hostId) {
        return queriesYDao.get(hostId);
    }

    public Set<Query> getQueriesInGroup(QueryGroupId groupId) {
        Set<QueryId> queryIds = queriesGroupsRelationYDao.getQueries(groupId);
        return queriesYDao.get(groupId.getHostId(), queryIds);
    }

    public void addQueriesToGroup(QueryGroupId groupId, Set<String> queries, boolean createQueries) {
        if (queries.isEmpty()) {
            return;
        }

        if (createQueries) {
            queriesYDao.addBatchIfNotExist(groupId.getHostId(), queries);
        }

        saveGroupState(groupId);
        Set<QueryId> queryIds = queries.stream().map(QueryId::textToId).collect(Collectors.toSet());
        queriesGroupsRelationYDao.addQueriesToGroup(groupId, queryIds);
    }

    public void removeQueriesFromGroup(QueryGroupId groupId, Set<QueryId> queries, boolean deleteUnused) {
        if (queries.isEmpty()) {
            return;
        }

        saveGroupState(groupId);

        queriesGroupsRelationYDao.removeQueriesFromGroup(groupId, queries);

        if (deleteUnused) {
            Set<QueryId> toRemove = queriesGroupsRelationYDao.getNonExistentQueries(groupId.getHostId(), queries);
            queriesYDao.delete(groupId.getHostId(), toRemove);
        }
    }

    public void saveGroupState(QueryGroupId groupId) {
        if (!queryGroupStateYDao.exist(groupId)) {
            QueryGroup queryGroup = null;
            if (!groupId.isSpecial()) {
                queryGroup = queryGroupYDao.getGroup(groupId);
            }
            if (queryGroup == null) {
                return;
            }
            Set<QueryId> queryIds = queriesGroupsRelationYDao.getQueries(groupId);
            Set<Query> queries = queriesYDao.get(groupId.getHostId(), queryIds);
            QueryGroupState state = groupId.isSpecial() ? new QueryGroupState(groupId, queries) : new QueryGroupState(queryGroup, queries);
            queryGroupStateYDao.add(groupId, state);
        }
    }

    /*
     */
    public boolean isLimitReached(QueryGroupId groupId, int toAdd) {
        return queriesGroupsRelationYDao.getGroupSize(groupId) + toAdd > getLimit(groupId);
    }

    public int getQueriesLeft(QueryGroupId groupId) {
        return getLimit(groupId) - queriesGroupsRelationYDao.getGroupSize(groupId);
    }

    public Integer getLimit(QueryGroupId groupId) {
        if (groupId.isSpecial()) {
            Integer limit = queryGroupLimitsYDao.getLimits(groupId.getHostId(), groupId.getSpecialGroup());
            if (limit != null) {
                return limit;
            }
            return groupId.getSpecialGroup().getLimit();
        }

        return QueryGroup.QUERY_LIMIT;
    }

    public QueryGroup fromSpecialGroup(QueryGroupId groupId) {
        boolean hasQueries = queriesGroupsRelationYDao.hasQueries(groupId);
        return new QueryGroup(groupId, groupId.getSpecialGroup().name(), hasQueries, groupId.getSpecialGroup());
    }
}
