package com.tasteby.service;

import com.tasteby.domain.DaemonConfig;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

/**
 * Background daemon that periodically scans channels and processes pending videos.
 */
@Service
public class DaemonScheduler {

    private static final Logger log = LoggerFactory.getLogger(DaemonScheduler.class);

    private final DaemonConfigService daemonConfigService;
    private final YouTubeService youTubeService;
    private final PipelineService pipelineService;
    private final CacheService cacheService;

    @Value("${app.daemon.enabled:true}")
    private boolean instanceEnabled;

    public DaemonScheduler(DaemonConfigService daemonConfigService,
                            YouTubeService youTubeService,
                            PipelineService pipelineService,
                            CacheService cacheService) {
        this.daemonConfigService = daemonConfigService;
        this.youTubeService = youTubeService;
        this.pipelineService = pipelineService;
        this.cacheService = cacheService;
    }

    @Scheduled(fixedDelay = 30_000) // Check every 30 seconds
    // #335 — 분산 락: 멀티 파드 환경에서 한 인스턴스만 실행. Redis 키 `lock:daemon-runner`.
    // lockAtMostFor: 작업이 비정상 종료돼도 15분 후 강제 해제 (다음 cron이 잡을 수 있게)
    // lockAtLeastFor: 빨리 끝나도 30초 동안 유지 (즉시 다른 cron이 같은 작업 잡는 것 방지)
    @SchedulerLock(name = "daemon-runner", lockAtMostFor = "PT15M", lockAtLeastFor = "PT30S")
    public void run() {
        // 인스턴스 차원 차단(dev/prod 동일 DB 공유 환경에서 dev 쪽 동시 폴링 방지).
        // dev .env: DAEMON_ENABLED=false → 이 인스턴스는 스케줄러 동작 안 함.
        // prod: 미설정 → 기본 true.
        if (!instanceEnabled) return;
        try {
            var config = getConfig();
            if (config == null) return;

            if (config.isScanEnabled()) {
                Instant lastScan = config.getLastScanAt() != null ? config.getLastScanAt().toInstant() : null;
                if (lastScan == null || Instant.now().isAfter(lastScan.plus(config.getScanIntervalMin(), ChronoUnit.MINUTES))) {
                    log.info("Running scheduled channel scan...");
                    int newVideos = 0;
                    try {
                        newVideos = youTubeService.scanAllChannels();
                    } finally {
                        // #275 — 외부 호출 예외 시에도 last_scan_at을 갱신해 다음 cron까지의 backoff를 보장
                        daemonConfigService.updateLastScan();
                    }
                    if (newVideos > 0) {
                        cacheService.flush();
                        log.info("Scan completed: {} new videos", newVideos);
                    }
                }
            }

            if (config.isProcessEnabled()) {
                Instant lastProcess = config.getLastProcessAt() != null ? config.getLastProcessAt().toInstant() : null;
                if (lastProcess == null || Instant.now().isAfter(lastProcess.plus(config.getProcessIntervalMin(), ChronoUnit.MINUTES))) {
                    log.info("Running scheduled video processing (limit={})...", config.getProcessLimit());
                    int restaurants = 0;
                    try {
                        restaurants = pipelineService.processPending(config.getProcessLimit());
                    } finally {
                        daemonConfigService.updateLastProcess();
                    }
                    if (restaurants > 0) {
                        cacheService.flush();
                        log.info("Processing completed: {} restaurants extracted", restaurants);
                    }
                }
            }
        } catch (Exception e) {
            log.error("Daemon scheduler error: {}", e.getMessage());
        }
    }

    private DaemonConfig getConfig() {
        try {
            return daemonConfigService.getConfig();
        } catch (Exception e) {
            log.debug("Cannot read daemon config: {}", e.getMessage());
            return null;
        }
    }
}
