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;

/**
 * 전역 지표 파라미터 설정 서비스.
 *
 * <p>프론트엔드 indicatorRegistry.ts 의 defaultParams 를 완전히 대체하며,
 * 사용자가 변경한 파라미터를 DB 에 저장하고 백엔드 지표 계산 시 우선 적용한다.</p>
 */
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class IndicatorSettingsService {

    private final GcIndicatorSettingsRepository repo;
    private final ObjectMapper mapper;

    // ── 공개 API ─────────────────────────────────────────────────────────────

    /**
     * 장치/사용자 식별자로 전체 지표 파라미터 맵을 조회한다.
     * 없으면 빈 맵 반환.
     *
     * @return Map&lt;indicatorType, Map&lt;paramKey, paramValue&gt;&gt;
     */
    @Transactional(readOnly = true)
    public Map<String, Map<String, Object>> getAll(Long userId, String deviceId) {
        return findEntity(userId, deviceId)
                .map(s -> parseParamsJson(s.getParamsJson()))
                .orElse(Collections.emptyMap());
    }

    /**
     * 특정 지표의 파라미터를 조회한다.
     * 없으면 빈 맵 반환.
     */
    @Transactional(readOnly = true)
    public Map<String, Object> getForType(Long userId, String deviceId, String indicatorType) {
        Map<String, Map<String, Object>> all = getAll(userId, deviceId);
        return all.getOrDefault(indicatorType, Collections.emptyMap());
    }

    /**
     * 전체 지표 파라미터를 저장(덮어쓰기)한다.
     * 프론트엔드에서 전체 설정을 한 번에 저장할 때 사용.
     *
     * @param allParams Map&lt;indicatorType, Map&lt;paramKey, paramValue&gt;&gt;
     */
    public void saveAll(Long userId, String deviceId,
                        Map<String, Map<String, Object>> 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<String, Object> params) {
        GcIndicatorSettings entity = findOrCreate(userId, deviceId);
        Map<String, Map<String, Object>> 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&lt;indicatorType, Map{plots, hlines}&gt;
     */
    @Transactional(readOnly = true)
    public Map<String, Map<String, Object>> 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<String, Object> visual) {
        GcIndicatorSettings entity = findOrCreate(userId, deviceId);
        Map<String, Map<String, Object>> 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<String, Object> mergeWithDb(Long userId, String deviceId,
                                           Map<String, Object> requestParams,
                                           String indicatorType) {
        Map<String, Object> dbParams = getForType(userId, deviceId, indicatorType);
        if (dbParams.isEmpty() && (requestParams == null || requestParams.isEmpty())) {
            return Map.of(); // 둘 다 없으면 빈 맵 반환 (IndicatorService 내부 기본값 사용)
        }

        // DB 값 기반 + 요청 파라미터로 덮어쓰기 (요청이 명시적으로 보낸 값 우선)
        Map<String, Object> merged = new HashMap<>(dbParams);
        if (requestParams != null) {
            merged.putAll(requestParams);
        }
        return merged;
    }

    // ── Private helpers ───────────────────────────────────────────────────────

    private Optional<GcIndicatorSettings> 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<String, Map<String, Object>> parseParamsJson(String json) {
        if (json == null || json.isBlank() || "{}".equals(json.trim())) {
            return new HashMap<>();
        }
        try {
            return mapper.readValue(json,
                    new TypeReference<Map<String, Map<String, Object>>>() {});
        } catch (JsonProcessingException e) {
            log.warn("[IndicatorSettings] JSON 파싱 실패: {}", e.getMessage());
            return new HashMap<>();
        }
    }

    @SuppressWarnings("unchecked")
    private Map<String, Map<String, Object>> parseVisualJson(String json) {
        if (json == null || json.isBlank() || "{}".equals(json.trim())) {
            return new HashMap<>();
        }
        try {
            return mapper.readValue(json,
                    new TypeReference<Map<String, Map<String, Object>>>() {});
        } catch (JsonProcessingException e) {
            log.warn("[IndicatorSettings] visual JSON 파싱 실패: {}", e.getMessage());
            return new HashMap<>();
        }
    }

    private String toJson(Object obj) {
        try {
            return mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            log.error("[IndicatorSettings] JSON 직렬화 실패", e);
            return "{}";
        }
    }
}
