package com.its.op.service.its.link;

import com.its.op.dao.repository.its.link.TbLinkParaClctSystRepository;
import com.its.op.dao.repository.its.link.TbLinkParaDetlRepository;
import com.its.op.dao.repository.its.link.TbLinkParaStupRepository;
import com.its.op.dao.repository.its.link.TbLinkRepository;
import com.its.op.dto.its.common.NewIdLongDto;
import com.its.op.dto.its.common.UsageCountDto;
import com.its.op.dto.its.link.TbLinkParaClctSystDto;
import com.its.op.dto.its.link.TbLinkParaDetlDto;
import com.its.op.dto.its.link.TbLinkParaStupDto;
import com.its.op.entity.its.link.TbLinkParaClctSyst;
import com.its.op.entity.its.link.TbLinkParaDetl;
import com.its.op.entity.its.link.TbLinkParaStup;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Slf4j
@RequiredArgsConstructor
@Service
public class TbLinkParaStupService {

    private final TbLinkParaStupRepository repo;
    private final TbLinkParaDetlRepository detlRepo;
    private final TbLinkParaClctSystRepository clctRepo;
    private final TbLinkRepository linkRepo;

    // 데이터 1건 조회, 없으면 exception
    private TbLinkParaStup requireOne(Long paramId) throws NoSuchElementException {
        Optional<TbLinkParaStup> info = this.repo.findById(paramId);
        if (info.isPresent()) {
            return info.get();
        }
        else {
            throw new NoSuchElementException("데이터가 존재하지 않습니다: " + paramId);
        }
    }

    /**
     * 가공파라미터 목록 조회(설정, 설정상세만 조회, 수집시스템은 조회 안함)
     * @return
     */
    @Transactional(readOnly = true)
    public List<TbLinkParaStupDto> findAll() {
        Map<Long, TbLinkParaStupDto> result = new HashMap<>();
        List<TbLinkParaStup> data = this.repo.findAll();

        // 파라미터, 파라미터 상세
        for (TbLinkParaStup entity : data) {
            TbLinkParaStupDto dto = entity.toDto();
            dto.setDetl(entity.getDetl().toDto());
            result.put(dto.getParaId(), dto);
        }

        // 수집 시스템, 개별적으로 읽어 올때만 사용하자....
//        List<TbLinkParaClctSyst> clcts = this.clctRepo.findAll();
//        for (TbLinkParaClctSyst clct : clcts) {
//            TbLinkParaStupDto dto = result.get(clct.getParaId());
//            if (dto != null) {
//                dto.getClcts().add(clct.toDto());
//            }
//        }
        return new ArrayList<>(result.values());
    }

    /**
     * 가공파라미터 정보 조회(설정, 설정상세, 수집시스템)
     * @param paramId
     * @return
     */
    @Transactional(readOnly = true)
    public TbLinkParaStupDto findById(Long paramId) {
        TbLinkParaStup data = requireOne(paramId);
        TbLinkParaStupDto result = data.toDto();

        List<TbLinkParaClctSyst> clcts = this.clctRepo.findClctsById(paramId);
        for (TbLinkParaClctSyst clct : clcts) {
            result.getClcts().add(clct.toDto());
        }
        return result;
    }

    /**
     * 신규 가공 파라미터 번호 조회
     * @return
     */
    @Transactional(readOnly = true)
    public NewIdLongDto getNewNmbr() {
        Long newId = this.repo.getNewNmbr();
        return NewIdLongDto.builder().newId(newId).build();
    }

    /**
     * 파리미터가 사용 중 인지를 체크
     * @param paramId
     * @return
     */
    @Transactional
    public UsageCountDto findUsageId(Long paramId) {
        UsageCountDto result = UsageCountDto.builder()
                .count(0)
                .build();
        Integer data = this.repo.findUsageId(paramId);
        result.setCount(data);
        return result;
    }

    /**
     * 파라미터 정보 삭제
     * @param paramId
     * @return
     */
    @Transactional
    public TbLinkParaStupDto deleteDataById(Long paramId) {
        if (paramId == 1) {
            throw new NoSuchElementException("첫번째 파라미터 정보는 삭제할 수 없습니다.: " + paramId);
        }
        UsageCountDto usageCount = findUsageId(paramId);
        if (usageCount.getCount() > 0) {
            throw new ConcurrentModificationException("사용중인 파라미터 정보 입니다. 삭제할 수 없습니다: " + paramId);
        }

        TbLinkParaStupDto result = findById(paramId);

        this.clctRepo.deleteDataById(paramId);
        this.detlRepo.deleteDataById(paramId);
        this.repo.deleteDataById(paramId);

        return result;
    }

//    @Transactional
//    public TbLinkParaStup mergeById(Long id, LinkParaStupDto.LinkParaStupUpdateReq req) {
//        Optional<TbLinkParaStup> data = this.repo.findById(id);
//        TbLinkParaStup obj = data.orElseGet(() -> new TbLinkParaStup(id));
//        //obj.updateInfo(req);
//        this.repo.save(obj);
//        return obj;
//    }

    /**
     * 파라미터 정보 생성 또는 업데이트
     * @param paramId
     * @param para
     * @param detl
     * @param clcts
     * @return
     */
    @Transactional
    public TbLinkParaStupDto mergeInfo(Long paramId,
                                       TbLinkParaStupDto.TbLinkParaStupUpdReq para,
                                       TbLinkParaDetlDto.TbLinkParaDetlUpdReq detl,
                                       List<TbLinkParaClctSystDto.TbLinkParaClctSystUpdReq> clcts) {
        TbLinkParaStup paraObj = para.toEntity();
        TbLinkParaDetl detlObj = detl.toEntity();

        // 수집시스템 파라미터 삭제
        this.clctRepo.deleteDataById(paramId);

        // 파라미터 정보 저장
        this.repo.save(paraObj);
        // 파라미터 상세 정보 저장
        this.detlRepo.save(detlObj);
        // 파라미터 수집 시스템 저장

        for (TbLinkParaClctSystDto.TbLinkParaClctSystUpdReq clct : clcts) {
            TbLinkParaClctSyst clctObj = clct.toEntity();
            this.clctRepo.save(clctObj);
        }
        return paraObj.toDto();
    }

    /**
     * 선택한 링크에 대하여 파라미터 값 변경
     * @param paraId
     * @param ids
     * @return
     */
    @Transactional
    public TbLinkParaStupDto applyParamSelect(Long paraId, List<Long> ids) {
        TbLinkParaStupDto result = findById(paraId);
        this.linkRepo.updateParamIds(paraId, ids);
        return result;
    }

    /**
     * 모든 링크에 대하여 파라미터 값 변경
     * @param paraId
     * @return
     */
    @Transactional
    public TbLinkParaStupDto applyParamAll(Long paraId) {
        TbLinkParaStupDto result = findById(paraId);
        this.linkRepo.updateParamAll(paraId);
        return result;
    }
}
