package ru.yandex.wmconsole.service;

import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
import ru.yandex.common.framework.user.UserInfo;
import ru.yandex.common.framework.user.blackbox.BlackBoxUserInfo;
import ru.yandex.common.util.db.LongRowMapper;
import ru.yandex.webmaster.common.host.HostEventService;
import ru.yandex.wmconsole.data.NotificationTypeEnum;
import ru.yandex.wmconsole.data.VerificationStateEnum;
import ru.yandex.wmconsole.data.info.DelegationInfo;
import ru.yandex.wmconsole.data.info.FreeVerificationInfo;
import ru.yandex.wmconsole.data.info.UsersHostsInfo;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmconsole.service.error.WMCUserProblem;
import ru.yandex.wmconsole.verification.VerificationTypeEnum;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.error.UserProblem;
import ru.yandex.wmtools.common.service.AbstractDbService;
import ru.yandex.wmtools.common.service.IViewerUserIdService;
import ru.yandex.wmtools.common.service.UserService;
import ru.yandex.wmtools.common.util.CollectionUtil;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author baton
 */
public class DelegationsService extends AbstractDbService {
    private static final Logger log = LoggerFactory.getLogger(DelegationsService.class);

    private static final String FIELD_USER_ID_GAVE = "user_id_gave";
    private static final String FIELD_USER_ID_GOT = "user_id_got";
    private static final String FIELD_HOST_ID = "host_id";
    private static final String FIELD_HOST_NAME = "hostname";
    private static final String FIELD_DELEGATION_ID = "id";
    private static final String FIELD_MAY_REDELEGATE = "may_redelegate";
    private static final String FIELD_VERIFICATION_STATE = "state";
    private static final String FIELD_DATE = "delegated_at";

    private static final String SELECT_DELEGATIONS_GIVEN_BY_USER_FOR_HOST_QUERY =
            "SELECT " +
                    "   d.id AS " + FIELD_DELEGATION_ID + ", " +
                    "   d.host_id AS " + FIELD_HOST_ID + ", " +
                    "   d.user_id_gave AS " + FIELD_USER_ID_GAVE + ", " +
                    "   d.user_id_got AS " + FIELD_USER_ID_GOT + ", " +
                    "   d.may_redelegate AS " + FIELD_MAY_REDELEGATE + ", " +
                    "   d.delegated_at AS " + FIELD_DATE + ", " +
                    "   uh.state AS " + FIELD_VERIFICATION_STATE + " " +
                    "FROM " +
                    "   tbl_delegations d " +
                    "   LEFT JOIN " +
                    "   tbl_users_hosts uh " +
                    "   ON uh.host_id = d.host_id " +
                    "   AND " +
                    "   d.user_id_got = uh.user_id " +
                    "WHERE " +
                    "   d.host_id = ? ";

    private static final String SELECT_IS_HOST_DELEGATED_QUERY =
            "SELECT " +
                    "   d.id " +
                    "FROM " +
                    "   tbl_delegations d " +
                    "WHERE " +
                    "   d.host_id = ? " +
                    "AND " +
                    "   d.user_id_got = ? ";

    private static final String SELECT_DELEGATION_FOR_HOST_AND_USER_QUERY =
            "SELECT " +
                    "   d.id AS " + FIELD_DELEGATION_ID + ", " +
                    "   d.host_id AS " + FIELD_HOST_ID + ", " +
                    "   d.user_id_gave AS " + FIELD_USER_ID_GAVE + ", " +
                    "   d.user_id_got AS " + FIELD_USER_ID_GOT + ", " +
                    "   d.may_redelegate AS " + FIELD_MAY_REDELEGATE + ", " +
                    "   d.delegated_at AS " + FIELD_DATE + ", " +
                    "   uh.state AS " + FIELD_VERIFICATION_STATE + " " +
                    "FROM " +
                    "   tbl_delegations d " +
                    "   LEFT JOIN " +
                    "   tbl_users_hosts uh " +
                    "   ON uh.host_id = d.host_id " +
                    "   AND " +
                    "   d.user_id_got = uh.user_id " +
                    "WHERE " +
                    "   d.host_id = ? " +
                    "AND " +
                    "   d.user_id_got = ? ";

    private static final String SELECT_FREE_DELEGATIONS_FOR_USER_QUERY =
            "SELECT " +
                    "   h.name AS " + FIELD_HOST_NAME + " " +
                    "FROM " +
                    "   tbl_delegations d " +
                    "LEFT JOIN " +
                    "   tbl_users_hosts uh " +
                    "ON " +
                    "   d.host_id = uh.host_id " +
                    "LEFT JOIN " +
                    "   tbl_hosts h " +
                    "ON " +
                    "   d.host_id = h.host_id " +
                    "AND " +
                    "   d.user_id_got = uh.user_id " +
                    "WHERE " +
                    "   uh.host_id IS NULL " +
                    "AND " +
                    "   d.user_id_got = ? ";

    private static final String INSERT_DELEGATION_UPDATE_QUERY =
            "INSERT IGNORE INTO " +
                    "   tbl_delegations (host_id, user_id_gave, user_id_got, may_redelegate, delegated_at) " +
                    "VALUES " +
                    "   (?, ?, ?, ?, ?) ";

    private static final String DELETE_ALL_DELEGATIONS_GIVEN_TO_USER_FOR_HOST_QUERY =
            "DELETE FROM " +
                    "   tbl_delegations " +
                    "WHERE " +
                    "   user_id_got = ? " +
                    "AND " +
                    "   host_id = ? ";

    private static final String LIST_DELEGATIONS =
            "SELECT " +
                    "   d.id AS " + FIELD_DELEGATION_ID + ", " +
                    "   d.host_id AS " + FIELD_HOST_ID + ", " +
                    "   d.user_id_gave AS " + FIELD_USER_ID_GAVE + ", " +
                    "   d.user_id_got AS " + FIELD_USER_ID_GOT + ", " +
                    "   d.may_redelegate AS " + FIELD_MAY_REDELEGATE + ", " +
                    "   d.delegated_at AS " + FIELD_DATE + ", " +
                    "   1 AS " + FIELD_VERIFICATION_STATE + ", " +
                    "   h.name AS host_name " +
                    "FROM " +
                    "   tbl_delegations d " +
                    "JOIN " +
                    "   tbl_hosts h " +
                    "USING(host_id) " +
                    "WHERE " +
                    "   d.id > ? " +
                    "ORDER BY d.id ASC " +
                    "LIMIT ?";

    private UsersHostsService usersHostsService;
    private IViewerUserIdService viewerUserIdService;
    private UserService userService;
    private SendInternalNotificationService sendInternalNotificationService;
    private HostEventService hostEventService;

    private final ParameterizedRowMapper<DelegationInfo> delegationInfoRowMapper = new ParameterizedRowMapper<DelegationInfo>() {
        @Override
        public DelegationInfo mapRow(ResultSet rs, int i) throws SQLException {
            UserInfo userGotInfo = userService.getUserInfo(rs.getLong(FIELD_USER_ID_GOT));
            if (userGotInfo == null) {
                return null;
            }
            VerificationStateEnum state = VerificationStateEnum.R.fromValueOrNull(rs.getInt(FIELD_VERIFICATION_STATE));
            return new DelegationInfo(
                    rs.getLong(FIELD_DELEGATION_ID),
                    rs.getLong(FIELD_HOST_ID),
                    rs.getLong(FIELD_USER_ID_GAVE),
                    userGotInfo,
                    rs.getBoolean(FIELD_MAY_REDELEGATE),
                    state != null && state.isVerified(),
                    rs.getDate(FIELD_DATE));
        }
    };

    private final ParameterizedRowMapper<DelegationInfo> fastDelegationInfoRowMapper = new ParameterizedRowMapper<DelegationInfo>() {
        @Override
        public DelegationInfo mapRow(ResultSet rs, int i) throws SQLException {
            UserInfo userGotInfo = new BlackBoxUserInfo(rs.getLong(FIELD_USER_ID_GOT));
            VerificationStateEnum state = VerificationStateEnum.R.fromValueOrNull(rs.getInt(FIELD_VERIFICATION_STATE));
            return new DelegationInfo(
                    rs.getLong(FIELD_DELEGATION_ID),
                    rs.getLong(FIELD_HOST_ID),
                    rs.getLong(FIELD_USER_ID_GAVE),
                    userGotInfo,
                    rs.getBoolean(FIELD_MAY_REDELEGATE),
                    state != null && state.isVerified(),
                    rs.getDate(FIELD_DATE));
        }
    };

    private static final ParameterizedRowMapper<FreeVerificationInfo> freeVerificationInfoRowMapper = new ParameterizedRowMapper<FreeVerificationInfo>() {
        @Override
        public FreeVerificationInfo mapRow(ResultSet rs, int i) throws SQLException {
            String hostName = rs.getString(FIELD_HOST_NAME);
            return new FreeVerificationInfo(
                    hostName,
                    VerificationTypeEnum.DELEGATION
            );
        }
    };

    public void addDelegation(long hostId, long userIdGave, String loginGot, boolean mayRedelegate, String userYandexUid) throws InternalException, UserException {
        usersHostsService.assertUserMayRedelegateHost(userIdGave, hostId);

        Long userIdGot = userService.getUidByLogin(loginGot);
        if (userIdGot == null) {
            throw new UserException(UserProblem.NO_SUCH_USER_IN_PASSPORT, "User " + loginGot + " not found in Yandex.Passport");
        }

        viewerUserIdService.assureUserIsInUsersList(userIdGot);

        UsersHostsInfo gotUserHostInfo = usersHostsService.getUsersHostsInfo(userIdGot, hostId);

        // Есть права и получены не читерски
        if (gotUserHostInfo != null && gotUserHostInfo.getVerificationState().isVerified() && (!VerificationTypeEnum.CHEAT.equals(gotUserHostInfo.getVerificationType()))) {
            throw new UserException(WMCUserProblem.HOST_ALREADY_VERIFIED, "Cannot delegate host " + hostId + " to user "
                    + userIdGot + ", because this host is already verified by this user.");
        }
        if (getDelegationForHostAndUser(hostId, userIdGot) != null) {
            throw new UserException(WMCUserProblem.HOST_ALREADY_VERIFIED, "Cannot delegate host " + hostId + " to user "
                    + userIdGot + ", because this host is already delegated to this user.");
        }

        getJdbcTemplate(WMCPartition.nullPartition()).update(INSERT_DELEGATION_UPDATE_QUERY, hostId, userIdGave, userIdGot, mayRedelegate, new Date());

        try {
            sendNotification(hostId, userIdGot, userIdGave, mayRedelegate, true);
        } catch (InternalException e) {
            log.warn("Failed to send delegation notification!", e);
        }

        try {
            hostEventService.hostRightsIsDelegated(hostId, userIdGot, userIdGave, userYandexUid, mayRedelegate);
        } catch (InternalException e) {
            log.warn("Unable to save delegation event", e);
        }

        if (gotUserHostInfo != null) {
            usersHostsService.putHostInVerificationQueue(userIdGot, hostId, VerificationTypeEnum.DELEGATION);
        }
    }

    public void deleteAllDelegationsGivenToUserForHost(long userId, long hostId, long revokeByUserId) throws InternalException, UserException {
        getJdbcTemplate(WMCPartition.nullPartition()).update(DELETE_ALL_DELEGATIONS_GIVEN_TO_USER_FOR_HOST_QUERY, userId, hostId);
        try {
            hostEventService.hostRightsRovoked(hostId, userId, revokeByUserId);
        } catch (InternalException e) {
            log.warn("Unable to save delegation revoked event", e);
        }
    }

    public List<DelegationInfo> listDelegationsForHost(long hostId) throws InternalException, UserException {
        return CollectionUtil.removeNulls(getJdbcTemplate(WMCPartition.nullPartition()).query(
                SELECT_DELEGATIONS_GIVEN_BY_USER_FOR_HOST_QUERY, delegationInfoRowMapper, hostId));
    }

    public Long getDelegationForHostAndUser(long hostId, long toUserId) throws InternalException {
        List<Long> delegations = getJdbcTemplate(WMCPartition.nullPartition()).query(SELECT_IS_HOST_DELEGATED_QUERY, new LongRowMapper(), hostId, toUserId);
        if (delegations.size() <= 0) {
            return null;
        }
        return delegations.get(0);
    }

    public DelegationInfo getDelegationInfoForHostAndUser(long hostId, long userId) throws InternalException {
        List<DelegationInfo> delegations = CollectionUtil.removeNulls(getJdbcTemplate(WMCPartition.nullPartition()).query(
                SELECT_DELEGATION_FOR_HOST_AND_USER_QUERY,
                delegationInfoRowMapper,
                hostId,
                userId));
        if (delegations.isEmpty()) {
            return null;
        }
        return delegations.get(0);
    }

    public List<FreeVerificationInfo> getFreeDelegationsForUser(long toUserId) throws InternalException {
        return getJdbcTemplate(WMCPartition.nullPartition()).query(SELECT_FREE_DELEGATIONS_FOR_USER_QUERY, freeVerificationInfoRowMapper, toUserId);
    }

    public List<Pair<DelegationInfo, String>> listAllDelegations(long fromId, int limit) throws InternalException {
        return getJdbcTemplate(WMCPartition.nullPartition()).query(LIST_DELEGATIONS, new ParameterizedRowMapper<Pair<DelegationInfo, String>>() {
            @Override
            public Pair<DelegationInfo, String> mapRow(ResultSet resultSet, int i) throws SQLException {
                DelegationInfo delegationInfo = fastDelegationInfoRowMapper.mapRow(resultSet, i);
                String hostName = resultSet.getString("host_name");
                return Pair.of(delegationInfo, hostName);
            }
        }, fromId, limit);
    }

    private void sendNotification(long hostId, long userGotId, long userGaveId, boolean mayRedelegate, boolean givingDelegation) throws InternalException {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("host_id", hostId);
        params.put("user_got_id", userGotId);
        params.put("user_gave_id", userGaveId);
        params.put("may_redelegate", mayRedelegate ? "true" : "false");
        params.put("giving_delegation", givingDelegation ? "true" : "false");
        params.put("date", new Date().getTime());

        sendInternalNotificationService.sendInternalNotification(NotificationTypeEnum.DELEGATION, params);
    }

    @Required
    public void setViewerUserIdService(IViewerUserIdService viewerUserIdService) {
        this.viewerUserIdService = viewerUserIdService;
    }

    @Required
    public void setUsersHostsService(UsersHostsService usersHostsService) {
        this.usersHostsService = usersHostsService;
    }

    @Required
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @Required
    public void setSendInternalNotificationService(SendInternalNotificationService sendInternalNotificationService) {
        this.sendInternalNotificationService = sendInternalNotificationService;
    }

    @Required
    public void setHostEventService(HostEventService hostEventService) {
        this.hostEventService = hostEventService;
    }
}
