package ru.yandex.qe.dispenser.ws.intercept;

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

import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.common.primitives.Longs;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;

import ru.yandex.qe.dispenser.InstrumentedQloudTvmService;
import ru.yandex.qe.dispenser.domain.Person;
import ru.yandex.qe.dispenser.domain.hierarchy.Hierarchy;
import ru.yandex.qe.dispenser.domain.hierarchy.Session;
import ru.yandex.qe.dispenser.passport.PassportService;
import ru.yandex.qe.dispenser.passport.util.PassportSecurityUtils;
import ru.yandex.qe.hitman.tvm.TvmConstants;
import ru.yandex.qe.hitman.tvm.qloud.TvmServiceTicketInfo;
import ru.yandex.qe.hitman.tvm.qloud.TvmUserTicketInfo;

public class PreAuthFilter extends AbstractPreAuthenticatedProcessingFilter {

    private static final Logger LOG = LoggerFactory.getLogger(PreAuthFilter.class);

    @Inject
    private PassportService passportService;
    @Inject
    private InstrumentedQloudTvmService qloudTvmService;
    @Value("#{${tvm.service.id.allowed.user.tickets}}")
    private Set<Integer> allowedUserServices;

    @Override
    public Object getPreAuthenticatedPrincipal(@NotNull final HttpServletRequest req) {
        Session.AUTH_LOGIN.remove();
        final Optional<TvmUser> tvmUser = tryCheckTvm(req);
        if (tvmUser.isPresent()) {
            final Optional<Person> person = Hierarchy.get().getPersonReader().tryReadPersonByUid(tvmUser.get().getUid());
            if (person.isPresent()) {
                Session.AUTH_LOGIN.set(person.get().getLogin());
                req.setAttribute(AccessLogFilter.LOGIN, person.get().getLogin());
                req.setAttribute(AccessLogFilter.UID, String.valueOf(tvmUser.get().getUid()));
                req.setAttribute(AccessLogFilter.TVM_CLIENT_ID, String.valueOf(tvmUser.get().getServiceId()));
                return person.get().getLogin();
            }
        }
        final ru.yandex.qe.dispenser.passport.Session session = checkAuthHeaders(req);
        if (session != null) {
            final String login = session.getLogin();
            Session.AUTH_LOGIN.set(login);
            req.setAttribute(AccessLogFilter.LOGIN, login);
            req.setAttribute(AccessLogFilter.UID, String.valueOf(session.getUserId()));
            return session.getLogin();
        }
        return null;
    }

    @Override
    protected Object getPreAuthenticatedCredentials(final HttpServletRequest request) {
        return "N/A";
    }

    @Override
    protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response,
                                            final Authentication authResult) throws IOException, ServletException {
        super.successfulAuthentication(request, response, authResult);
        final ImmutablePair<String, String> header = PassportSecurityUtils.getRefreshHeader();
        if (header != null) {
            response.addHeader(header.getKey(), header.getValue());
        }
    }

    private ru.yandex.qe.dispenser.passport.Session checkAuthHeaders(final HttpServletRequest request) {
        final String remoteAddress = PassportSecurityUtils.getRemoteAddress(request);
        PassportSecurityUtils.resetRefreshHeader();
        final String sessionCookie = PassportSecurityUtils.findYaSessionCookie(request);
        if (sessionCookie != null) {
            final ru.yandex.qe.dispenser.passport.Session session = passportService.getSession(sessionCookie, remoteAddress);
            return checkSession(request, remoteAddress, session);
        }
        final String authorization = request.getHeader("Authorization");
        if (authorization != null) {
            final ru.yandex.qe.dispenser.passport.Session session = passportService.getOauthSession(authorization, remoteAddress);
            return checkSession(request, remoteAddress, session);
        }
        LOG.debug("No session_id cookie and authorization header in request from ip {} to {}", remoteAddress, request.getRequestURI());
        return null;
    }

    private ru.yandex.qe.dispenser.passport.Session checkSession(final HttpServletRequest httpServletRequest, final String remoteAddress,
                                                       final ru.yandex.qe.dispenser.passport.Session session) {
        if (!session.isValid()) {
            LOG.info("Invalid session cookie or authorization header in request from ip {} to {}", remoteAddress, httpServletRequest.getRequestURI());
            return null;
        }
        if (session.isNeedRenew()) {
            PassportSecurityUtils.saveRefreshHeader("X-Yandex-Refresh-Cookie", "1");
        }
        return session;
    }

    private Optional<TvmUser> tryCheckTvm(final HttpServletRequest request) {
        try {
            return checkTvm(request);
        } catch (RuntimeException e) {
            LOG.error("Failed to check TVM tickets", e);
            return Optional.empty();
        }
    }

    private Optional<TvmUser> checkTvm(final HttpServletRequest request) {
        final String serviceTicket = request.getHeader(TvmConstants.TVM_SERVICE_HEADER_NAME);
        final String userTicket = request.getHeader(TvmConstants.TVM_USER_HEADER_NAME);
        if (serviceTicket == null || userTicket == null) {
            LOG.debug("TVM user or service tickets are missing");
            return Optional.empty();
        }
        final Optional<TvmServiceTicketInfo> serviceTicketInfo = qloudTvmService.validateServiceTicket(serviceTicket);
        if (!serviceTicketInfo.isPresent()) {
            LOG.info("Invalid TVM service ticket");
            return Optional.empty();
        }
        final Optional<TvmUserTicketInfo> userTicketInfo = qloudTvmService.validateUserTicket(userTicket);
        if (!userTicketInfo.isPresent()) {
            LOG.info("Invalid TVM user ticket");
            return Optional.empty();
        }
        if (!allowedUserServices.contains(serviceTicketInfo.get().getSource())) {
            LOG.info("TVM service {} is not allowed to use user tickets", serviceTicketInfo.get().getSource());
            return Optional.empty();
        }
        final String defaultUid = userTicketInfo.get().getDefaultUid();
        if (StringUtils.isEmpty(defaultUid)) {
            LOG.info("No default uid in TVM ticket");
            return Optional.empty();
        }
        final Long defaultUidValue = Longs.tryParse(defaultUid);
        if (defaultUidValue == null) {
            LOG.info("No valid default uid in TVM ticket");
            return Optional.empty();
        }
        if (defaultUidValue <= 0L) {
            LOG.info("Default uid in TVM ticket is not valid");
            return Optional.empty();
        }
        return Optional.of(new TvmUser(defaultUidValue, serviceTicketInfo.get().getSource()));
    }

    private static class TvmUser {

        private final long uid;
        private final int serviceId;

        private TvmUser(final long uid, final int serviceId) {
            this.uid = uid;
            this.serviceId = serviceId;
        }

        public long getUid() {
            return uid;
        }

        public int getServiceId() {
            return serviceId;
        }

    }

}
