package com.gcsc.connection.apikey.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gcsc.connection.apikey.dto.ApiKeyCreateResponse;
import com.gcsc.connection.apikey.dto.ApiKeyRequestCreateDto;
import com.gcsc.connection.apikey.dto.ApiKeyRequestResponse;
import com.gcsc.connection.apikey.dto.ApiKeyRequestReviewDto;
import com.gcsc.connection.apikey.entity.ApiKeyStatus;
import com.gcsc.connection.apikey.entity.KeyRequestStatus;
import com.gcsc.connection.apikey.entity.SnpApiKey;
import com.gcsc.connection.apikey.entity.SnpApiKeyRequest;
import com.gcsc.connection.apikey.entity.SnpApiPermission;
import com.gcsc.connection.apikey.repository.SnpApiKeyRepository;
import com.gcsc.connection.apikey.repository.SnpApiKeyRequestRepository;
import com.gcsc.connection.apikey.repository.SnpApiPermissionRepository;
import com.gcsc.connection.common.exception.BusinessException;
import com.gcsc.connection.common.exception.ErrorCode;
import com.gcsc.connection.common.util.AesEncryptor;
import com.gcsc.connection.service.repository.SnpServiceApiRepository;
import com.gcsc.connection.user.entity.SnpUser;
import com.gcsc.connection.user.repository.SnpUserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.security.SecureRandom;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

/**
 * API Key 신청/심사 워크플로우 서비스
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class ApiKeyRequestService {

    private static final int RAW_KEY_HEX_LENGTH = 32;
    private static final String KEY_PREFIX = "snp_";
    private static final int PREFIX_LENGTH = 8;

    private final SnpApiKeyRequestRepository snpApiKeyRequestRepository;
    private final SnpApiKeyRepository snpApiKeyRepository;
    private final SnpApiPermissionRepository snpApiPermissionRepository;
    private final SnpServiceApiRepository snpServiceApiRepository;
    private final SnpUserRepository snpUserRepository;
    private final AesEncryptor aesEncryptor;
    private final ObjectMapper objectMapper;

    /**
     * API Key 신청 생성
     */
    @Transactional
    public ApiKeyRequestResponse createRequest(Long userId, ApiKeyRequestCreateDto dto) {
        SnpUser user = snpUserRepository.findById(userId)
            .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));

        String requestedApisJson = toJson(dto.requestedApiIds());

        LocalDateTime fromDate = dto.usageFromDate() != null
            ? LocalDate.parse(dto.usageFromDate()).atStartOfDay() : null;
        LocalDateTime toDate = dto.usageToDate() != null
            ? LocalDate.parse(dto.usageToDate()).atStartOfDay() : null;

        SnpApiKeyRequest request = SnpApiKeyRequest.builder()
            .user(user)
            .keyName(dto.keyName())
            .purpose(dto.purpose())
            .requestedApis(requestedApisJson)
            .serviceIp(dto.serviceIp())
            .servicePurpose(dto.servicePurpose())
            .dailyRequestEstimate(dto.dailyRequestEstimate())
            .usageFromDate(fromDate)
            .usageToDate(toDate)
            .build();

        SnpApiKeyRequest saved = snpApiKeyRequestRepository.save(request);
        return ApiKeyRequestResponse.from(saved);
    }

    /**
     * 내 신청 목록 조회
     */
    @Transactional(readOnly = true)
    public List<ApiKeyRequestResponse> getMyRequests(Long userId) {
        return snpApiKeyRequestRepository.findByUserUserId(userId).stream()
            .map(ApiKeyRequestResponse::from)
            .toList();
    }

    /**
     * 전체 신청 목록 조회 (최신순)
     */
    @Transactional(readOnly = true)
    public List<ApiKeyRequestResponse> getAllRequests() {
        return snpApiKeyRequestRepository.findAllByOrderByCreatedAtDesc().stream()
            .map(ApiKeyRequestResponse::from)
            .toList();
    }

    /**
     * 대기 중인 신청 목록 조회
     */
    @Transactional(readOnly = true)
    public List<ApiKeyRequestResponse> getPendingRequests() {
        return snpApiKeyRequestRepository.findByStatus(KeyRequestStatus.PENDING).stream()
            .map(ApiKeyRequestResponse::from)
            .toList();
    }

    /**
     * 신청 심사 (승인/거절)
     */
    @Transactional
    public ApiKeyCreateResponse reviewRequest(Long requestId, Long reviewerId,
                                              ApiKeyRequestReviewDto dto) {
        SnpApiKeyRequest request = snpApiKeyRequestRepository.findById(requestId)
            .orElseThrow(() -> new BusinessException(ErrorCode.API_KEY_REQUEST_NOT_FOUND));

        if (request.getStatus() != KeyRequestStatus.PENDING) {
            throw new BusinessException(ErrorCode.API_KEY_REQUEST_ALREADY_PROCESSED);
        }

        SnpUser reviewer = snpUserRepository.findById(reviewerId)
            .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));

        if ("APPROVED".equals(dto.status())) {
            return approveRequest(request, reviewer, dto);
        } else {
            request.reject(reviewer, dto.reviewComment());
            return null;
        }
    }

    private ApiKeyCreateResponse approveRequest(SnpApiKeyRequest request, SnpUser reviewer,
                                                ApiKeyRequestReviewDto dto) {
        String adjustedApisJson = dto.adjustedApiIds() != null && !dto.adjustedApiIds().isEmpty()
            ? toJson(dto.adjustedApiIds()) : null;
        LocalDateTime adjFrom = dto.adjustedFromDate() != null
            ? LocalDate.parse(dto.adjustedFromDate()).atStartOfDay() : null;
        LocalDateTime adjTo = dto.adjustedToDate() != null
            ? LocalDate.parse(dto.adjustedToDate()).atStartOfDay() : null;
        request.approve(reviewer, adjustedApisJson, adjFrom, adjTo);

        // API Key 생성
        String rawKey = generateRawKey();
        String prefix = rawKey.substring(0, PREFIX_LENGTH);
        String encryptedKey = aesEncryptor.encrypt(rawKey);

        // 일일 요청 제한: 검토자가 조정한 값 > 신청자 입력값 > null(무제한)
        Long dailyLimit = dto.adjustedDailyRequestLimit() != null
            ? dto.adjustedDailyRequestLimit()
            : request.getDailyRequestEstimate();

        SnpApiKey apiKey = SnpApiKey.builder()
            .user(request.getUser())
            .apiKey(encryptedKey)
            .apiKeyPrefix(prefix)
            .keyName(request.getKeyName())
            .status(ApiKeyStatus.ACTIVE)
            .approvedBy(reviewer)
            .approvedAt(LocalDateTime.now())
            .expiresAt(request.getUsageToDate())
            .dailyRequestLimit(dailyLimit)
            .build();

        SnpApiKey savedKey = snpApiKeyRepository.save(apiKey);

        // 권한 생성
        List<Long> apiIds = dto.adjustedApiIds() != null && !dto.adjustedApiIds().isEmpty()
            ? dto.adjustedApiIds()
            : parseApiIds(request.getRequestedApis());

        createPermissions(savedKey, apiIds, reviewer.getUserId());

        return new ApiKeyCreateResponse(
            savedKey.getApiKeyId(),
            savedKey.getKeyName(),
            rawKey,
            savedKey.getApiKeyPrefix(),
            savedKey.getStatus().name()
        );
    }

    private void createPermissions(SnpApiKey apiKey, List<Long> apiIds, Long grantedBy) {
        if (apiIds == null || apiIds.isEmpty()) {
            return;
        }

        List<SnpApiPermission> permissions = apiIds.stream()
            .map(apiId -> snpServiceApiRepository.findById(apiId)
                .map(serviceApi -> SnpApiPermission.builder()
                    .apiKey(apiKey)
                    .api(serviceApi)
                    .isActive(true)
                    .grantedBy(grantedBy)
                    .grantedAt(LocalDateTime.now())
                    .build())
                .orElse(null))
            .filter(perm -> perm != null)
            .toList();

        snpApiPermissionRepository.saveAll(permissions);
    }

    private String generateRawKey() {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[RAW_KEY_HEX_LENGTH / 2];
        random.nextBytes(bytes);
        StringBuilder hex = new StringBuilder(KEY_PREFIX);
        for (byte b : bytes) {
            hex.append(String.format("%02x", b));
        }
        return hex.toString();
    }

    private String toJson(List<Long> apiIds) {
        if (apiIds == null || apiIds.isEmpty()) {
            return null;
        }
        try {
            return objectMapper.writeValueAsString(apiIds);
        } catch (JsonProcessingException e) {
            log.error("API ID 목록 JSON 변환 실패", e);
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private List<Long> parseApiIds(String json) {
        if (json == null || json.isBlank()) {
            return List.of();
        }
        try {
            return objectMapper.readValue(json, List.class);
        } catch (JsonProcessingException e) {
            log.error("API ID 목록 JSON 파싱 실패", e);
            return List.of();
        }
    }
}
