package com.ga.common.code;

import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

/**
 * 공통코드 조회/관리. Redis 캐시 적용.
 *
 * 캐시 키:  commonCode::{groupCode}
 * 무효화:   refreshCache(groupCode) 또는 코드 변경 시 자동.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class CommonCodeService {

    private final CommonCodeMapper mapper;

    /** 활성 코드 목록 (Redis 캐시) */
    @Cacheable(value = "commonCode", key = "#groupCode")
    public List<CommonCodeVO> getCodesByGroup(String groupCode) {
        return mapper.selectActiveCodes(groupCode);
    }

    /** 코드 → 코드명 (없으면 null) */
    public String getCodeName(String groupCode, String code) {
        if (code == null) return null;
        return getCodesByGroup(groupCode).stream()
                .filter(c -> code.equals(c.getCode()))
                .map(CommonCodeVO::getCodeName)
                .findFirst().orElse(null);
    }

    /** 유효성 검증. 미존재 시 예외. */
    public void validateCode(String groupCode, String code) {
        boolean exists = getCodesByGroup(groupCode).stream()
                .anyMatch(c -> c.getCode().equals(code));
        if (!exists) {
            throw new BizException(ErrorCode.INVALID_PARAMETER,
                    "[" + groupCode + "] 그룹의 [" + code + "] 코드가 없습니다");
        }
    }

    @CacheEvict(value = "commonCode", key = "#groupCode")
    public void refreshCache(String groupCode) {
        log.info("Common code cache evicted: {}", groupCode);
    }

    @CacheEvict(value = "commonCode", allEntries = true)
    public void refreshAll() {
        log.info("Common code cache evicted: ALL");
    }

    // ========== 그룹 관리 ==========

    public List<CommonCodeGroupVO> listGroups(String keyword) {
        return mapper.selectGroupList(keyword);
    }

    public CommonCodeGroupVO getGroup(String groupCode) {
        CommonCodeGroupVO g = mapper.selectGroup(groupCode);
        if (g == null) throw new BizException(ErrorCode.NOT_FOUND);
        return g;
    }

    @Transactional
    public void createGroup(CommonCodeGroupVO vo) {
        if (mapper.selectGroup(vo.getGroupCode()) != null) {
            throw new BizException(ErrorCode.DUPLICATE_DATA);
        }
        mapper.insertGroup(vo);
    }

    @Transactional
    public void updateGroup(CommonCodeGroupVO vo) {
        CommonCodeGroupVO g = getGroup(vo.getGroupCode());
        if ("Y".equals(g.getIsSystem())) {
            throw new BizException(ErrorCode.SYSTEM_CODE_LOCKED);
        }
        mapper.updateGroup(vo);
        refreshCache(vo.getGroupCode());
    }

    @Transactional
    @CacheEvict(value = "commonCode", key = "#groupCode")
    public void deleteGroup(String groupCode) {
        CommonCodeGroupVO g = getGroup(groupCode);
        if ("Y".equals(g.getIsSystem())) {
            throw new BizException(ErrorCode.SYSTEM_CODE_LOCKED);
        }
        mapper.deleteGroup(groupCode);
    }

    // ========== 상세 코드 관리 ==========

    public List<CommonCodeVO> listCodes(String groupCode) {
        return mapper.selectCodes(groupCode);
    }

    public CommonCodeVO getCode(Long codeId) {
        CommonCodeVO c = mapper.selectCode(codeId);
        if (c == null) throw new BizException(ErrorCode.NOT_FOUND);
        return c;
    }

    @Transactional
    public void createCode(CommonCodeVO vo) {
        if (mapper.existsByGroupAndCode(vo.getGroupCode(), vo.getCode()) > 0) {
            throw new BizException(ErrorCode.DUPLICATE_DATA);
        }
        mapper.insertCode(vo);
        refreshCache(vo.getGroupCode());
    }

    @Transactional
    public void updateCode(CommonCodeVO vo) {
        CommonCodeVO old = getCode(vo.getCodeId());
        // 시스템 그룹은 코드 수정 제한
        Optional<CommonCodeGroupVO> g = Optional.ofNullable(mapper.selectGroup(old.getGroupCode()));
        if (g.isPresent() && "Y".equals(g.get().getIsSystem())) {
            // 코드 식별자(code) 자체는 변경 불가, 코드명/설명만 허용
            vo.setCode(old.getCode());
            vo.setGroupCode(old.getGroupCode());
        }
        mapper.updateCode(vo);
        refreshCache(old.getGroupCode());
    }

    @Transactional
    public void deleteCode(Long codeId) {
        CommonCodeVO c = getCode(codeId);
        Optional<CommonCodeGroupVO> g = Optional.ofNullable(mapper.selectGroup(c.getGroupCode()));
        if (g.isPresent() && "Y".equals(g.get().getIsSystem())) {
            throw new BizException(ErrorCode.SYSTEM_CODE_LOCKED);
        }
        mapper.deleteCode(codeId);
        refreshCache(c.getGroupCode());
    }
}
