package com.eactive.eai.common.security;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.bind.DatatypeConverter;

import org.springframework.stereotype.Component;

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.security.keyderiv.DerivedKey;
import com.eactive.eai.common.security.keyderiv.KeyDerivationStrategy;
import com.eactive.eai.common.security.loader.CryptoModuleConfigLoader;
import com.eactive.eai.common.security.mapper.CryptoModuleConfigMapper;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.data.entity.onl.security.CryptoModuleConfig;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

@Component
public class CryptoModuleManager implements Lifecycle {

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

	private static final ObjectMapper objectMapper = new ObjectMapper();

	private final ConcurrentHashMap<String, CryptoModuleConfigVO> configMap    = new ConcurrentHashMap<>();
	private final ConcurrentHashMap<String, KeyDerivationStrategy> strategyCache = new ConcurrentHashMap<>();
	private final ConcurrentHashMap<String, CachedDerivedKey>      dynamicKeyCache = new ConcurrentHashMap<>();

	private boolean started;
	private final LifecycleSupport lifecycle = new LifecycleSupport(this);

	private CryptoModuleManager() {}

	public static CryptoModuleManager getInstance() {
		return ApplicationContextProvider.getContext().getBean(CryptoModuleManager.class);
	}

	@Override
	public void start() throws LifecycleException {
		if (started) throw new LifecycleException("RECEAICRY001");
		lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
		try {
			loadAll();
		} catch (Exception e) {
			throw new LifecycleException("RECEAICRY002");
		}
		started = true;
		lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
	}

	@Override
	public void stop() throws LifecycleException {
		if (!started) throw new LifecycleException("RECEAICRY003");
		lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
		configMap.clear();
		dynamicKeyCache.clear();
		strategyCache.clear();
		started = false;
		lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
	}

	private void loadAll() {
		CryptoModuleConfigLoader loader = ctx().getBean(CryptoModuleConfigLoader.class);
		CryptoModuleConfigMapper mapper = ctx().getBean(CryptoModuleConfigMapper.class);

		List<CryptoModuleConfig> all = loader.findAll();
		configMap.clear();
		for (CryptoModuleConfig entity : all) {
			if ("Y".equalsIgnoreCase(entity.getUseYn())) {
				CryptoModuleConfigVO vo = mapper.toVo(entity);
				configMap.put(vo.getCryptoName(), vo);
			}
		}
		logger.warn("CryptoModuleManager] 암호화모듈 로드 완료. 건수=" + configMap.size());
	}

	public void reload(String cryptoId) {
		CryptoModuleConfigLoader loader = ctx().getBean(CryptoModuleConfigLoader.class);
		CryptoModuleConfigMapper mapper = ctx().getBean(CryptoModuleConfigMapper.class);

		configMap.values().removeIf(vo -> cryptoId.equals(vo.getCryptoId()));
		evictDynamicCache(cryptoId);

		loader.findById(cryptoId).ifPresent(entity -> {
			if ("Y".equalsIgnoreCase(entity.getUseYn())) {
				CryptoModuleConfigVO vo = mapper.toVo(entity);
				configMap.put(vo.getCryptoName(), vo);
				logger.warn("CryptoModuleManager] 재로드 완료: " + vo.getCryptoName());
			}
		});
	}

	/**
	 * STATIC 키 방식 — 초기화된 CryptoModuleExtension 반환.
	 * Cipher는 thread-safe하지 않으므로 호출마다 새 인스턴스를 생성한다.
	 */
	public CryptoModuleExtension createExtension(String cryptoName) throws Exception {
		CryptoModuleConfigVO vo = getVO(cryptoName);
		byte[] encKey = DatatypeConverter.parseHexBinary(vo.getEncKeyHex());
		byte[] decKey = vo.getDecKeyHex() != null
				? DatatypeConverter.parseHexBinary(vo.getDecKeyHex())
				: encKey;
		byte[] iv = vo.getIvHex() != null ? DatatypeConverter.parseHexBinary(vo.getIvHex()) : null;
		return buildExtension(vo, iv, encKey, decKey);
	}

	/**
	 * DYNAMIC 키 방식 — 전략으로 키를 도출하고 캐시를 적용한 뒤 CryptoModuleExtension 반환.
	 */
	public CryptoModuleExtension createExtension(String cryptoName, Map<String, String> runtimeContext)
			throws Exception {
		CryptoModuleConfigVO vo = getVO(cryptoName);

		if ("STATIC".equalsIgnoreCase(vo.getKeySourceType())) {
			return createExtension(cryptoName);
		}

		KeyDerivationStrategy strategy = resolveStrategy(vo.getKeyDerivStrategy());
		Map<String, String> params = parseParams(vo.getKeyDerivParams());
		String cacheKey = strategy.buildCacheKey(cryptoName, params, runtimeContext);

		DerivedKey derivedKey = null;

		if ("Y".equalsIgnoreCase(vo.getCacheYn())) {
			CachedDerivedKey cached = dynamicKeyCache.get(cacheKey);
			if (cached != null && !cached.isExpired()) {
				derivedKey = cached.getDerivedKey();
			}
		}

		if (derivedKey == null) {
			derivedKey = strategy.deriveKey(params, runtimeContext);
			if ("Y".equalsIgnoreCase(vo.getCacheYn())) {
				int ttl = vo.getCacheTtlSec() != null ? vo.getCacheTtlSec() : 300;
				dynamicKeyCache.put(cacheKey, new CachedDerivedKey(derivedKey, ttl));
			}
		}
		byte[] iv = null;
		if(derivedKey.getIv() != null) {
			iv = derivedKey.getIv();
		} else if (vo.getIvHex() != null){
			iv = DatatypeConverter.parseHexBinary(vo.getIvHex());
		}
		return buildExtension(vo, iv, derivedKey.getEncKey(), derivedKey.getDecKey());
	}

	/**
	 * 등록된 전체 암호화모듈의 진단정보를 반환한다.
	 * 원본 키(encKey/decKey/ivHex)는 포함하지 않는다.
	 */
	public List<CryptoModuleDiagnosticDTO> describeAll() {
		List<CryptoModuleDiagnosticDTO> result = new ArrayList<>();
		for (String cryptoName : configMap.keySet()) {
			result.add(describe(cryptoName));
		}
		return result;
	}

	/**
	 * 단일 암호화모듈의 진단정보를 반환한다. DYNAMIC 키 방식인 경우 resolveStrategy를 통해
	 * 전략 클래스 로드 성공 여부를 함께 확인한다. 원본 키(encKey/decKey/ivHex)는 포함하지 않는다.
	 */
	public CryptoModuleDiagnosticDTO describe(String cryptoName) {
		CryptoModuleConfigVO vo = getVO(cryptoName);

		CryptoModuleDiagnosticDTO dto = new CryptoModuleDiagnosticDTO();
		dto.setCryptoName(vo.getCryptoName());
		dto.setCryptoDesc(vo.getCryptoDesc());
		dto.setAlgType(vo.getAlgType());
		dto.setCipherMode(vo.getCipherMode());
		dto.setPadding(vo.getPadding());
		dto.setHasIv(vo.getIvHex() != null);
		dto.setKeySourceType(vo.getKeySourceType());
		dto.setCacheYn(vo.getCacheYn());
		dto.setCacheTtlSec(vo.getCacheTtlSec());
		dto.setUseYn(vo.getUseYn());

		if (!"STATIC".equalsIgnoreCase(vo.getKeySourceType()) && vo.getKeyDerivStrategy() != null) {
			dto.setKeyDerivStrategy(vo.getKeyDerivStrategy());
			try {
				KeyDerivationStrategy strategy = resolveStrategy(vo.getKeyDerivStrategy());
				dto.setStrategyLoaded(true);
				dto.setStrategyClassName(strategy.getClass().getName());
			} catch (Exception e) {
				dto.setStrategyLoaded(false);
				dto.setStrategyLoadError(e.getMessage());
			}
		}
		return dto;
	}

	/**
	 * 동적 키 캐시에 존재하는 캐시 키 목록을 반환한다. (전략별 buildCacheKey 결과이며, 원본 키 값이 아니다)
	 */
	public List<String> listDynamicCacheKeys() {
		return new ArrayList<>(dynamicKeyCache.keySet());
	}

	private CryptoModuleConfigVO getVO(String cryptoName) {
		CryptoModuleConfigVO vo = configMap.get(cryptoName);
		if (vo == null) {
			throw new IllegalArgumentException("암호화모듈 미등록: " + cryptoName);
		}
		return vo;
	}

	private CryptoModuleExtension buildExtension(CryptoModuleConfigVO vo, byte[] iv,
			byte[] encKey, byte[] decKey) throws Exception {
		CryptoModuleExtension ext = "ARIA".equalsIgnoreCase(vo.getAlgType())
				? new ARIACryptoModuleExtension()
				: new AESCryptoModuleExtension();
		ext.init(vo.getAlgType(), vo.getCipherMode(), vo.getPadding(), iv, encKey, decKey);
		return ext;
	}

	/**
	 * DB key_deriv_strategy 컬럼에 저장된 FQCN으로 전략 클래스를 로드한다.
	 * 한 번 로드된 인스턴스는 strategyCache에 보관하여 재사용한다.
	 */
	private KeyDerivationStrategy resolveStrategy(String fqcn) {
		return strategyCache.computeIfAbsent(fqcn, key -> {
			try {
				Class<?> clazz = Class.forName(key.trim());
				return (KeyDerivationStrategy) clazz.getDeclaredConstructor().newInstance();
			} catch (Exception e) {
				throw new IllegalArgumentException("전략 클래스 로드 실패: " + key, e);
			}
		});
	}

	private Map<String, String> parseParams(String json) throws Exception {
		if (json == null || json.trim().isEmpty()) {
			return new HashMap<>();
		}
		return objectMapper.readValue(json, new TypeReference<Map<String, String>>() {});
	}

	private void evictDynamicCache(String cryptoId) {
		configMap.values().stream()
				.filter(vo -> cryptoId.equals(vo.getCryptoId()))
				.map(CryptoModuleConfigVO::getCryptoName)
				.forEach(name -> {
					Iterator<String> it = dynamicKeyCache.keySet().iterator();
					while (it.hasNext()) {
						if (it.next().startsWith(name + ":")) it.remove();
					}
				});
	}

	private org.springframework.context.ApplicationContext ctx() {
		return ApplicationContextProvider.getContext();
	}

	@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 boolean isStarted() {
		return started;
	}

	private static class CachedDerivedKey {
		private final DerivedKey derivedKey;
		private final long expireAt;

		CachedDerivedKey(DerivedKey derivedKey, int ttlSec) {
			this.derivedKey = derivedKey;
			this.expireAt = System.currentTimeMillis() + (ttlSec * 1000L);
		}

		boolean isExpired() {
			return System.currentTimeMillis() > expireAt;
		}

		DerivedKey getDerivedKey() {
			return derivedKey;
		}
	}
}
