package com.goldenchart.service;

import com.goldenchart.dto.LiveStrategyBulkRequest;
import com.goldenchart.dto.LiveStrategySettingsDto;
import com.goldenchart.entity.GcAppSettings;
import com.goldenchart.entity.GcLiveStrategySettings;
import com.goldenchart.entity.GcWatchlist;
import com.goldenchart.repository.GcLiveStrategySettingsRepository;
import com.goldenchart.repository.GcWatchlistRepository;
import com.goldenchart.websocket.DynamicSubscriptionManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class LiveStrategySettingsService {

    private final GcLiveStrategySettingsRepository repo;
    private final GcWatchlistRepository            watchlistRepo;
    private final AppSettingsService               appSettingsService;
    private final DynamicSubscriptionManager       subscriptionManager;
    private final LiveStrategyEvaluator            liveStrategyEvaluator;
    private final StrategyConditionTimeframeService conditionTimeframes;

    // ── 조회 ──────────────────────────────────────────────────────────────────

    @Transactional(readOnly = true)
    public LiveStrategySettingsDto get(Long userId, String deviceId, String market) {
        GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
        Optional<GcLiveStrategySettings> entity = findEntity(userId, deviceId, market);
        LiveStrategySettingsDto dto = entity.isPresent()
                ? toDto(entity.get())
                : defaultDtoFromApp(market, app);
        // 실시간 체크 마스터 스위치·전략 ID는 앱 전역 설정(gc_app_settings) 기준
        dto = mergeGlobalTemplate(dto, app, market);
        enrichStrategyTimeframes(dto);
        return dto;
    }

    @Transactional(readOnly = true)
    public List<LiveStrategySettingsDto> listActive(Long userId, String deviceId) {
        GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
        if (!Boolean.TRUE.equals(app.getLiveStrategyCheck()) || app.getLiveStrategyId() == null) {
            return List.of();
        }
        List<String> watchlist = listWatchlistSymbols(userId, deviceId);
        Long strategyId = app.getLiveStrategyId();
        List<String> strategyCandleTypes = conditionTimeframes.collectForStrategyList(strategyId);
        return watchlist.stream()
                .map(m -> LiveStrategySettingsDto.builder()
                            .market(m)
                            .strategyId(strategyId)
                            .liveCheck(true)
                            .executionType(app.getLiveExecutionType())
                            .positionMode(app.getLivePositionMode())
                            .strategyCandleTypes(strategyCandleTypes)
                            .build())
                .toList();
    }

    // ── 관심종목 연동 (WatchlistService 에서 호출) ─────────────────────────────

    /** 관심종목 등록 시 — DB 관심종목 = 전략 체크 대상 행 생성/갱신 */
    public void enableForWatchlistMarket(Long userId, String deviceId, String market) {
        GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
        LiveStrategySettingsDto template = findEntity(userId, deviceId, market)
                .map(e -> mergeGlobalTemplate(toDto(e), app, market))
                .orElseGet(() -> mergeGlobalTemplate(defaultDtoFromApp(market, app), app, market));
        template.setLiveCheck(true);
        enrichStrategyTimeframes(template);
        upsertEntity(userId, deviceId, template);
        pinIfReady(market, template);
        log.debug("[LiveStrategy] watchlist add → enabled {}", market);
    }

    /** 관심종목 해제 시 — 해당 종목 전략 체크 비활성화 */
    public void disableForWatchlistMarket(Long userId, String deviceId, String market) {
        findEntity(userId, deviceId, market).ifPresent(entity -> {
            entity.setIsLiveCheck(false);
            repo.save(entity);
            liveStrategyEvaluator.invalidateCache(market);
        });
        log.debug("[LiveStrategy] watchlist remove → disabled {}", market);
    }

    // ── 일괄 저장 (레거시 API) ─────────────────────────────────────────────────

    public List<LiveStrategySettingsDto> saveBulk(Long userId, String deviceId,
                                                   LiveStrategyBulkRequest req) {
        if (req.getMarkets() == null || req.getMarkets().isEmpty()) {
            return List.of();
        }
        LiveStrategySettingsDto template = req.getTemplate() != null
                ? req.getTemplate()
                : defaultDtoFromApp("KRW-BTC", appSettingsService.getEntity(userId, deviceId));
        persistGlobalTemplate(userId, deviceId, template);
        List<LiveStrategySettingsDto> results = new ArrayList<>();
        for (String market : req.getMarkets()) {
            if (market == null || market.isBlank()) continue;
            LiveStrategySettingsDto one = LiveStrategySettingsDto.builder()
                    .market(market)
                    .strategyId(template.getStrategyId())
                    .liveCheck(true)
                    .executionType(template.getExecutionType())
                    .positionMode(template.getPositionMode())
                    .build();
            results.add(upsertEntity(userId, deviceId, one));
        }
        log.info("[LiveStrategySettings] bulk save: {} markets", results.size());
        return results;
    }

    // ── 저장/갱신 — 전역 템플릿 + 관심종목 전체 동기화 ─────────────────────────

    public LiveStrategySettingsDto save(Long userId, String deviceId,
                                        LiveStrategySettingsDto dto) {
        persistGlobalTemplate(userId, deviceId, dto);
        syncAllWatchlistMarkets(userId, deviceId, dto);
        String market = dto.getMarket() != null ? dto.getMarket() : "KRW-BTC";
        log.info("[LiveStrategySettings] global saved + watchlist sync, template market={}", market);
        GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
        LiveStrategySettingsDto out = findEntity(userId, deviceId, market)
                .map(this::toDto)
                .orElseGet(() -> defaultDtoFromApp(market, app));
        out = mergeGlobalTemplate(out, app, market);
        if (dto.getExecutionType() != null) {
            out.setExecutionType(dto.getExecutionType());
        }
        if (dto.getPositionMode() != null) {
            out.setPositionMode(dto.getPositionMode());
        }
        enrichStrategyTimeframes(out);
        return out;
    }

    // ── Private ───────────────────────────────────────────────────────────────

    private void persistGlobalTemplate(Long userId, String deviceId, LiveStrategySettingsDto dto) {
        Map<String, Object> patch = new HashMap<>();
        patch.put("liveStrategyCheck", dto.isLiveCheck());
        patch.put("liveStrategyId", dto.getStrategyId());
        patch.put("liveExecutionType", dto.getExecutionType());
        patch.put("livePositionMode", dto.getPositionMode());
        appSettingsService.save(userId, deviceId, patch);
    }

    private void syncAllWatchlistMarkets(Long userId, String deviceId,
                                        LiveStrategySettingsDto template) {
        GcAppSettings app = appSettingsService.getEntity(userId, deviceId);
        boolean active = Boolean.TRUE.equals(app.getLiveStrategyCheck())
                && app.getLiveStrategyId() != null;
        for (String symbol : listWatchlistSymbols(userId, deviceId)) {
            LiveStrategySettingsDto row = findEntity(userId, deviceId, symbol)
                    .map(e -> mergeGlobalTemplate(toDto(e), app, symbol))
                    .orElseGet(() -> defaultDtoFromApp(symbol, app));
            row.setLiveCheck(active);
            row.setStrategyId(app.getLiveStrategyId());
            row.setExecutionType(app.getLiveExecutionType() != null
                    ? app.getLiveExecutionType() : row.getExecutionType());
            row.setPositionMode(app.getLivePositionMode() != null
                    ? app.getLivePositionMode() : row.getPositionMode());
            enrichStrategyTimeframes(row);
            LiveStrategySettingsDto saved = upsertEntity(userId, deviceId, row);
            if (active) {
                pinIfReady(symbol, saved);
            }
        }
    }

    private void enrichStrategyTimeframes(LiveStrategySettingsDto dto) {
        if (dto.getStrategyId() != null) {
            dto.setStrategyCandleTypes(
                    conditionTimeframes.collectForStrategyList(dto.getStrategyId()));
        }
    }

    private void pinAllStrategyTimeframes(String market, Long strategyId) {
        if (strategyId == null) return;
        subscriptionManager.ensureMarketPinned(market, "1m");
        for (String ct : conditionTimeframes.collectForStrategy(strategyId)) {
            if (!"1m".equals(ct)) {
                subscriptionManager.ensureMarketPinned(market, ct);
            }
        }
    }

    private LiveStrategySettingsDto upsertEntity(Long userId, String deviceId,
                                                  LiveStrategySettingsDto dto) {
        String market = dto.getMarket() != null ? dto.getMarket() : "KRW-BTC";
        GcLiveStrategySettings entity = findEntity(userId, deviceId, market)
                .orElseGet(() -> GcLiveStrategySettings.builder()
                        .userId(userId)
                        .deviceId(userId == null ? deviceId : null)
                        .market(market)
                        .build());

        applyOwnerKeys(entity, userId, deviceId);

        entity.setStrategyId(dto.getStrategyId());
        entity.setIsLiveCheck(dto.isLiveCheck());
        entity.setExecutionType(
                "REALTIME_TICK".equals(dto.getExecutionType()) ? "REALTIME_TICK" : "CANDLE_CLOSE");
        entity.setPositionMode(
                "SIGNAL_ONLY".equals(dto.getPositionMode()) ? "SIGNAL_ONLY" : "LONG_ONLY");

        repo.save(entity);
        liveStrategyEvaluator.invalidateCache(market);
        return toDto(entity);
    }

    private void pinIfReady(String market, LiveStrategySettingsDto dto) {
        if (Boolean.TRUE.equals(dto.isLiveCheck()) && dto.getStrategyId() != null) {
            pinAllStrategyTimeframes(market, dto.getStrategyId());
        }
    }

    private List<String> listWatchlistSymbols(Long userId, String deviceId) {
        List<GcWatchlist> list = userId != null
                ? watchlistRepo.findByUserIdOrderByDisplayOrderAsc(userId)
                : watchlistRepo.findByDeviceIdOrderByDisplayOrderAsc(
                        deviceId != null ? deviceId : "anonymous");
        return list.stream().map(GcWatchlist::getSymbol).toList();
    }

    /**
     * user_id·device_id 어느 쪽으로 저장됐든 동일 마켓 행을 찾는다.
     * (게스트 → 로그인 전환 시 device_id 행만 있어도 upsert 가 INSERT 로 중복 나지 않게)
     */
    private Optional<GcLiveStrategySettings> findEntity(Long userId, String deviceId,
                                                         String market) {
        if (userId != null) {
            Optional<GcLiveStrategySettings> byUser = repo.findByUserIdAndMarket(userId, market);
            if (byUser.isPresent()) return byUser;
        }
        if (deviceId != null && !deviceId.isBlank()) {
            return repo.findByDeviceIdAndMarket(deviceId, market);
        }
        return Optional.empty();
    }

    /** 회원: user_id 전용 행 / 비회원: device_id 전용 행 (UK 충돌 방지) */
    private void applyOwnerKeys(GcLiveStrategySettings entity, Long userId, String deviceId) {
        if (userId != null) {
            entity.setUserId(userId);
            entity.setDeviceId(null);
        } else {
            entity.setUserId(null);
            entity.setDeviceId(deviceId != null && !deviceId.isBlank() ? deviceId : null);
        }
    }

    private LiveStrategySettingsDto toDto(GcLiveStrategySettings e) {
        LiveStrategySettingsDto dto = LiveStrategySettingsDto.builder()
                .market(e.getMarket())
                .strategyId(e.getStrategyId())
                .liveCheck(Boolean.TRUE.equals(e.getIsLiveCheck()))
                .executionType(e.getExecutionType())
                .positionMode(e.getPositionMode() != null ? e.getPositionMode() : "LONG_ONLY")
                .build();
        enrichStrategyTimeframes(dto);
        return dto;
    }

    /** 앱 전역 실시간 체크 ON/OFF·전략 ID를 DTO에 반영 (종목별 행과 분리) */
    private LiveStrategySettingsDto mergeGlobalTemplate(
            LiveStrategySettingsDto dto, GcAppSettings app, String market) {
        dto.setMarket(market);
        dto.setLiveCheck(Boolean.TRUE.equals(app.getLiveStrategyCheck()));
        dto.setStrategyId(app.getLiveStrategyId());
        if (app.getLiveExecutionType() != null) {
            dto.setExecutionType(app.getLiveExecutionType());
        }
        if (app.getLivePositionMode() != null) {
            dto.setPositionMode(app.getLivePositionMode());
        }
        return dto;
    }

    private LiveStrategySettingsDto defaultDtoFromApp(String market, GcAppSettings app) {
        return LiveStrategySettingsDto.builder()
                .market(market)
                .strategyId(app.getLiveStrategyId())
                .liveCheck(Boolean.TRUE.equals(app.getLiveStrategyCheck()))
                .executionType(app.getLiveExecutionType() != null ? app.getLiveExecutionType() : "CANDLE_CLOSE")
                .positionMode(app.getLivePositionMode() != null ? app.getLivePositionMode() : "LONG_ONLY")
                .build();
    }
}
