package com.kamco.cd.kamcoback.postgres.core;

import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.Basic;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.SearchReq;
import com.kamco.cd.kamcoback.common.service.BaseCoreService;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity;
import com.kamco.cd.kamcoback.postgres.repository.code.CommonCodeRepository;
import jakarta.persistence.EntityNotFoundException;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class CommonCodeCoreService
    implements BaseCoreService<CommonCodeDto.Basic, Long, SearchReq> {

  private final CommonCodeRepository commonCodeRepository;

  /**
   * 모두 조회
   *
   * @return
   */
  @Cacheable(value = "commonCodes")
  public List<CommonCodeDto.Basic> findAll() {
    return commonCodeRepository.findByAll().stream().map(CommonCodeEntity::toDto).toList();
  }

  /**
   * 등록
   *
   * @param req
   * @return
   */
  @CacheEvict(value = "commonCodes", allEntries = true)
  public ResponseObj save(CommonCodeDto.AddReq req) {

    String regex = "^[A-Z0-9]+(_[A-Z0-9]+)*$";
    boolean isValid = req.getCode().matches(regex);
    if (!isValid) {
      return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다.");
    }

    Long existsCount =
        commonCodeRepository.findByParentIdCodeExists(req.getParentId(), req.getCode());
    if (existsCount > 0) {
      return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다.");
    }

    CommonCodeEntity entity =
        new CommonCodeEntity(
            req.getCode(),
            req.getName(),
            req.getDescription(),
            req.getOrder(),
            req.isUsed(),
            req.getProps1(),
            req.getProps2(),
            req.getProps3());

    if (req.getParentId() != null) {
      CommonCodeEntity parentCommonCodeEntity =
          commonCodeRepository
              .findById(req.getParentId())
              .orElseThrow(
                  () ->
                      new EntityNotFoundException(
                          "parent id 를 찾을 수 없습니다. id : " + req.getParentId()));

      entity.addParent(parentCommonCodeEntity);
    } else {
      entity.addParent(null);
    }
    commonCodeRepository.save(entity).toDto();

    return new ResponseObj(ApiResponseCode.OK, "");
  }

  /**
   * 수정
   *
   * @param id
   * @param req
   * @return
   */
  @CacheEvict(value = "commonCodes", allEntries = true)
  public ResponseObj update(Long id, CommonCodeDto.ModifyReq req) {
    CommonCodeEntity found =
        commonCodeRepository
            .findByCodeId(id)
            .orElseThrow(() -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + id));

    found.update(
        req.getName(),
        req.getDescription(),
        req.isUsed(),
        req.getProps1(),
        req.getProps2(),
        req.getProps3());

    return new ResponseObj(ApiResponseCode.OK, "");
  }

  /**
   * 순서 변경
   *
   * @param req
   * @return
   */
  @CacheEvict(value = "commonCodes", allEntries = true)
  public ResponseObj updateOrder(CommonCodeDto.OrderReq req) {

    CommonCodeEntity found =
        commonCodeRepository
            .findByCodeId(req.getId())
            .orElseThrow(
                () -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + req.getId()));

    found.updateOrder(req.getOrder());

    return new ResponseObj(ApiResponseCode.OK, "");
  }

  public List<CommonCodeDto.Basic> findByCode(String code) {
    return commonCodeRepository.findByCode(code).stream().map(CommonCodeEntity::toDto).toList();
  }

  /**
   * 공통코드 이름 조회
   *
   * @param parentCodeCd
   * @param childCodeCd
   * @return
   */
  public Optional<String> getCode(String parentCodeCd, String childCodeCd) {
    return commonCodeRepository.getCode(parentCodeCd, childCodeCd);
  }

  /**
   * 공통코드 삭제
   *
   * @param id
   * @return
   */
  @CacheEvict(value = "commonCodes", allEntries = true)
  public ResponseObj removeCode(Long id) {
    CommonCodeEntity entity =
        commonCodeRepository
            .findByCodeId(id)
            .orElseThrow(() -> new EntityNotFoundException("code를 찾을 수 없습니다. id " + id));

    // 하위코드가 있으면 삭제 불가
    if (!entity.getChildren().isEmpty()) {
      return new ResponseObj(
          ApiResponseCode.UNPROCESSABLE_ENTITY,
          "하위에 다른 공통코드를 가지고 있습니다.<br/>하위공통 코드를 이동한 후 삭제할 수 있습니다.");
    }

    // id 코드 deleted = false 업데이트
    entity.deleted();
    return new ResponseObj(ApiResponseCode.OK, "");
  }

  @Override
  public void remove(Long aLong) {
    // 미사용
  }

  /**
   * id 로 단건 조회
   *
   * @param id
   * @return
   */
  @Override
  public Basic getOneById(Long id) {
    CommonCodeEntity entity =
        commonCodeRepository
            .findByCodeId(id)
            .orElseThrow(() -> new EntityNotFoundException("code를 찾을 수 없습니다. id " + id));
    return entity.toDto();
  }

  @Override
  public Page<Basic> search(SearchReq searchReq) {
    return null;
  }

  /**
   * 중복 체크
   *
   * @param parentId
   * @param code
   * @return
   */
  public ResponseObj getCodeCheckDuplicate(Long parentId, String code) {
    Long existsCount = commonCodeRepository.findByParentIdCodeExists(parentId, code);

    String regex = "^[A-Z0-9]+(_[A-Z0-9]+)*$";
    boolean isValid = code.matches(regex);
    if (!isValid) {
      return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다.");
    }

    if (existsCount > 0) {
      return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다.");
    }

    return new ResponseObj(ApiResponseCode.OK, "");
  }
}
