package com.goldenchart.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.goldenchart.entity.GcIndicatorSettings;
import com.goldenchart.repository.GcIndicatorSettingsRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* 전역 지표 파라미터 설정 서비스.
*
*
프론트엔드 indicatorRegistry.ts 의 defaultParams 를 완전히 대체하며,
* 사용자가 변경한 파라미터를 DB 에 저장하고 백엔드 지표 계산 시 우선 적용한다.
*/
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class IndicatorSettingsService {
private final GcIndicatorSettingsRepository repo;
private final ObjectMapper mapper;
// ── 공개 API ─────────────────────────────────────────────────────────────
/**
* 장치/사용자 식별자로 전체 지표 파라미터 맵을 조회한다.
* 없으면 빈 맵 반환.
*
* @return Map<indicatorType, Map<paramKey, paramValue>>
*/
@Transactional(readOnly = true)
public Map> getAll(Long userId, String deviceId) {
return findEntity(userId, deviceId)
.map(s -> parseParamsJson(s.getParamsJson()))
.orElse(Collections.emptyMap());
}
/**
* 특정 지표의 파라미터를 조회한다.
* 없으면 빈 맵 반환.
*/
@Transactional(readOnly = true)
public Map getForType(Long userId, String deviceId, String indicatorType) {
Map> all = getAll(userId, deviceId);
return all.getOrDefault(indicatorType, Collections.emptyMap());
}
/**
* 전체 지표 파라미터를 저장(덮어쓰기)한다.
* 프론트엔드에서 전체 설정을 한 번에 저장할 때 사용.
*
* @param allParams Map<indicatorType, Map<paramKey, paramValue>>
*/
public void saveAll(Long userId, String deviceId,
Map> allParams) {
GcIndicatorSettings entity = findOrCreate(userId, deviceId);
entity.setParamsJson(toJson(allParams));
repo.save(entity);
log.debug("[IndicatorSettings] saved {} indicator types for device={}", allParams.size(), deviceId);
}
/**
* 특정 지표의 파라미터만 병합(upsert)한다.
* 프론트엔드에서 단일 지표 파라미터를 변경할 때 사용.
*
* @param indicatorType 지표 타입 (e.g. "RSI", "MACD")
* @param params 파라미터 맵 (e.g. {"length": 9, "src": "close"})
*/
public void saveForType(Long userId, String deviceId,
String indicatorType, Map params) {
GcIndicatorSettings entity = findOrCreate(userId, deviceId);
Map> all = new HashMap<>(parseParamsJson(entity.getParamsJson()));
all.put(indicatorType, params);
entity.setParamsJson(toJson(all));
repo.save(entity);
log.debug("[IndicatorSettings] saved params for {}:{} device={}", indicatorType, params, deviceId);
}
// ── 시각 설정 (색상·선굵기·수평선) ──────────────────────────────────────
/**
* 전체 지표의 시각 설정을 조회한다.
*
* @return Map<indicatorType, Map{plots, hlines}>
*/
@Transactional(readOnly = true)
public Map> getAllVisual(Long userId, String deviceId) {
return findEntity(userId, deviceId)
.map(s -> parseVisualJson(s.getVisualConfigJson()))
.orElse(Collections.emptyMap());
}
/**
* 특정 지표의 시각 설정(색상·선굵기·수평선)을 저장(병합)한다.
*
* @param indicatorType 지표 타입 (e.g. "RSI")
* @param visual {@code {plots:[...], hlines:[...]}} 구조의 맵
*/
public void saveVisualForType(Long userId, String deviceId,
String indicatorType, Map visual) {
GcIndicatorSettings entity = findOrCreate(userId, deviceId);
Map> all = new HashMap<>(parseVisualJson(entity.getVisualConfigJson()));
all.put(indicatorType, visual);
entity.setVisualConfigJson(toJson(all));
repo.save(entity);
log.debug("[IndicatorSettings] saved visual for {}:{} device={}", indicatorType, visual, deviceId);
}
/**
* 요청 params 에 없는 키를 DB 설정값으로 채운다.
* IndicatorController 에서 호출 — 하드코딩 기본값 제거.
*
* @param requestParams 프론트엔드가 보낸 파라미터 (null 허용)
* @param indicatorType 지표 타입
* @return 병합된 파라미터 맵 (DB 값 우선, 없으면 요청값)
*/
@Transactional(readOnly = true)
public Map mergeWithDb(Long userId, String deviceId,
Map requestParams,
String indicatorType) {
Map dbParams = getForType(userId, deviceId, indicatorType);
if (dbParams.isEmpty() && (requestParams == null || requestParams.isEmpty())) {
return Map.of(); // 둘 다 없으면 빈 맵 반환 (IndicatorService 내부 기본값 사용)
}
// DB 값 기반 + 요청 파라미터로 덮어쓰기 (요청이 명시적으로 보낸 값 우선)
Map merged = new HashMap<>(dbParams);
if (requestParams != null) {
merged.putAll(requestParams);
}
return merged;
}
// ── Private helpers ───────────────────────────────────────────────────────
private Optional findEntity(Long userId, String deviceId) {
if (userId != null) return repo.findByUserId(userId);
if (deviceId != null) return repo.findByDeviceId(deviceId);
return Optional.empty();
}
private GcIndicatorSettings findOrCreate(Long userId, String deviceId) {
return findEntity(userId, deviceId).orElseGet(() ->
repo.save(GcIndicatorSettings.builder()
.userId(userId)
.deviceId(deviceId)
.paramsJson("{}")
.build()));
}
@SuppressWarnings("unchecked")
private Map> parseParamsJson(String json) {
if (json == null || json.isBlank() || "{}".equals(json.trim())) {
return new HashMap<>();
}
try {
return mapper.readValue(json,
new TypeReference