package com.gcsc.connection.monitoring.service;

import com.gcsc.connection.common.exception.BusinessException;
import com.gcsc.connection.common.exception.ErrorCode;
import com.gcsc.connection.monitoring.dto.DailyUptimeResponse;
import com.gcsc.connection.monitoring.dto.HeartbeatStatusResponse;
import com.gcsc.connection.monitoring.dto.HealthHistoryResponse;
import com.gcsc.connection.monitoring.dto.RecentCheckResponse;
import com.gcsc.connection.monitoring.dto.ServiceStatusDetailResponse;
import com.gcsc.connection.monitoring.entity.SnpServiceHealthLog;
import com.gcsc.connection.monitoring.repository.SnpServiceHealthLogRepository;
import com.gcsc.connection.service.entity.ServiceStatus;
import com.gcsc.connection.service.entity.SnpService;
import com.gcsc.connection.service.repository.SnpServiceRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.reactive.function.client.WebClient;

import org.springframework.data.domain.PageRequest;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
@RequiredArgsConstructor
public class HeartbeatService {

    private final SnpServiceRepository snpServiceRepository;
    private final SnpServiceHealthLogRepository snpServiceHealthLogRepository;
    private final WebClient webClient;

    /**
     * 전체 서비스 헬스 상태 조회
     */
    @Transactional(readOnly = true)
    public List<HeartbeatStatusResponse> getAllServiceStatus() {
        return snpServiceRepository.findAll().stream()
            .map(HeartbeatStatusResponse::from)
            .toList();
    }

    /**
     * 서비스 헬스체크 이력 조회
     */
    @Transactional(readOnly = true)
    public List<HealthHistoryResponse> getHealthHistory(Long serviceId) {
        snpServiceRepository.findById(serviceId)
            .orElseThrow(() -> new BusinessException(ErrorCode.SERVICE_NOT_FOUND));

        return snpServiceHealthLogRepository.findByServiceServiceIdOrderByCheckedAtDesc(serviceId).stream()
            .map(HealthHistoryResponse::from)
            .toList();
    }

    /**
     * 서비스 상태 상세 조회 (status.claude.com 스타일)
     */
    @Transactional(readOnly = true)
    public ServiceStatusDetailResponse getServiceStatusDetail(Long serviceId) {
        SnpService service = snpServiceRepository.findById(serviceId)
            .orElseThrow(() -> new BusinessException(ErrorCode.SERVICE_NOT_FOUND));

        LocalDateTime since90d = LocalDateTime.now().minusDays(90);

        // 90일 일별 uptime
        List<Object[]> dailyRows = snpServiceHealthLogRepository.findDailyUptime(serviceId, since90d);
        List<DailyUptimeResponse> dailyUptime = new ArrayList<>();
        long totalAll = 0;
        long upAll = 0;

        for (Object[] row : dailyRows) {
            LocalDate date = ((java.sql.Date) row[0]).toLocalDate();
            long total = ((Number) row[1]).longValue();
            long upCount = ((Number) row[2]).longValue();
            double pct = total > 0 ? (double) upCount / total * 100 : 0;
            dailyUptime.add(new DailyUptimeResponse(date, total, upCount, pct));
            totalAll += total;
            upAll += upCount;
        }

        double uptimePercent90d = totalAll > 0 ? (double) upAll / totalAll * 100 : 0;

        // 최근 60건 체크 이력
        List<RecentCheckResponse> recentChecks = snpServiceHealthLogRepository
            .findByServiceServiceIdOrderByCheckedAtDesc(serviceId, PageRequest.of(0, 60))
            .stream()
            .map(RecentCheckResponse::from)
            .toList();

        return new ServiceStatusDetailResponse(
            service.getServiceId(),
            service.getServiceCode(),
            service.getServiceName(),
            service.getHealthStatus().name(),
            service.getHealthResponseTime(),
            service.getHealthCheckedAt() != null ? service.getHealthCheckedAt().toString() : null,
            uptimePercent90d,
            dailyUptime,
            recentChecks
        );
    }

    /**
     * 전체 서비스 상태 요약 (status page 메인)
     */
    @Transactional(readOnly = true)
    public List<ServiceStatusDetailResponse> getAllServiceStatusDetail() {
        return snpServiceRepository.findByIsActiveTrue().stream()
            .map(s -> getServiceStatusDetail(s.getServiceId()))
            .toList();
    }

    /**
     * 단일 서비스 헬스체크 실행
     */
    @Transactional
    public HeartbeatStatusResponse checkService(Long serviceId) {
        SnpService service = snpServiceRepository.findById(serviceId)
            .orElseThrow(() -> new BusinessException(ErrorCode.SERVICE_NOT_FOUND));

        performHealthCheck(service);
        return HeartbeatStatusResponse.from(service);
    }

    /**
     * 활성화된 모든 서비스 헬스체크 실행
     */
    @Transactional
    public void checkAllActiveServices() {
        List<SnpService> activeServices = snpServiceRepository.findByIsActiveTrue();
        activeServices.stream()
            .filter(s -> s.getHealthCheckUrl() != null && !s.getHealthCheckUrl().isBlank())
            .forEach(this::performHealthCheck);
    }

    /**
     * 개별 서비스 헬스체크 수행
     */
    private void performHealthCheck(SnpService service) {
        ServiceStatus previousStatus = service.getHealthStatus();
        ServiceStatus newStatus;
        Integer responseTime = null;
        String errorMessage = null;

        long start = System.currentTimeMillis();

        try {
            webClient.get()
                .uri(service.getHealthCheckUrl())
                .retrieve()
                .toBodilessEntity()
                .block();

            newStatus = ServiceStatus.UP;
            responseTime = (int) (System.currentTimeMillis() - start);
        } catch (Exception e) {
            newStatus = ServiceStatus.DOWN;
            responseTime = (int) (System.currentTimeMillis() - start);
            errorMessage = e.getMessage();
            log.warn("헬스체크 실패 - 서비스: {}, 오류: {}", service.getServiceCode(), e.getMessage());
        }

        // 매 체크마다 기록 (status page 이력용)
        SnpServiceHealthLog healthLog = SnpServiceHealthLog.builder()
            .service(service)
            .previousStatus(previousStatus.name())
            .currentStatus(newStatus.name())
            .responseTime(responseTime)
            .errorMessage(errorMessage)
            .checkedAt(LocalDateTime.now())
            .build();
        snpServiceHealthLogRepository.save(healthLog);

        if (previousStatus != newStatus) {
            log.info("서비스 상태 변경 - {}: {} -> {}", service.getServiceCode(), previousStatus, newStatus);
        }

        service.updateHealthStatus(newStatus, responseTime);
        snpServiceRepository.save(service);
    }
}
