package com.eactive.eai.authserver.service;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.stereotype.Component;

import com.eactive.eai.agent.authserver.ReloadApiScopeCommand;
import com.eactive.eai.authserver.dao.ClientDAO;
import com.eactive.eai.authserver.dao.ScopeDAO;
import com.eactive.eai.authserver.vo.ClientVO;
import com.eactive.eai.common.dao.DAOException;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.lifecycle.LifecycleSupport;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.transformer.util.MemoryLogger;

@Component
public class OAuth2Manager implements Lifecycle {

	static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);

	private LifecycleSupport lifecycle = new LifecycleSupport(this);

	private Map<String, ClientDetails> clientDetailsStore = new HashMap<>();
	private Map<String, HashSet<String>> apiScopeMap = new HashMap<>();

	private boolean started;

	@Autowired
	private ClientDAO clientDAO;

	@Autowired
	private ScopeDAO scopeDAO;

	public static OAuth2Manager getInstance() {
		return ApplicationContextProvider.getContext().getBean(OAuth2Manager.class);
	}
	
	public ClientVO getClientInfo(String clientId) throws DAOException {
		ClientVO vo = clientDAO.getClientById(clientId);
		logger.info("OAuth2Manager | getClientVO clientId=" + clientId + " - " + vo);
		return vo;
	}
	
	public Map<String, ClientDetails> getClientDeatilsStore() {
		return clientDetailsStore;
	}

	public Map<String, HashSet<String>> getApiScopeMap() {
		return apiScopeMap;
	}

	private void initAllClients() throws DAOException {
		clientDetailsStore = clientDAO.loadClientDetails();

		logger.info("OAuth2Manager | all clients are initialized");
		MemoryLogger.txlog("OAuth2Manager | all clients are initialized");
	}

	private void initAllApiScopeRelations() throws DAOException {
		apiScopeMap = scopeDAO.loadApiScopeRelations();

		logger.info("OAuth2Manager | all API-Scope Relation are initialized");
		MemoryLogger.txlog("OAuth2Manager | all API-Scope Relation are initialized");
	}

	@Override
	public void addLifecycleListener(LifecycleListener listener) {
		lifecycle.addLifecycleListener(listener);
	}

	@Override
	public LifecycleListener[] findLifecycleListeners() {
		return lifecycle.findLifecycleListeners();
	}

	@Override
	public void removeLifecycleListener(LifecycleListener listener) {
		lifecycle.removeLifecycleListener(listener);
	}

	@Override
	public void start() throws LifecycleException {
		if (started)
			throw new LifecycleException("RECEAIAAC015");

		// Notify our interested LifecycleListeners
		lifecycle.fireLifecycleEvent(STARTING_EVENT, this);

		try {
			// client 초기화
			clientDetailsStore.clear();
			initAllClients();

			// api-scope 초기화
			apiScopeMap.clear();
			initAllApiScopeRelations();
		} catch (Exception e) {
			throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAICPM201"));
		}

		started = true;
		// Notify our interested LifecycleListeners
		lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
	}

	@Override
	public void stop() throws LifecycleException {
		if (!started)
			throw new LifecycleException("RECEAIAAC020");

		// Notify our interested LifecycleListeners
		lifecycle.fireLifecycleEvent(STOPING_EVENT, this);

		clientDetailsStore = null;
		apiScopeMap = null;

		started = false;
		// Notify our interested LifecycleListeners
		lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
	}

	@Override
	public boolean isStarted() {
		return this.started;
	}

	public synchronized void reloadClient(String keyName) throws Exception {
		// 1. Memory에서 제거
		clientDetailsStore.remove(keyName);

		// 2. DB에서 select
		ClientVO client = clientDAO.getClientById(keyName);
		if (client != null) {
			// 3. DB에 있으면 Memory에 적재
			clientDetailsStore.put(keyName, client);
		}
	}

	public void reloadApiScopeRelation(String[] args) throws Exception {
		if (args == null || args.length != 3) {
			throw new Exception("Arguments 3 required");
		}

		String commandType = args[0];
		String apiId = args[1];
		String scopeId = args[2];

		if (StringUtils.equals(commandType, ReloadApiScopeCommand.COMMAND_TYPE_API)) {
			reloadApiScopeRelationForApi(apiId);
		} else if (StringUtils.equals(commandType, ReloadApiScopeCommand.COMMAND_TYPE_SCOPE)) {
			reloadApiScopeRelationForScope(scopeId);
		} else if (StringUtils.equals(commandType, ReloadApiScopeCommand.COMMAND_TYPE_API_SCOPE)) {
			reloadApiScopeRelationForApiScope(apiId, scopeId);
		} else {
			throw new IllegalArgumentException("Invalid command type: " + commandType);
		}
	}

	private void reloadApiScopeRelationForApi(String apiId) throws DAOException {
		apiScopeMap.remove(apiId);
		logger.debug(String.format("Type=%s, ID=%s removed", ReloadApiScopeCommand.COMMAND_TYPE_API, apiId));

		HashSet<String> scopeSet = scopeDAO.getApiScopeRelationsByApiId(apiId);
		if (scopeSet != null) {
			apiScopeMap.put(apiId, scopeSet);
			logger.debug(String.format("Type=%s, ID=%s added", ReloadApiScopeCommand.COMMAND_TYPE_API, apiId));
		}
	}

	private void reloadApiScopeRelationForScope(String scopeId) throws DAOException {
		for (Set<String> scopeSet : apiScopeMap.values()) {
			scopeSet.remove(scopeId);
		}
		logger.debug(String.format("Type=%s, ID=%s removed", ReloadApiScopeCommand.COMMAND_TYPE_SCOPE, scopeId));

		List<String> apiList = scopeDAO.getApiScopeRelationsByScopeId(scopeId);
		if (apiList != null) {
			for (String apiId : apiList) {
				Set<String> scopeSet = apiScopeMap.computeIfAbsent(apiId, k -> new HashSet<>());
				scopeSet.add(scopeId);
				logger.debug(String.format("Type=%s, ID=%s, %s added", ReloadApiScopeCommand.COMMAND_TYPE_SCOPE, apiId,
						scopeId));
			}
		}
	}

	private void reloadApiScopeRelationForApiScope(String apiId, String scopeId) throws DAOException {
		Set<String> scopeSet = apiScopeMap.computeIfAbsent(apiId, key -> new HashSet<String>());
		scopeSet.remove(scopeId);
		logger.debug(String.format("Type=%s, ID=%s, %s removed", ReloadApiScopeCommand.COMMAND_TYPE_API_SCOPE, apiId,
				scopeId));

		if (scopeDAO.hasApiScopeRelation(apiId, scopeId)) {
			scopeSet.add(scopeId);
			logger.debug(String.format("Type=%s, ID=%s, %s added", ReloadApiScopeCommand.COMMAND_TYPE_API_SCOPE, apiId,
					scopeId));
		}
	}

	public ClientVO getClientVO(String clientId){
		ClientDetails clientDetails = clientDetailsStore.get(clientId);

		if(clientDetails instanceof ClientVO){
			return (ClientVO) clientDetails;
		}

		return null;
	}
}
