package com.interplug.qcast.biz.estimate;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.interplug.qcast.biz.estimate.dto.EstimateApiResponse;
import com.interplug.qcast.biz.estimate.dto.EstimateCopyRequest;
import com.interplug.qcast.biz.estimate.dto.EstimateRequest;
import com.interplug.qcast.biz.estimate.dto.EstimateResponse;
import com.interplug.qcast.biz.estimate.dto.EstimateSendRequest;
import com.interplug.qcast.biz.estimate.dto.EstimateSendResponse;
import com.interplug.qcast.biz.estimate.dto.ItemRequest;
import com.interplug.qcast.biz.estimate.dto.ItemResponse;
import com.interplug.qcast.biz.estimate.dto.NoteRequest;
import com.interplug.qcast.biz.estimate.dto.NoteResponse;
import com.interplug.qcast.biz.estimate.dto.PlanSyncResponse;
import com.interplug.qcast.biz.estimate.dto.PriceRequest;
import com.interplug.qcast.biz.estimate.dto.RoofInfoResponse;
import com.interplug.qcast.biz.estimate.dto.RoofRequest;
import com.interplug.qcast.biz.estimate.dto.RoofResponse;
import com.interplug.qcast.biz.file.FileMapper;
import com.interplug.qcast.biz.file.dto.FileRequest;
import com.interplug.qcast.biz.file.dto.FileResponse;
import com.interplug.qcast.biz.object.ObjectMapper;
import com.interplug.qcast.biz.object.dto.ObjectRequest;
import com.interplug.qcast.biz.object.dto.ObjectResponse;
import com.interplug.qcast.biz.pwrGnrSimulation.PwrGnrSimService;
import com.interplug.qcast.biz.pwrGnrSimulation.dto.PwrGnrSimGuideResponse;
import com.interplug.qcast.biz.pwrGnrSimulation.dto.PwrGnrSimRequest;
import com.interplug.qcast.biz.pwrGnrSimulation.dto.PwrGnrSimResponse;
import com.interplug.qcast.config.Exception.ErrorCode;
import com.interplug.qcast.config.Exception.QcastException;
import com.interplug.qcast.config.message.Messages;
import com.interplug.qcast.util.ExcelUtil;
import com.interplug.qcast.util.InterfaceQsp;
import com.interplug.qcast.util.PdfUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;

@Slf4j
@Service
@RequiredArgsConstructor
public class EstimateService {
  private final InterfaceQsp interfaceQsp;

  @Autowired Messages message;

  @Value("${file.ini.root.path}")
  private String baseDirPath;

  @Value("${qsp.url}")
  private String QSP_API_URL;

  @Value("${file.excel.template.path}")
  private String excelTemplateFilePath;

  private final ObjectMapper objectMapper;

  private final EstimateMapper estimateMapper;

  private final FileMapper fileMapper;

  @Autowired private PwrGnrSimService pwrGnrSimService;

  /**
   * QSP 1차점 price 관리 목록 조회
   *
   * @param priceRequest 1차점 price 관련 정보
   * @return PriceResponse 1차점 price 목록
   * @throws Exception
   */
  public EstimateApiResponse selectStorePriceList(PriceRequest priceRequest) throws Exception {
    // Validation
    if (StringUtils.isEmpty(priceRequest.getSaleStoreId())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Sale Store ID"));
    }
    if (StringUtils.isEmpty(priceRequest.getSapSalesStoreCd())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Sap Sale Store Code"));
    }
    if (StringUtils.isEmpty(priceRequest.getDocTpCd())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Estimate Type"));
    }

    EstimateApiResponse response = null;
    /* [1]. QSP API (url + param) Setting */
    String url = QSP_API_URL + "/api/price/storePriceList";
    String apiUrl =
        UriComponentsBuilder.fromHttpUrl(url)
            .queryParam("saleStoreId", priceRequest.getSaleStoreId())
            .queryParam("sapSalesStoreCd", priceRequest.getSapSalesStoreCd())
            .queryParam("docTpCd", priceRequest.getDocTpCd())
            .build()
            .toUriString();

    /* [2]. QSP API CALL -> Response */
    String strResponse = interfaceQsp.callApi(HttpMethod.GET, apiUrl, null);

    if (!"".equals(strResponse)) {
      com.fasterxml.jackson.databind.ObjectMapper om =
          new com.fasterxml.jackson.databind.ObjectMapper()
              .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
      response = om.readValue(strResponse, EstimateApiResponse.class);
    } else {
      // [msg] No data
      throw new QcastException(ErrorCode.NOT_FOUND, message.getMessage("common.message.no.data"));
    }

    return response;
  }

  /**
   * QSP 아이템 가격 목록 조회
   *
   * @param priceRequest QSP 관련 아이템 목록 정보
   * @return PriceResponse QSP 아이템 가격 목록
   * @throws Exception
   */
  public EstimateApiResponse selectItemPriceList(PriceRequest priceRequest) throws Exception {
    // Validation
    if (StringUtils.isEmpty(priceRequest.getSaleStoreId())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Sale Store ID"));
    }
    if (StringUtils.isEmpty(priceRequest.getSapSalesStoreCd())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Sap Sale Store Code"));
    }
    if (StringUtils.isEmpty(priceRequest.getPriceCd())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Price Code"));
    }

    EstimateApiResponse response = null;
    /* [1]. QSP API CALL -> Response */
    String strResponse =
        interfaceQsp.callApi(
            HttpMethod.POST, QSP_API_URL + "/api//price/storePriceItemList", priceRequest);

    if (!"".equals(strResponse)) {
      com.fasterxml.jackson.databind.ObjectMapper om =
          new com.fasterxml.jackson.databind.ObjectMapper()
              .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
      response = om.readValue(strResponse, EstimateApiResponse.class);
    } else {
      // [msg] No data
      throw new QcastException(ErrorCode.NOT_FOUND, message.getMessage("common.message.no.data"));
    }

    return response;
  }

  /**
   * 견적 특이사항 타이틀 목록 조회
   *
   * @param noteRequest 견적 특이사항 조회 정보
   * @return List<NoteResponse> 견적 특이사항 목록
   * @throws Exception
   */
  public List<NoteResponse> selectSpecialNoteTitleList(NoteRequest noteRequest) throws Exception {
    return estimateMapper.selectEstimateNoteTitleList(noteRequest);
  }

  /**
   * 견적 특이사항 목록 조회
   *
   * @param noteRequest 견적 특이사항 조회 정보
   * @return List<NoteResponse> 견적 특이사항 목록
   * @throws Exception
   */
  public List<NoteResponse> selectSpecialNoteList(NoteRequest noteRequest) throws Exception {
    return estimateMapper.selectEstimateNoteList(noteRequest);
  }

  /**
   * 견적서 상세 조회
   *
   * @param objectNo 물건번호
   * @param planNo 플랜번호
   * @return EstimateResponse 견적서 상세 정보
   * @throws Exception
   */
  public EstimateResponse selectEstimateDetail(String objectNo, String planNo) throws Exception {
    // Validation
    if (StringUtils.isEmpty(objectNo)) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Object No"));
    }
    if (StringUtils.isEmpty(planNo)) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Plan No"));
    }

    EstimateRequest estimateRequest = new EstimateRequest();
    estimateRequest.setObjectNo(objectNo);
    estimateRequest.setPlanNo(planNo);

    // 견적서 상세 조회
    EstimateResponse response = estimateMapper.selectEstimateDetail(estimateRequest);

    if (response == null) {
      throw new QcastException(ErrorCode.NOT_FOUND, message.getMessage("common.message.no.data"));
    } else {

      // 아이템 목록 조회
      List<ItemResponse> estimateItemList = estimateMapper.selectEstimateItemList(estimateRequest);
      response.setItemList(estimateItemList);

      // 총 합산금액 계산
      this.selectTotalPriceInfo(response, estimateItemList, "");

      // 첨부파일 목록 조회
      FileRequest fileDeleteReq = new FileRequest();
      fileDeleteReq.setObjectNo(objectNo);
      fileDeleteReq.setCategory("10");
      fileDeleteReq.setPlanNo(planNo);

      List<FileResponse> fileList = fileMapper.selectFileList(fileDeleteReq);
      response.setFileList(fileList);
    }

    return response;
  }

  /**
   * 견적서 저장
   *
   * @param estimateRequest 견적서 저장 정보
   * @throws Exception
   */
  public void insertEstimate(EstimateRequest estimateRequest) throws Exception {
    // Validation
    if (StringUtils.isEmpty(estimateRequest.getObjectNo())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Object No"));
    }
    if (StringUtils.isEmpty(estimateRequest.getPlanNo())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Plan No"));
    }
    if (StringUtils.isEmpty(estimateRequest.getSaleStoreId())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Sale Store ID"));
    }
    if (StringUtils.isEmpty(estimateRequest.getSapSalesStoreCd())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Sap Sale Store Code"));
    }

    String splitStr = "、";
    List<RoofRequest> roofList = new ArrayList<RoofRequest>();
    List<ItemRequest> itemList = estimateRequest.getItemList();

    try {
      // 도면 작성일 경우에만 지붕재 데이터를 셋팅
      if ("1".equals(estimateRequest.getDrawingFlg())) {
        // [1]. 견적서 기본셋팅
        estimateRequest.setEstimateType("YJOD");
        estimateRequest.setPriceCd("UNIT_PRICE");

        // 물건정보 조회 후 데이터 축출
        ObjectResponse objectResponse =
            objectMapper.selectObjectDetail(estimateRequest.getObjectNo());
        if (objectResponse != null) {
          estimateRequest.setWeatherPoint(
              objectResponse.getPrefName() + " - " + objectResponse.getAreaName());
          estimateRequest.setCharger(objectResponse.getReceiveUser());
        }

        // [2]. 지붕재 관련 데이터 셋팅
        roofList = estimateRequest.getRoofList();

        // 지붕재 시공사양 ID
        String constructSpecifications = "";
        // 지붕재 아이템 ID
        String roofMaterialIds = "";
        // 지붕재 공법 ID
        String supportMethodIds = "";
        // 지붕재 아이템명
        String roofMaterialIdMultis = "";
        // 지붕재 공법명
        String supportMethodIdMultis = "";
        // 지붕재 시공사양명
        String constructSpecificationMultis = "";
        // 가대 메이커명
        String supportMeakers = "";
        for (RoofRequest roofRequest : roofList) {
          if (!StringUtils.isEmpty(roofRequest.getRoofMaterialId())) {
            roofMaterialIds += !StringUtils.isEmpty(roofMaterialIds) ? splitStr : "";
            roofMaterialIds += roofRequest.getRoofMaterialId();
          }

          if (!StringUtils.isEmpty(roofRequest.getSupportMethodId())) {
            supportMethodIds += !StringUtils.isEmpty(supportMethodIds) ? splitStr : "";
            supportMethodIds += roofRequest.getSupportMethodId();
          }

          if (!StringUtils.isEmpty(roofRequest.getConstructSpecification())) {
            constructSpecifications +=
                !StringUtils.isEmpty(constructSpecifications) ? splitStr : "";
            constructSpecifications += roofRequest.getConstructSpecification();
          }

          if (!StringUtils.isEmpty(roofRequest.getRoofMaterialIdMulti())) {
            roofMaterialIdMultis += !StringUtils.isEmpty(roofMaterialIdMultis) ? splitStr : "";
            roofMaterialIdMultis += roofRequest.getRoofMaterialIdMulti();
          }

          if (!StringUtils.isEmpty(roofRequest.getSupportMethodIdMulti())) {
            supportMethodIdMultis += !StringUtils.isEmpty(supportMethodIdMultis) ? splitStr : "";
            supportMethodIdMultis += roofRequest.getSupportMethodIdMulti();
          }

          if (!StringUtils.isEmpty(roofRequest.getConstructSpecificationMulti())) {
            constructSpecificationMultis +=
                !StringUtils.isEmpty(constructSpecificationMultis) ? splitStr : "";
            constructSpecificationMultis += roofRequest.getConstructSpecificationMulti();
          }

          if (!StringUtils.isEmpty(roofRequest.getSupportMeaker())) {
            supportMeakers += !StringUtils.isEmpty(supportMeakers) ? splitStr : "";
            supportMeakers += roofRequest.getSupportMeaker();
          }
        }

        // 지붕재 관련 데이터 셋팅
        estimateRequest.setRoofMaterialId(roofMaterialIds);
        estimateRequest.setSupportMethodId(supportMethodIds);
        estimateRequest.setConstructSpecification(constructSpecifications);
        estimateRequest.setRoofMaterialIdMulti(roofMaterialIdMultis);
        estimateRequest.setSupportMethodIdMulti(supportMethodIdMultis);
        estimateRequest.setConstructSpecificationMulti(constructSpecificationMultis);
        estimateRequest.setSupportMeaker(supportMeakers);

        // [3]. 아이템 관련 데이터 셋팅
        String[] arrItemId = new String[itemList.size()];
        int i = 0;
        for (ItemRequest itemRequest : itemList) {
          arrItemId[i++] = itemRequest.getItemId();
        }
        estimateRequest.setArrItemId(arrItemId);
        // 아이템의 마스터 정보 및 정가 정보 조회
        List<ItemResponse> itemResponseList = estimateMapper.selectItemMasterList(estimateRequest);
        // BOM 정보 목록
        List<ItemRequest> estimateBomList = new ArrayList<ItemRequest>();

        int j = 1;
        for (ItemRequest itemRequest : itemList) {
          int dispOrder = 100 * j++;
          itemRequest.setDispOrder(String.valueOf(dispOrder));

          for (ItemResponse itemResponse : itemResponseList) {
            if (itemRequest.getItemId().equals(itemResponse.getItemId())) {
              itemRequest.setItemNo(itemResponse.getItemNo());
              itemRequest.setItemName(itemResponse.getItemName());
              itemRequest.setUnit(itemResponse.getUnit());
              itemRequest.setPnowW(itemResponse.getPnowW());
              itemRequest.setSpecification(itemResponse.getPnowW());
              itemRequest.setUnitPrice(itemResponse.getSalePrice());
              itemRequest.setSalePrice(itemResponse.getSalePrice());
              itemRequest.setFileUploadFlg(itemResponse.getFileUploadFlg());
              itemRequest.setPkgMaterialFlg(itemResponse.getPkgMaterialFlg());
              itemRequest.setItemGroup(itemResponse.getItemGroup());
              itemRequest.setItemCtgGr(itemResponse.getItemCtgGr());
              itemRequest.setPartAdd("0");
              itemRequest.setDelFlg("0");
              break;
            }
          }

          // 아이템 BOM Header인 경우 컴포넌트 등록 처리
          if ("ERLA".equals(itemRequest.getItemCtgGr())) {
            List<ItemResponse> itemBomList =
                estimateMapper.selectItemMasterBomList(itemRequest.getItemId());

            int k = 1;
            for (ItemResponse itemResponse : itemBomList) {
              ItemRequest bomItem = new ItemRequest();

              bomItem.setPaDispOrder(String.valueOf(dispOrder));
              bomItem.setDispOrder(String.valueOf(dispOrder + k++));
              bomItem.setItemId(itemResponse.getItemId());
              bomItem.setItemNo(itemResponse.getItemNo());
              bomItem.setItemName(itemResponse.getItemName());
              bomItem.setUnit(itemResponse.getUnit());
              bomItem.setPnowW(itemResponse.getPnowW());
              bomItem.setSpecification(itemResponse.getPnowW());
              bomItem.setAmount(
                  String.valueOf(
                      Integer.parseInt(itemResponse.getBomAmount())
                          * Integer.parseInt(itemRequest.getAmount())));
              bomItem.setBomAmount(itemResponse.getBomAmount());
              bomItem.setUnitPrice(itemResponse.getSalePrice());
              bomItem.setSalePrice(itemResponse.getSalePrice());
              bomItem.setFileUploadFlg(itemResponse.getFileUploadFlg());
              bomItem.setPkgMaterialFlg(itemResponse.getPkgMaterialFlg());
              bomItem.setItemGroup(itemResponse.getItemGroup());
              bomItem.setItemCtgGr(itemResponse.getItemCtgGr());
              bomItem.setPartAdd("0");
              bomItem.setDelFlg("0");

              estimateBomList.add(bomItem);
            }
          }
        }

        // BOM 컴포넌트 추가
        for (ItemRequest estimateBom : estimateBomList) {
          itemList.add(estimateBom);
        }

        // [4]. 견적특이사항 관련 데이터 셋팅
        NoteRequest noteRequest = new NoteRequest();
        noteRequest.setArrItemId(arrItemId);
        noteRequest.setSchSpnTypeCd("COMM");
        List<NoteResponse> noteList = estimateMapper.selectEstimateNoteList(noteRequest);
        noteRequest.setSchSpnTypeCd("PROD");
        List<NoteResponse> noteItemList = estimateMapper.selectEstimateNoteItemList(noteRequest);

        // 견적특이사항 코드
        String estimateOptions = "";
        for (NoteResponse noteResponse : noteList) {
          if ("ATTR001".equals(noteResponse.getCode())) {
            estimateOptions += !StringUtils.isEmpty(estimateOptions) ? splitStr : "";
            estimateOptions += "ATTR001"; // 공통 필수체크
          } else if ("ATTR002".equals(noteResponse.getCode())) {
            estimateOptions += !StringUtils.isEmpty(estimateOptions) ? splitStr : "";
            estimateOptions += "ATTR002"; // YJOD 필수체크
          } else if ("ATTR003".equals(noteResponse.getCode())) {
            estimateOptions += !StringUtils.isEmpty(estimateOptions) ? splitStr : "";
            estimateOptions += "ATTR003"; // 출력제어시간 기본 체크
          } else if ("ATTR004".equals(noteResponse.getCode())
              && "1".equals(estimateRequest.getNorthArrangement())) {
            estimateOptions += !StringUtils.isEmpty(estimateOptions) ? splitStr : "";
            estimateOptions += "ATTR004"; // 북면설치 체크
          } else if ("ATTR005".equals(noteResponse.getCode())
              && "1".equals(objectResponse.getSaltAreaFlg())) {
            estimateOptions += !StringUtils.isEmpty(estimateOptions) ? splitStr : "";
            estimateOptions += "ATTR005"; // 염해지역 체크
          } else if ("ATTR006".equals(objectResponse.getColdRegionFlg())
              && "1".equals(estimateRequest.getNorthArrangement())) {
            estimateOptions += !StringUtils.isEmpty(estimateOptions) ? splitStr : "";
            estimateOptions += "ATTR006"; // 적설지역 체크
          } else if ("ATTR007".equals(noteResponse.getCode())) {
            estimateOptions += !StringUtils.isEmpty(estimateOptions) ? splitStr : "";
            estimateOptions += "ATTR007"; // 지붕재치수, 레이아웃 기본 체크
          } else if ("ATTR008".equals(noteResponse.getCode())) {
            estimateOptions += !StringUtils.isEmpty(estimateOptions) ? splitStr : "";
            estimateOptions += "ATTR008"; // 별도비용 기본 체크
          } else if ("ATTR009".equals(noteResponse.getCode())) {
            estimateOptions += !StringUtils.isEmpty(estimateOptions) ? splitStr : "";
            estimateOptions += "ATTR009"; // 과적재 기본 체크
          }
        }
        for (NoteResponse noteResponse : noteItemList) {
          if (estimateOptions.indexOf(noteResponse.getCode()) < 0) {
            estimateOptions += !StringUtils.isEmpty(estimateOptions) ? splitStr : "";
            estimateOptions += noteResponse.getCode();
          }
        }
        estimateRequest.setEstimateOption(estimateOptions);

        // 아이템별 특이사항 코드 체크
        for (ItemRequest itemRequest : itemList) {
          String specialNoteCd = "";
          for (NoteResponse noteResponse : noteItemList) {
            if (itemRequest.getItemId().equals(noteResponse.getItemId())) {
              specialNoteCd += !StringUtils.isEmpty(specialNoteCd) ? splitStr : "";
              specialNoteCd += noteResponse.getCode();
            }
          }
          itemRequest.setSpecialNoteCd(specialNoteCd);
        }
      }

      // 아이템 목록 필수 값 체크
      BigDecimal capacity = BigDecimal.ZERO;
      String moduleModel = "";
      String pcTypeNo = "";
      for (ItemRequest itemRequest : itemList) {
        if (!"1".equals(itemRequest.getDelFlg())) {
          if (StringUtils.isEmpty(itemRequest.getDispOrder())) {
            throw new QcastException(
                ErrorCode.INVALID_INPUT_VALUE,
                message.getMessage("common.message.required.data", "Display Order"));
          }
          if (StringUtils.isEmpty(itemRequest.getItemId())) {
            throw new QcastException(
                ErrorCode.INVALID_INPUT_VALUE,
                message.getMessage("common.message.required.data", "Item ID"));
          }
          if (StringUtils.isEmpty(itemRequest.getAmount())) {
            throw new QcastException(
                ErrorCode.INVALID_INPUT_VALUE,
                message.getMessage("common.message.required.data", "Item Amount"));
          }

          // 수량
          BigDecimal amount =
              new BigDecimal(
                  StringUtils.isEmpty(itemRequest.getAmount()) ? "0" : itemRequest.getAmount());
          // 아이템용량
          BigDecimal pnowW =
              new BigDecimal(
                  StringUtils.isEmpty(itemRequest.getPnowW()) ? "0" : itemRequest.getPnowW());

          // 모듈/PC 체크
          if ("MODULE_".equals(itemRequest.getItemGroup())) {
            moduleModel += !StringUtils.isEmpty(moduleModel) ? splitStr : "";
            moduleModel += itemRequest.getItemNo();

            capacity = capacity.add(amount.multiply(pnowW));
          } else if ("PC_".equals(itemRequest.getItemGroup())) {
            pcTypeNo += !StringUtils.isEmpty(pcTypeNo) ? splitStr : "";
            pcTypeNo += itemRequest.getItemNo();
          }
        }
      }
      estimateRequest.setCapacity(String.valueOf(capacity));
      estimateRequest.setModuleModel(moduleModel);
      estimateRequest.setPcTypeNo(pcTypeNo);

      // 물건정보 수정
      if (!StringUtils.isEmpty(estimateRequest.getObjectName())
          || !StringUtils.isEmpty(estimateRequest.getObjectNameOmit())
          || !StringUtils.isEmpty(estimateRequest.getSurfaceType())
          || !StringUtils.isEmpty(estimateRequest.getSetupHeight())
          || !StringUtils.isEmpty(estimateRequest.getStandardWindSpeedId())
          || !StringUtils.isEmpty(estimateRequest.getSnowfall())) {
        estimateMapper.updateObject(estimateRequest);
      }

      // 견적서 정보 수정
      estimateRequest.setPriceCd(
          !StringUtils.isEmpty(estimateRequest.getPriceCd())
              ? estimateRequest.getPriceCd()
              : "UNIT_PRICE");
      estimateMapper.updateEstimate(estimateRequest);

      // 도면 작성일 경우에만 지붕재 데이터 초기화 후 저장
      if ("1".equals(estimateRequest.getDrawingFlg())) {
        // 견적서 지붕면/아이템 제거
        estimateMapper.deleteEstimateRoofList(estimateRequest);
        estimateMapper.deleteEstimateRoofItemList(estimateRequest);

        // 견적서 지붕면/아이템 신규 추가
        for (RoofRequest roofRequest : roofList) {
          roofRequest.setObjectNo(estimateRequest.getObjectNo());
          roofRequest.setPlanNo(estimateRequest.getPlanNo());
          roofRequest.setUserId(estimateRequest.getUserId());

          estimateMapper.insertEstimateRoof(roofRequest);

          List<ItemRequest> roofItemList = roofRequest.getRoofItemList();
          for (ItemRequest itemRequest : roofItemList) {
            itemRequest.setObjectNo(estimateRequest.getObjectNo());
            itemRequest.setPlanNo(estimateRequest.getPlanNo());
            itemRequest.setRoofNo(roofRequest.getRoofNo());

            estimateMapper.insertEstimateRoofItem(itemRequest);
          }
        }
      }

      // 견적서 모든 아이템 제거
      estimateMapper.deleteEstimateItemList(estimateRequest);

      // 견적서 아이템 신규 추가
      String hisNo = estimateMapper.selectEstimateItemHisNo(estimateRequest); // 아이템 히스토리 번호 조회
      for (ItemRequest itemRequest : itemList) {
        itemRequest.setHisNo(hisNo);
        itemRequest.setObjectNo(estimateRequest.getObjectNo());
        itemRequest.setPlanNo(estimateRequest.getPlanNo());
        itemRequest.setAmount(
            !StringUtils.isEmpty(itemRequest.getAmount()) ? itemRequest.getAmount() : "0");
        itemRequest.setSalePrice(
            !StringUtils.isEmpty(itemRequest.getSalePrice()) ? itemRequest.getSalePrice() : "0");
        itemRequest.setBomAmount(
            !StringUtils.isEmpty(itemRequest.getBomAmount()) ? itemRequest.getBomAmount() : "0");
        itemRequest.setPartAdd(
            !StringUtils.isEmpty(itemRequest.getPartAdd()) ? itemRequest.getPartAdd() : "0");
        itemRequest.setItemChangeFlg(
            !StringUtils.isEmpty(itemRequest.getItemChangeFlg())
                ? itemRequest.getItemChangeFlg()
                : "0");
        itemRequest.setUserId(estimateRequest.getUserId());

        estimateMapper.insertEstimateItemHis(itemRequest);

        if (!"1".equals(itemRequest.getDelFlg())) {
          estimateMapper.insertEstimateItem(itemRequest);
        }
      }

      // QSP Q.CAST SEND API
      List<EstimateSendResponse> resultList = new ArrayList<EstimateSendResponse>();
      resultList = this.sendEstimateApi(estimateRequest);
      // API에서 받은 문서번호 업데이트
      for (EstimateSendResponse result : resultList) {
        estimateRequest.setObjectNo(result.getObjectNo());
        estimateRequest.setPlanNo(result.getPlanNo());
        estimateRequest.setDocNo(result.getDocNo());
        estimateRequest.setSyncFlg(result.getSyncFlg());

        estimateMapper.updateEstimateApi(estimateRequest);
      }
    } catch (Exception e) {
      e.printStackTrace();
      throw new QcastException(ErrorCode.INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * 견적서 복사
   *
   * @param estimateCopyRequest 견적서 복사 정보
   * @return EstimateResponse 견적서 복사 후 상세 정보
   * @throws Exception
   */
  public EstimateResponse insertEstimateCopy(EstimateCopyRequest estimateCopyRequest)
      throws Exception {
    // Validation
    if (StringUtils.isEmpty(estimateCopyRequest.getObjectNo())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Object No"));
    }
    if (StringUtils.isEmpty(estimateCopyRequest.getPlanNo())) {
      throw new QcastException(
          ErrorCode.INVALID_INPUT_VALUE,
          message.getMessage("common.message.required.data", "Plan No"));
    }
    // 응답 객체
    EstimateResponse response = new EstimateResponse();

    try {

      ObjectRequest objectRequest = new ObjectRequest();
      objectRequest.setSaleStoreId(estimateCopyRequest.getCopySaleStoreId());
      objectRequest.setReceiveUser(
          StringUtils.defaultString(estimateCopyRequest.getCopyReceiveUser()));
      objectRequest.setDelFlg("0");
      objectRequest.setTempFlg("0");
      objectRequest.setTempDelFlg("0");
      objectRequest.setUserId(estimateCopyRequest.getUserId());

      // [1]. 신규 물건번호 생성
      objectMapper.insertObjectNo(objectRequest);
      objectRequest.setObjectNo(estimateCopyRequest.getObjectNo());
      objectRequest.setCopyObjectNo(objectMapper.selectObjectNo(objectRequest));

      // [2]. 물건정보 복사
      objectRequest.setContentsPath(baseDirPath + "\\\\" + objectRequest.getCopyObjectNo());
      objectMapper.insertObjectCopy(objectRequest);
      objectRequest.setObjectNo(objectRequest.getCopyObjectNo());
      objectMapper.updateObjectDelivery(objectRequest);

      // [3]. 아이템 관련 데이터 셋팅 (복사 시 정가 셋팅)
      EstimateRequest estimateRequest = new EstimateRequest();
      estimateRequest.setObjectNo(estimateCopyRequest.getObjectNo());
      estimateRequest.setPlanNo(estimateCopyRequest.getPlanNo());

      List<ItemRequest> itemList = new ArrayList<ItemRequest>();
      List<ItemResponse> estimateItemList = estimateMapper.selectEstimateItemList(estimateRequest);
      for (ItemResponse itemResponse : estimateItemList) {
        ItemRequest itemRequest = new ItemRequest();
        itemRequest.setDispOrder(itemResponse.getDispOrder());
        itemRequest.setPaDispOrder(itemResponse.getPaDispOrder());
        itemRequest.setItemId(itemResponse.getItemId());
        itemRequest.setAmount(itemResponse.getAmount());
        itemRequest.setBomAmount(itemResponse.getBomAmount());
        itemRequest.setSpecialNoteCd(itemResponse.getSpecialNoteCd());
        itemRequest.setItemChangeFlg("0");

        itemList.add(itemRequest);
      }

      String[] arrItemId = new String[itemList.size()];
      int i = 0;
      for (ItemRequest itemRequest : itemList) {
        arrItemId[i++] = itemRequest.getItemId();
      }
      estimateRequest.setArrItemId(arrItemId);
      // 아이템의 마스터 정보 및 정가 정보 조회
      List<ItemResponse> itemResponseList = estimateMapper.selectItemMasterList(estimateRequest);

      for (ItemRequest itemRequest : itemList) {
        for (ItemResponse itemResponse : itemResponseList) {
          if (itemRequest.getItemId().equals(itemResponse.getItemId())) {
            itemRequest.setItemNo(itemResponse.getItemNo());
            itemRequest.setItemName(itemResponse.getItemName());
            itemRequest.setUnit(itemResponse.getUnit());
            itemRequest.setPnowW(itemResponse.getPnowW());
            itemRequest.setSpecification(itemResponse.getPnowW());
            itemRequest.setUnitPrice(itemResponse.getSalePrice());
            itemRequest.setSalePrice(itemResponse.getSalePrice());
            itemRequest.setPkgMaterialFlg(itemResponse.getPkgMaterialFlg());
            itemRequest.setFileUploadFlg(itemResponse.getFileUploadFlg());
            itemRequest.setItemGroup(itemResponse.getItemGroup());

            break;
          }
        }
      }

      // [4]. 견적서 복사
      estimateCopyRequest.setCopyObjectNo(objectRequest.getObjectNo());
      estimateCopyRequest.setCopyPlanNo("1");
      estimateMapper.insertEstimateCopy(estimateCopyRequest);

      // [5]. 견적서 아이템 복사
      for (ItemRequest itemRequest : itemList) {
        itemRequest.setObjectNo(estimateCopyRequest.getCopyObjectNo());
        itemRequest.setPlanNo(estimateCopyRequest.getCopyPlanNo());
        itemRequest.setPartAdd(
            !StringUtils.isEmpty(itemRequest.getPartAdd()) ? itemRequest.getPartAdd() : "0");
        itemRequest.setItemChangeFlg(
            !StringUtils.isEmpty(itemRequest.getItemChangeFlg())
                ? itemRequest.getItemChangeFlg()
                : "0");
        itemRequest.setUserId(estimateCopyRequest.getUserId());

        estimateMapper.insertEstimateItem(itemRequest);
      }

      // [6]. 견적서 지붕재 복사
      List<RoofResponse> roofList = estimateMapper.selectEstimateRoofList(estimateRequest);
      List<RoofResponse> roofItemList = estimateMapper.selectEstimateRoofItemList(estimateRequest);

      for (RoofResponse roofResponse : roofList) {
        List<RoofResponse> roofItemList2 = new ArrayList<RoofResponse>();

        // 현재 매칭되는 지붕재 아이템 축출
        for (RoofResponse roofItemResponse : roofItemList) {
          if (roofResponse.getRoofNo().equals(roofItemResponse.getRoofNo())) {
            roofItemList2.add(roofItemResponse);
          }
        }

        RoofRequest roofRequest = new RoofRequest();
        roofRequest.setObjectNo(estimateCopyRequest.getCopyObjectNo());
        roofRequest.setPlanNo(estimateCopyRequest.getCopyPlanNo());
        roofRequest.setRoofSurface(roofResponse.getRoofSurface());
        roofRequest.setRoofMaterialId(roofResponse.getRoofMaterialId());
        roofRequest.setSupportMethodId(roofResponse.getSupportMethodId());
        roofRequest.setConstructSpecification(roofResponse.getConstructSpecification());
        roofRequest.setSlope(roofResponse.getSlope());
        roofRequest.setAngle(roofResponse.getAngle());
        roofRequest.setClassType(roofResponse.getClassType());
        roofRequest.setAzimuth(roofResponse.getAzimuth());
        roofRequest.setUserId(estimateCopyRequest.getUserId());

        estimateMapper.insertEstimateRoof(roofRequest);

        for (RoofResponse roofItemResponse : roofItemList2) {
          ItemRequest itemRequest = new ItemRequest();
          itemRequest.setRoofNo(roofRequest.getRoofNo());
          itemRequest.setObjectNo(estimateCopyRequest.getCopyObjectNo());
          itemRequest.setPlanNo(estimateCopyRequest.getCopyPlanNo());
          itemRequest.setItemId(roofItemResponse.getItemId());
          itemRequest.setItemNo(roofItemResponse.getItemNo());
          itemRequest.setItemName(roofItemResponse.getItemName());
          itemRequest.setSpecification(roofItemResponse.getSpecification());
          itemRequest.setAmount(roofItemResponse.getAmount());
          itemRequest.setPcItemId(roofItemResponse.getPcItemId());
          itemRequest.setCircuit(roofItemResponse.getCircuit());

          estimateMapper.insertEstimateRoofItem(itemRequest);
        }
      }

      // [7]. 견적서 도면 복사 (추후 개발 필요)

      // [8]. QSP Q.CAST SEND API
      List<EstimateSendResponse> resultList = new ArrayList<EstimateSendResponse>();
      estimateRequest.setObjectNo(estimateCopyRequest.getCopyObjectNo());
      estimateRequest.setPlanNo(estimateCopyRequest.getCopyPlanNo());
      resultList = this.sendEstimateApi(estimateRequest);
      // API에서 받은 문서번호 업데이트
      for (EstimateSendResponse result : resultList) {
        estimateRequest.setObjectNo(result.getObjectNo());
        estimateRequest.setPlanNo(result.getPlanNo());
        estimateRequest.setDocNo(result.getDocNo());
        estimateRequest.setSyncFlg(result.getSyncFlg());
        estimateRequest.setUserId(estimateCopyRequest.getUserId());

        estimateMapper.updateEstimateApi(estimateRequest);
      }

    } catch (Exception e) {
      throw new QcastException(ErrorCode.INTERNAL_SERVER_ERROR);
    }

    // [9]. 최종 생성 물건번호 리턴
    response.setObjectNo(estimateCopyRequest.getCopyObjectNo());
    response.setPlanNo(estimateCopyRequest.getCopyPlanNo());
    return response;
  }

  /**
   * 견적서 엑셀 다운로드
   *
   * @param request HttpServletRequest
   * @param response HttpServletResponse
   * @param estimateRequest 견적서 엑셀 다운로드 요청 정보
   * @throws Exception
   */
  public void excelDownload(
      HttpServletRequest request, HttpServletResponse response, EstimateRequest estimateRequest)
      throws Exception {

    // file Name 명이 없는경우
    if (estimateRequest.getFileName() == null || "".equals(estimateRequest.getFileName())) {
      estimateRequest.setFileName(
          "Quation_Detail_" + new SimpleDateFormat("yyyyMMdd").format(new Date()));
    }

    EstimateResponse estimateResponse = new EstimateResponse();
    String splitStr = "、";

    try {
      // 견적서 상세 조회
      estimateResponse = estimateMapper.selectEstimatePdfDetail(estimateRequest);

      if ("1".equals(estimateRequest.getSchDisplayFlg())) {
        estimateResponse.setCustSaleStoreName(estimateResponse.getObjectName());
        estimateResponse.setCustOmit(estimateResponse.getObjectNameOmit());
      } else {
        estimateResponse.setCustOmit("様邸");
      }

      // 견적서 특이사항 조회
      List<NoteResponse> noteList = new ArrayList<NoteResponse>();
      if (!StringUtils.isEmpty(estimateResponse.getEstimateOption())) {
        String[] arrSpnAttrCd =
            new String[estimateResponse.getEstimateOption().split(splitStr).length];
        int i = 0;
        for (String str : estimateResponse.getEstimateOption().split(splitStr)) {
          arrSpnAttrCd[i++] = str;
        }

        NoteRequest noteRequest = new NoteRequest();
        noteRequest.setArrSpnAttrCd(arrSpnAttrCd);
        noteList = estimateMapper.selectEstimateNoteList(noteRequest);

        estimateResponse.setNoteList(noteList);
      }

      // 지붕재 목록 조회
      RoofInfoResponse roofInfoResponse = new RoofInfoResponse();
      List<RoofResponse> roofList = estimateMapper.selectEstimateRoofList(estimateRequest);
      List<RoofResponse> roofPcList = estimateMapper.selectEstimateRoofPcList(estimateRequest);
      estimateRequest.setSchItemGroup("MODULE_");
      List<RoofResponse> roofVolList = estimateMapper.selectEstimateRoofVolList(estimateRequest);

      BigDecimal moduleTotAmount = BigDecimal.ZERO;
      BigDecimal moduleTotVolKw = BigDecimal.ZERO;
      for (RoofResponse roofVol : roofVolList) {
        BigDecimal amount =
            new BigDecimal(StringUtils.isEmpty(roofVol.getAmount()) ? "0" : roofVol.getAmount());
        BigDecimal vol =
            new BigDecimal(StringUtils.isEmpty(roofVol.getVolKw()) ? "0" : roofVol.getVolKw());

        String classTypeName = "";
        if ("0".equals(roofVol.getClassType())) {
          classTypeName = roofVol.getSlope() + "寸";
        } else {
          classTypeName = roofVol.getAngle() + "º";
        }
        roofVol.setClassTypeName(classTypeName);

        moduleTotAmount = moduleTotAmount.add(amount);
        moduleTotVolKw = moduleTotVolKw.add(vol);
      }
      roofInfoResponse.setModuleTotAmount(String.valueOf(moduleTotAmount));
      roofInfoResponse.setModuleTotVolKw(String.valueOf(moduleTotVolKw));

      roofInfoResponse.setRoofList(roofList);
      roofInfoResponse.setRoofPcList(roofPcList);
      roofInfoResponse.setRoofVolList(roofVolList);

      // 인증용량 구하기 (지붕면마다 모듈과 PCS의 총 용량을 서로 비교해 낮은쪽 용량으로 합산)
      roofInfoResponse.setCertVolKw(estimateMapper.selectEstimateRoofCertVolKw(estimateRequest));

      estimateResponse.setRoofInfo(roofInfoResponse);

      // 아이템 목록 조회
      List<ItemResponse> estimateItemList = estimateMapper.selectEstimateItemList(estimateRequest);

      // 총 합산금액 계산
      this.selectTotalPriceInfo(
          estimateResponse, estimateItemList, estimateRequest.getSchUnitPriceFlg());

      int j = 1;
      for (ItemResponse itemResponse : estimateItemList) {
        itemResponse.setNo(String.valueOf(j++));

        // 문자열 통화로 변환 처리
        itemResponse.setSalePrice(
            String.format("%1$,.0f", Double.parseDouble(itemResponse.getSalePrice())));
        itemResponse.setAmount(
            String.format("%1$,.0f", Double.parseDouble(itemResponse.getAmount())));
        itemResponse.setSaleTotPrice(
            String.format("%1$,.0f", Double.parseDouble(itemResponse.getSaleTotPrice())));

        if ("YJSS".equals(estimateResponse.getEstimateType())
            && (!StringUtils.isEmpty(itemResponse.getPaDispOrder())
                || !"1".equals(itemResponse.getPkgMaterialFlg()))) {
          itemResponse.setSalePrice("");
          itemResponse.setSaleTotPrice("");
        }
      }

      // 합산 문자열 통화로 변환 처리
      estimateResponse.setPkgYn("YJSS".equals(estimateResponse.getEstimateType()) ? "Y" : "N");
      estimateResponse.setPkgNo(
          "YJSS".equals(estimateResponse.getEstimateType()) ? String.valueOf(j++) : "");
      if ("YJSS".equals(estimateResponse.getEstimateType())) {
        estimateResponse.setPkgAsp(
            String.format("%1$,.0f", Double.parseDouble(estimateResponse.getPkgAsp())));
      }
      estimateResponse.setTotVol(
          String.format("%1$,.0f", Double.parseDouble(estimateResponse.getTotVol())));
      estimateResponse.setPkgTotPrice(
          String.format("%1$,.0f", Double.parseDouble(estimateResponse.getPkgTotPrice())));
      estimateResponse.setSupplyPrice(
          String.format("%1$,.0f", Double.parseDouble(estimateResponse.getSupplyPrice())));
      estimateResponse.setVatPrice(
          String.format("%1$,.0f", Double.parseDouble(estimateResponse.getVatPrice())));
      estimateResponse.setTotPrice(
          String.format("%1$,.0f", Double.parseDouble(estimateResponse.getTotPrice())));

      // 발전시뮬레이션 계산
      PwrGnrSimRequest pwrGnrSimRequest = new PwrGnrSimRequest();
      pwrGnrSimRequest.setObjectNo(estimateResponse.getObjectNo());
      pwrGnrSimRequest.setPlanNo(estimateResponse.getPlanNo());

      PwrGnrSimResponse pwrGnrSimResponse =
          pwrGnrSimService.selectPwrGnrSimulation(pwrGnrSimRequest);
      pwrGnrSimResponse.setPwrGnrSimType(estimateRequest.getPwrGnrSimType());

      if (pwrGnrSimResponse != null) {
        try {
          // 발전시뮬레이션 안내사항 조회
          PwrGnrSimGuideResponse pwrGnrSimGuideInfo =
              pwrGnrSimService.selectPwrGnrSimulationGuideInfo();
          if (pwrGnrSimGuideInfo != null) {
            pwrGnrSimResponse.setGuideInfo(pwrGnrSimGuideInfo.getData());
          }
        } catch (Exception e) {
        }
      }

      estimateResponse.setPwrGnrSim(pwrGnrSimResponse);

      if ("PDF".equals(estimateRequest.getSchDownload())) { // PDF 다운로드
        String[] arrSection = new String[6];
        int iSection = 0;

        String templateFilePath = "pdf_download_quotation_detail_template.html";

        // 템플릿 html 조회
        Document doc = PdfUtil.getPdfDoc(request, templateFilePath);

        // 견적서 상세 pdf Html 생성
        doc = this.estimatePdfHtml(doc, estimateResponse, estimateItemList);

        // 발전시뮬레이션 pdf Html 생성
        doc = pwrGnrSimService.pwrGnrSimPdfHtml(doc, pwrGnrSimResponse);

        // SchDrawingFlg (1 : 견적서,2 : 발전시뮬레이션, 3 : 도면, 4 : 가대)
        // ex) 1|2|3|4
        if (!StringUtils.isEmpty(estimateRequest.getSchDrawingFlg())) {
          if (estimateRequest.getSchDrawingFlg().indexOf("1") > -1) {
            arrSection[iSection] = "div.section1";
            iSection++;
            arrSection[iSection] = "div.section2";
            iSection++;
          }

          if (estimateRequest.getSchDrawingFlg().indexOf("2") > -1) {
            arrSection[iSection] = "div.section3"; // 발전시뮬레이션
            iSection++;
          }

          if (estimateRequest.getSchDrawingFlg().indexOf("3") > -1) {
            arrSection[iSection] = "div.section4"; // 도면
            iSection++;
            arrSection[iSection] = "div.section5"; // 도면
            iSection++;
          }

          if (estimateRequest.getSchDrawingFlg().indexOf("4") > -1) {
            arrSection[iSection] = "div.section6";
            iSection++;
          }
        }

        // pdf 다운로드
        PdfUtil.pdfDownload(request, response, doc, estimateRequest.getFileName(), arrSection);

      } else {

        Workbook workbook = null;

        String excelTemplateNam = "excel_download_quotation_detail_template.xlsx";

        ExcelUtil excelUtil = new ExcelUtil();
        byte[] excelBytes =
            excelUtil.download(
                request,
                response,
                excelUtil.convertVoToMap(estimateResponse),
                excelUtil.convertListToMap(estimateItemList),
                excelTemplateNam);

        InputStream in = new ByteArrayInputStream(excelBytes);
        workbook = WorkbookFactory.create(in); // JXLS POI 엑셀로 재변환

        // SchDrawingFlg (1 : 견적서,2 : 발전시뮬레이션, 3 : 도면, 4 : 가대)
        // ex) 1|2|3|4
        if (!StringUtils.isEmpty(estimateRequest.getSchDrawingFlg())) {
          if (estimateRequest.getSchDrawingFlg().indexOf("1") < 0) {
            workbook.removeSheetAt(workbook.getSheetIndex("見積書"));
            workbook.removeSheetAt(workbook.getSheetIndex("特異事項"));
          }

          if (estimateRequest.getSchDrawingFlg().indexOf("2") < 0) {
            workbook.removeSheetAt(workbook.getSheetIndex("発電シミュレーション"));
          }

          if (estimateRequest.getSchDrawingFlg().indexOf("3") < 0) {
            workbook.removeSheetAt(workbook.getSheetIndex("割付図・系統図"));
            workbook.removeSheetAt(workbook.getSheetIndex("架台図"));
          }
        }

        // 추후 개발 (가대중량표)
        if (estimateRequest.getSchDrawingFlg().indexOf("4") < 0) {}

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        workbook.write(byteArrayOutputStream);
        excelBytes = byteArrayOutputStream.toByteArray();

        response.setHeader(
            "Content-Disposition",
            "attachment; filename=\"" + estimateRequest.getFileName() + ".xlsx\"");
        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
        response.getOutputStream().write(excelBytes);
      }

    } catch (Exception e) {
      throw new QcastException(ErrorCode.INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * 아이템 목록을 통한 견적서 가격 자동계산
   *
   * @param estimateResponse 견적서 정보
   * @param itemList 아이템 정보 목록
   * @param unitPriceFlg 가격 표시 코드
   * @throws Exception
   */
  public void selectTotalPriceInfo(
      EstimateResponse estimateResponse, List<ItemResponse> itemList, String unitPriceFlg)
      throws Exception {
    BigDecimal totAmount = BigDecimal.ZERO;
    BigDecimal totVol = BigDecimal.ZERO;
    BigDecimal pkgTotPrice = BigDecimal.ZERO;
    BigDecimal supplyPrice = BigDecimal.ZERO;
    BigDecimal vatPrice = BigDecimal.ZERO;
    BigDecimal totPrice = BigDecimal.ZERO;

    String estimateType = estimateResponse.getEstimateType();

    // 주택패키지 단가
    BigDecimal pkgAsp =
        new BigDecimal(
            StringUtils.isEmpty(estimateResponse.getPkgAsp()) ? "0" : estimateResponse.getPkgAsp());

    for (ItemResponse itemResponse : itemList) {
      // 수량
      BigDecimal amount =
          new BigDecimal(
              StringUtils.isEmpty(itemResponse.getAmount()) ? "0" : itemResponse.getAmount());
      // 판매가
      BigDecimal salePrice = BigDecimal.ZERO;
      if ("1".equals(unitPriceFlg)) {
        salePrice =
            new BigDecimal(
                StringUtils.isEmpty(itemResponse.getUnitPrice())
                    ? "0"
                    : itemResponse.getUnitPrice());
      } else {
        salePrice =
            new BigDecimal(
                StringUtils.isEmpty(itemResponse.getSalePrice())
                    ? "0"
                    : itemResponse.getSalePrice());
      }
      // 아이템용량
      BigDecimal pnowW =
          new BigDecimal(
              StringUtils.isEmpty(itemResponse.getPnowW()) ? "0" : itemResponse.getPnowW());

      // 아이템 단가 합산
      itemResponse.setSaleTotPrice(String.valueOf(salePrice.multiply(amount)));

      // 컴포넌트는 제외하고 계산
      if (StringUtils.isEmpty(itemResponse.getPaDispOrder())) {
        // YJSS인 경우 (PKG 단가 * 모듈용량) + 패키지 제외상품 총 합산
        // YJOD인 경우 모든 아이템의 총 합산
        if ("YJSS".equals(estimateType)) {
          if ("1".equals(itemResponse.getPkgMaterialFlg())) { // 패키지 제외상품 여부(1)
            supplyPrice = supplyPrice.add(salePrice.multiply(amount));
          } else {
            if ("1".equals(itemResponse.getModuleFlg())) {
              totVol = totVol.add(pnowW.multiply(amount));
            }
          }
        } else {
          if ("1".equals(itemResponse.getModuleFlg())) {
            totVol = totVol.add(pnowW.multiply(amount));
          }

          supplyPrice = supplyPrice.add(salePrice.multiply(amount));
        }

        // 주문수량 더하기
        totAmount = totAmount.add(amount);
      }
    }

    if ("YJSS".equals(estimateType)) {
      pkgTotPrice = pkgAsp.multiply(totVol);
      pkgTotPrice = pkgTotPrice.setScale(0, BigDecimal.ROUND_HALF_UP);
      supplyPrice = supplyPrice.add(pkgTotPrice);
    }

    // 부가세 계산 (10프로)
    vatPrice = supplyPrice.multiply(new BigDecimal("0.1"));
    // 총 가격 합산 (공급가 + 부가세)
    totPrice = totPrice.add(supplyPrice.add(vatPrice));

    estimateResponse.setPkgTotPrice(String.valueOf(pkgTotPrice));
    estimateResponse.setTotAmount(String.valueOf(totAmount.setScale(0, BigDecimal.ROUND_HALF_UP)));
    estimateResponse.setTotVol(String.valueOf(totVol));
    estimateResponse.setTotVolKw(
        String.valueOf(
            totVol.multiply(new BigDecimal("0.001")).setScale(3, BigDecimal.ROUND_FLOOR)));
    estimateResponse.setSupplyPrice(
        String.valueOf(supplyPrice.setScale(0, BigDecimal.ROUND_HALF_UP)));
    estimateResponse.setVatPrice(String.valueOf(vatPrice.setScale(0, BigDecimal.ROUND_HALF_UP)));
    estimateResponse.setTotPrice(String.valueOf(totPrice.setScale(0, BigDecimal.ROUND_HALF_UP)));
  }

  /**
   * QSP Q.CAST 견적서 전송 API
   *
   * @param estimateRequest 견적서 요청 정보
   * @return String 문서번호
   * @throws Exception
   */
  public List<EstimateSendResponse> sendEstimateApi(EstimateRequest estimateRequest)
      throws Exception {
    EstimateSendRequest estimateSendRequest = new EstimateSendRequest();
    List<EstimateSendResponse> quoteList = new ArrayList<EstimateSendResponse>();
    String docNo = "";

    // 견적서 상세 조회
    EstimateSendResponse estimateSendResponse =
        estimateMapper.selectEstimateApiDetail(estimateRequest);

    if (estimateSendResponse == null) {
      throw new QcastException(ErrorCode.NOT_FOUND, message.getMessage("common.message.no.data"));
    } else {
      estimateSendResponse.setSaveType("3");
      estimateSendResponse.setSyncFlg("0");
      estimateSendResponse.setConstructSpecification(
          !StringUtils.isEmpty(estimateSendResponse.getConstructSpecification())
              ? estimateSendResponse.getConstructSpecification().split("、")[0]
              : "");
      estimateSendResponse.setDelFlg("1".equals(estimateSendResponse.getDelFlg()) ? "Y" : "N");

      // 아이템 목록 조회
      estimateRequest.setSchBomNotExist("1");
      List<ItemResponse> estimateItemList = estimateMapper.selectEstimateItemList(estimateRequest);
      estimateSendResponse.setItemList(estimateItemList);

      // 첨부파일 목록 조회
      FileRequest fileDeleteReq = new FileRequest();
      fileDeleteReq.setObjectNo(estimateRequest.getObjectNo());
      fileDeleteReq.setPlanNo(estimateRequest.getPlanNo());
      fileDeleteReq.setCategory("10");

      List<FileResponse> fileList = fileMapper.selectFileList(fileDeleteReq);
      estimateSendResponse.setFileList(fileList);

      quoteList.add(estimateSendResponse);
      estimateSendRequest.setQuoteList(quoteList);
    }

    EstimateApiResponse response = null;
    /* [1]. QSP API CALL -> Response */
    String strResponse =
        interfaceQsp.callApi(
            HttpMethod.POST, QSP_API_URL + "/api/order/qcastQuotationSave", estimateSendRequest);

    if (!"".equals(strResponse)) {
      com.fasterxml.jackson.databind.ObjectMapper om =
          new com.fasterxml.jackson.databind.ObjectMapper()
              .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
      response = om.readValue(strResponse, EstimateApiResponse.class);

      Map<String, Object> result = (Map<String, Object>) response.getData();

      if (result != null) {
        List<Map<String, Object>> succList = (List<Map<String, Object>>) result.get("successList");
        for (Map<String, Object> succ : succList) {
          if (succ != null) {
            for (EstimateSendResponse data : quoteList) {
              if (data.getObjectNo().equals(succ.get("objectNo"))
                  && data.getPlanNo().equals(succ.get("planNo"))) {
                data.setSyncFlg(String.valueOf(succ.get("syncFlg")));
                data.setDocNo(String.valueOf(succ.get("docNo")));
              }
            }
          }
        }
      }
    } else {
      log.error("sendEstimateApi >>> " + message.getMessage("common.message.no.data"));
    }

    return quoteList;
  }

  /**
   * 견적서 PDF HTML 생성
   *
   * @param doc Document
   * @param data 견적서 상세정보
   * @param list 견적서 아이템 목록 정보
   * @return Document Document
   * @throws IOException
   * @throws QcastException
   */
  public Document estimatePdfHtml(Document doc, EstimateResponse data, List<ItemResponse> list)
      throws IOException, QcastException {

    Element elm;

    // 견적서 상세 정보 설정
    elm = doc.getElementById("custSaleStoreName");
    elm.text(StringUtils.defaultString(data.getCustSaleStoreName()));

    elm = doc.getElementById("custOmit");
    elm.text(StringUtils.defaultString(data.getCustOmit()));

    elm = doc.getElementById("estimateValidityTerm");
    elm.text(StringUtils.defaultString(data.getEstimateValidityTerm()));

    elm = doc.getElementById("objectName1");
    elm.text(StringUtils.defaultString(data.getObjectName()));

    elm = doc.getElementById("objectNo1");
    elm.text(StringUtils.defaultString(data.getObjectNo()));

    elm = doc.getElementById("planNo");
    elm.text(StringUtils.defaultString(data.getPlanNo()));

    elm = doc.getElementById("estimateDate");
    elm.text(StringUtils.defaultString(data.getEstimateDate()));

    elm = doc.getElementById("bizNo");
    elm.text(StringUtils.defaultString(data.getBizNo()));

    elm = doc.getElementById("zipNo");
    elm.text(StringUtils.defaultString(data.getZipNo()));

    elm = doc.getElementById("address");
    elm.text(StringUtils.defaultString(data.getAddress()));

    elm = doc.getElementById("tel");
    elm.text(StringUtils.defaultString(data.getTel()));

    elm = doc.getElementById("fax");
    elm.text(StringUtils.defaultString(data.getFax()));

    elm = doc.getElementById("totVolKw1");
    elm.text(StringUtils.defaultString(data.getTotVolKw()));

    elm = doc.getElementById("totPrice");
    elm.text(StringUtils.defaultString(data.getTotPrice()));

    StringBuilder sb = new StringBuilder();
    for (ItemResponse itemResponse : list) {
      sb.append("<tr>");
      sb.append(
          "<td style='width:60px;'>" + StringUtils.defaultString(itemResponse.getNo()) + "</td>");
      sb.append(
          "<td style='width:120px;text-align:left;'>"
              + StringUtils.defaultString(itemResponse.getItemName())
              + "</td>");
      sb.append(
          "<td style='width:120px;'>"
              + StringUtils.defaultString(itemResponse.getItemNo())
              + "</td>");
      sb.append(
          "<td style='width:80px;text-align:right;'>"
              + StringUtils.defaultString(itemResponse.getSalePrice())
              + "</td>");
      sb.append(
          "<td style='width:60px;text-align:right;'>"
              + StringUtils.defaultString(itemResponse.getAmount())
              + "</td>");
      sb.append(
          "<td style='width:60px;'>" + StringUtils.defaultString(itemResponse.getUnit()) + "</td>");
      sb.append(
          "<td style='width:80px;text-align:right;'>"
              + StringUtils.defaultString(itemResponse.getSaleTotPrice())
              + "</td>");
      sb.append("</tr>");
    }
    if ("Y".equals(data.getPkgYn())) {
      sb.append("<tr>");
      sb.append("<td style='width:60px;'>" + StringUtils.defaultString(data.getPkgNo()) + "</td>");
      sb.append("<td style='width:120px;text-align:left;'>住宅 PKG</td>");
      sb.append("<td style='width:120px;'>-</td>");
      sb.append(
          "<td style='width:80px;text-align:right;'>"
              + StringUtils.defaultString(data.getPkgAsp())
              + "</td>");
      sb.append(
          "<td style='width:60px;text-align:right;'>"
              + StringUtils.defaultString(data.getTotVol())
              + "</td>");
      sb.append("<td style='width:60px;'>W</td>");
      sb.append(
          "<td style='width:80px;text-align:right;'>"
              + StringUtils.defaultString(data.getPkgTotPrice())
              + "</td>");
      sb.append("</tr>");
    }
    elm = doc.getElementById("itemList_detail");
    elm.before(sb.toString());

    elm = doc.getElementById("supplyPrice");
    elm.text(StringUtils.defaultString(data.getSupplyPrice()));

    elm = doc.getElementById("vatPrice");
    elm.text(StringUtils.defaultString(data.getVatPrice()));

    elm = doc.getElementById("totPrice1");
    elm.text(StringUtils.defaultString(data.getTotPrice()));

    elm = doc.getElementById("remarks");
    elm.text(StringUtils.defaultString(data.getRemarks()));

    // 견적서 특이사항 목록 설정
    if (data.getNoteList() != null) {
      sb = new StringBuilder();
      for (NoteResponse noteResponse : data.getNoteList()) {
        sb.append("<tr>");
        sb.append(
            "<td style='text-align:left;'>"
                + StringUtils.defaultString(noteResponse.getCodeNm())
                + "</td>");
        sb.append(
            "<td style='text-align:left;'>"
                + StringUtils.defaultString(noteResponse.getRemarks())
                + "</td>");
        sb.append("</tr>");
      }
      elm = doc.getElementById("noteList_detail");
      elm.append(sb.toString());
    }

    // 도면 설정
    elm = doc.getElementById("objectNo4");
    elm.text(
        StringUtils.defaultString(data.getObjectNo())
            + " (Plan No : "
            + StringUtils.defaultString(data.getPlanNo())
            + ")");

    elm = doc.getElementById("objectName");
    elm.text(StringUtils.defaultString(data.getObjectName()));

    elm = doc.getElementById("totVolKw");
    elm.text(StringUtils.defaultString(data.getTotVolKw()) + " Kw");

    elm = doc.getElementById("certVolKw");
    elm.text(StringUtils.defaultString(data.getRoofInfo().getCertVolKw()) + " Kw");

    elm = doc.getElementById("drawingEstimateCreateDate4");
    elm.text(StringUtils.defaultString(data.getDrawingEstimateCreateDate()));

    elm = doc.getElementById("prefName4");
    elm.text(StringUtils.defaultString(data.getPrefName()));

    elm = doc.getElementById("areaName4");
    elm.text(StringUtils.defaultString(data.getAreaName()));

    elm = doc.getElementById("snowfall4");
    elm.text(StringUtils.defaultString(data.getSnowfall()) + " cm");

    elm = doc.getElementById("standardWindSpeedName4");
    elm.text(StringUtils.defaultString(data.getStandardWindSpeedName()));

    // 도면(가대제외 이미지) 노출 ???

    int no = 1;
    sb = new StringBuilder();
    for (ItemResponse itemResponse : list) {
      if (!"STAND_".equals(itemResponse.getItemGroup())) {
        sb.append("<tr>");
        sb.append("<td>" + (no++) + "</td>");
        sb.append(
            "<td style='text-align:left;'>"
                + StringUtils.defaultString(itemResponse.getItemNo())
                + "</td>");
        sb.append(
            "<td style='text-align:right;'>"
                + StringUtils.defaultString(itemResponse.getAmount())
                + "</td>");
        sb.append("</tr>");
      }
    }
    elm = doc.getElementById("notStandItemList_detail");
    elm.append(sb.toString());

    if (data.getRoofInfo().getRoofPcList() != null) {
      no = 1;
      sb = new StringBuilder();
      for (RoofResponse roofResponse : data.getRoofInfo().getRoofPcList()) {
        sb.append("<tr>");
        sb.append("<td>" + (no++) + "</td>");
        sb.append(
            "<td style='text-align:left;'>"
                + StringUtils.defaultString(roofResponse.getItemNo())
                + "["
                + roofResponse.getPcModuleAmount()
                + "]"
                + "</td>");
        sb.append("</tr>");
      }
      elm = doc.getElementById("pcsItemList_detail");
      elm.append(sb.toString());
    }

    if (data.getRoofInfo().getRoofVolList() != null) {
      sb = new StringBuilder();
      for (RoofResponse roofResponse : data.getRoofInfo().getRoofVolList()) {
        sb.append("<tr>");
        sb.append("<td>" + StringUtils.defaultString(roofResponse.getRoofSurface()) + "</td>");
        sb.append("<td>" + StringUtils.defaultString(roofResponse.getClassTypeName()) + "</td>");
        sb.append("<td>" + StringUtils.defaultString(roofResponse.getAmount()) + "</td>");
        sb.append(
            "<td style='text-align:right;'>"
                + StringUtils.defaultString(roofResponse.getVolKw())
                + "</td>");
        sb.append("</tr>");
      }
      elm = doc.getElementById("surFaceList_detail");
      elm.before(sb.toString());

      elm = doc.getElementById("moduleTotAmount");
      elm.text(StringUtils.defaultString(data.getRoofInfo().getModuleTotAmount()));

      elm = doc.getElementById("moduleTotVolKw");
      elm.text(StringUtils.defaultString(data.getRoofInfo().getModuleTotVolKw()));
    }

    if (data.getRoofInfo().getRoofList() != null) {
      sb = new StringBuilder();
      for (RoofResponse roofResponse : data.getRoofInfo().getRoofList()) {
        sb.append("<tr>");
        sb.append("<td>" + StringUtils.defaultString(roofResponse.getRoofSurface()) + "</td>");
        sb.append(
            "<td style='text-align:left;'>"
                + StringUtils.defaultString(roofResponse.getRoofMaterialName())
                + "</td>");
        sb.append("<td>" + StringUtils.defaultString(roofResponse.getSlope()) + "寸</td>");
        sb.append(
            "<td>"
                + StringUtils.defaultString(roofResponse.getConstructSpecificationName())
                + "</td>");
        sb.append(
            "<td style='text-align:left;'>"
                + StringUtils.defaultString(roofResponse.getSupportMethodName())
                + "</td>");
        sb.append("<td>" + StringUtils.defaultString(roofResponse.getSurfaceType()) + "</td>");
        sb.append("<td>" + StringUtils.defaultString(roofResponse.getSetupHeight()) + "</td>");
        sb.append("</tr>");
      }
      elm = doc.getElementById("surFaceList_detail2");
      elm.before(sb.toString());
    }

    elm = doc.getElementById("objectNo5");
    elm.text(
        StringUtils.defaultString(data.getObjectNo())
            + " (Plan No : "
            + StringUtils.defaultString(data.getPlanNo())
            + ")");

    elm = doc.getElementById("objectName5");
    elm.text(StringUtils.defaultString(data.getObjectName()));

    elm = doc.getElementById("totVolKw5");
    elm.text(StringUtils.defaultString(data.getTotVolKw()) + " Kw");

    elm = doc.getElementById("certVolKw5");
    elm.text(StringUtils.defaultString(data.getRoofInfo().getCertVolKw()) + " Kw");

    elm = doc.getElementById("drawingEstimateCreateDate5");
    elm.text(StringUtils.defaultString(data.getDrawingEstimateCreateDate()));

    elm = doc.getElementById("prefName5");
    elm.text(StringUtils.defaultString(data.getPrefName()));

    elm = doc.getElementById("areaName5");
    elm.text(StringUtils.defaultString(data.getAreaName()));

    elm = doc.getElementById("snowfall5");
    elm.text(StringUtils.defaultString(data.getSnowfall()) + " cm");

    elm = doc.getElementById("standardWindSpeedName5");
    elm.text(StringUtils.defaultString(data.getStandardWindSpeedName()));

    // 도면(가대 이미지) 노출 ???

    no = 1;
    sb = new StringBuilder();
    for (ItemResponse itemResponse : list) {
      if ("STAND_".equals(itemResponse.getItemGroup())) {
        sb.append("<tr>");
        sb.append("<td>" + (no++) + "</td>");
        sb.append(
            "<td style='text-align:left;'>"
                + StringUtils.defaultString(itemResponse.getItemNo())
                + "</td>");
        sb.append(
            "<td style='text-align:right;'>"
                + StringUtils.defaultString(itemResponse.getAmount())
                + "</td>");
        sb.append("</tr>");
      }
    }
    elm = doc.getElementById("standItemList_detail");
    elm.append(sb.toString());

    if (data.getRoofInfo().getRoofList() != null) {
      sb = new StringBuilder();
      for (RoofResponse roofResponse : data.getRoofInfo().getRoofList()) {
        sb.append("<tr>");
        sb.append("<td>" + StringUtils.defaultString(roofResponse.getRoofSurface()) + "</td>");
        sb.append(
            "<td style='text-align:left;'>"
                + StringUtils.defaultString(roofResponse.getRoofMaterialName())
                + "</td>");
        sb.append("<td>" + StringUtils.defaultString(roofResponse.getSlope()) + "寸</td>");
        sb.append(
            "<td>"
                + StringUtils.defaultString(roofResponse.getConstructSpecificationName())
                + "</td>");
        sb.append(
            "<td style='text-align:left;'>"
                + StringUtils.defaultString(roofResponse.getSupportMethodName())
                + "</td>");
        sb.append("<td>" + StringUtils.defaultString(roofResponse.getSurfaceType()) + "</td>");
        sb.append("<td>" + StringUtils.defaultString(roofResponse.getSetupHeight()) + "</td>");
        sb.append("</tr>");
      }
      elm = doc.getElementById("roofList_detail");
      elm.before(sb.toString());
    }

    return doc;
  }

  /**
   * Plan 확정 정보 동기화
   *
   * @param planSyncList Plan 목록
   * @return
   * @throws Exception
   */
  public int setPlanConfirmSyncSave(List<PlanSyncResponse> planSyncList) throws Exception {
    int cnt = 0;
    for (PlanSyncResponse planSyncData : planSyncList) {
      // Plan 확정 처리
      cnt += estimateMapper.updatePlanConfirmSync(planSyncData);
    }
    return cnt;
  }

  /**
   * QSP Q.CAST 견적서 동기화 실패 목록
   *
   * @return EstimateSendRequest 견적서 실패 목록
   * @throws Exception
   */
  public EstimateSendRequest selectEstimateSyncFailList() throws Exception {
    EstimateSendRequest estimateSendRequest = new EstimateSendRequest();
    List<EstimateSendResponse> quoteList = new ArrayList<EstimateSendResponse>();
    String docNo = "";

    // 견적서 동기화 실패 목록 조회
    List<EstimateSendResponse> estimateSendListResponse =
        estimateMapper.selectEstimateApiFailList();

    for (EstimateSendResponse estimateSendResponse : estimateSendListResponse) {
      EstimateRequest estimateRequest = new EstimateRequest();
      estimateRequest.setObjectNo(estimateSendResponse.getObjectNo());
      estimateRequest.setPlanNo(estimateSendResponse.getPlanNo());

      estimateSendResponse.setSaveType("3");
      estimateSendResponse.setSyncFlg("0");
      estimateSendResponse.setConstructSpecification(
          !StringUtils.isEmpty(estimateSendResponse.getConstructSpecification())
              ? estimateSendResponse.getConstructSpecification().split("、")[0]
              : "");
      estimateSendResponse.setDelFlg("1".equals(estimateSendResponse.getDelFlg()) ? "Y" : "N");

      // 아이템 목록 조회
      estimateRequest.setSchBomNotExist("1");
      List<ItemResponse> estimateItemList = estimateMapper.selectEstimateItemList(estimateRequest);
      estimateSendResponse.setItemList(estimateItemList);

      // 첨부파일 목록 조회
      FileRequest fileDeleteReq = new FileRequest();
      fileDeleteReq.setObjectNo(estimateRequest.getObjectNo());
      fileDeleteReq.setPlanNo(estimateRequest.getPlanNo());
      fileDeleteReq.setCategory("10");

      List<FileResponse> fileList = fileMapper.selectFileList(fileDeleteReq);
      estimateSendResponse.setFileList(fileList);

      quoteList.add(estimateSendResponse);
      estimateSendRequest.setQuoteList(quoteList);
    }

    return estimateSendRequest;
  }

  /**
   * 견적서 정보 QSP 동기화
   *
   * @param planSyncList Plan 목록
   * @return
   * @throws Exception
   */
  public int setEstimateSyncSave(List<PlanSyncResponse> planSyncList) throws Exception {
    int cnt = 0;
    for (PlanSyncResponse planSyncData : planSyncList) {
      EstimateRequest estimateRequest = new EstimateRequest();
      estimateRequest.setObjectNo(planSyncData.getObjectNo());
      estimateRequest.setPlanNo(planSyncData.getPlanNo());
      estimateRequest.setDocNo(planSyncData.getDocNo());
      estimateRequest.setSyncFlg(planSyncData.getSyncFlg());
      estimateRequest.setUserId("system");

      // 견적서 동기화 여부 처리
      cnt += estimateMapper.updateEstimateApi(estimateRequest);
    }
    return cnt;
  }
}
