/*
 * Copyright 1999-2020 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.nacos.naming.web;

import com.alibaba.nacos.common.constant.HttpHeaderConsts;
import com.alibaba.nacos.common.utils.HttpMethod;
import com.alibaba.nacos.common.utils.InternetAddressUtil;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.core.context.RequestContextHolder;
import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.naming.core.v2.client.ClientAttributes;
import com.alibaba.nacos.naming.core.v2.client.impl.IpPortBasedClient;
import com.alibaba.nacos.naming.core.v2.client.manager.ClientManager;
import com.alibaba.nacos.naming.misc.Loggers;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import org.springframework.beans.factory.annotation.Autowired;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Optional;

/**
 * <p>
 * collect client attributes for 1.x.
 * </p>
 *
 * @author hujun
 */
public class ClientAttributesFilter implements Filter {
    
    private static final String BEAT_URI = "/beat";
    
    private static final String IP = "ip";
    
    private static final String PORT = "port";
    
    private static final String ZERO = "0";
    
    @Autowired
    private ClientManager clientManager;
    
    public static Optional<ClientAttributes> getCurrentClientAttributes() {
        Object clientAttributes = RequestContextHolder.getContext()
                .getExtensionContext(ClientAttributes.class.getSimpleName());
        if (clientAttributes instanceof ClientAttributes) {
            return Optional.of((ClientAttributes) clientAttributes);
        }
        return Optional.empty();
    }
    
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String uri = request.getRequestURI();
        String method = request.getMethod();
        try {
            if (isRegisterInstanceUri(uri, method)) {
                //register
                ClientAttributes attributes = getClientAttributes();
                RequestContextHolder.getContext()
                        .addExtensionContext(ClientAttributes.class.getSimpleName(), attributes);
            } else if (isBeatUri(uri, method)) {
                //beat
                String ip = WebUtils.optional(request, IP, StringUtils.EMPTY);
                int port = Integer.parseInt(WebUtils.optional(request, PORT, ZERO));
                String clientId = IpPortBasedClient.getClientId(ip + InternetAddressUtil.IP_PORT_SPLITER + port, true);
                IpPortBasedClient client = (IpPortBasedClient) clientManager.getClient(clientId);
                if (client != null) {
                    ClientAttributes requestClientAttributes = getClientAttributes();
                    //update clientAttributes,when client version attributes is null,then update.
                    if (canUpdateClientAttributes(client, requestClientAttributes)) {
                        client.setAttributes(requestClientAttributes);
                    }
                }
            }
        } catch (Exception e) {
            Loggers.SRV_LOG.error("handler client attributes error", e);
        }
        try {
            filterChain.doFilter(request, servletResponse);
        } catch (ServletException e) {
            throw new RuntimeException(e);
        }
    }
    
    private boolean isBeatUri(String uri, String httpMethod) {
        return ((UtilsAndCommons.NACOS_SERVER_CONTEXT + UtilsAndCommons.NACOS_NAMING_CONTEXT
                + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT + BEAT_URI).equals(uri) || (
                UtilsAndCommons.NACOS_SERVER_CONTEXT + UtilsAndCommons.DEFAULT_NACOS_NAMING_CONTEXT_V2
                        + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT + BEAT_URI).equals(uri))
                && HttpMethod.PUT.equals(httpMethod);
    }
    
    private boolean isRegisterInstanceUri(String uri, String httpMethod) {
        return ((UtilsAndCommons.NACOS_SERVER_CONTEXT + UtilsAndCommons.NACOS_NAMING_CONTEXT
                + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT).equals(uri) || (UtilsAndCommons.NACOS_SERVER_CONTEXT
                + UtilsAndCommons.DEFAULT_NACOS_NAMING_CONTEXT_V2
                + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT).equals(uri)) && HttpMethod.POST.equals(httpMethod);
    }
    
    private boolean canUpdateClientAttributes(IpPortBasedClient client, ClientAttributes requestClientAttributes) {
        if (requestClientAttributes.getClientAttribute(HttpHeaderConsts.CLIENT_VERSION_HEADER) == null) {
            return false;
        }
        if (client.getClientAttributes() != null
                && client.getClientAttributes().getClientAttribute(HttpHeaderConsts.CLIENT_VERSION_HEADER) != null) {
            return false;
        }
        return true;
    }
    
    private ClientAttributes getClientAttributes() {
        String version = RequestContextHolder.getContext().getBasicContext().getUserAgent();
        String app = RequestContextHolder.getContext().getBasicContext().getApp();
        String clientIp = RequestContextHolder.getContext().getBasicContext().getAddressContext().getSourceIp();
        ClientAttributes clientAttributes = new ClientAttributes();
        if (version != null) {
            clientAttributes.addClientAttribute(HttpHeaderConsts.CLIENT_VERSION_HEADER, version);
        }
        if (app != null) {
            clientAttributes.addClientAttribute(HttpHeaderConsts.APP_FILED, app);
        }
        if (clientIp != null) {
            clientAttributes.addClientAttribute(HttpHeaderConsts.CLIENT_IP, clientIp);
        }
        return clientAttributes;
    }
}
