package itn.com.cmm.util;

import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;

import itn.com.cmm.MjonMsgSendVO;
import itn.com.cmm.OptimalMsgResultDTO;
import itn.let.mail.service.StatusResponse;
import itn.let.mjo.event.service.MjonEventVO;
import itn.let.mjo.mjocommon.MjonCommon;
import itn.let.mjo.msg.service.MjonMsgVO;
import itn.let.mjo.msgagent.service.MjonMsgAgentStsVO;
import itn.let.mjo.spammsg.web.ComGetSpamStringParser;
import itn.let.sym.site.service.JoinSettingVO;
import itn.let.uss.umt.service.MberManageVO;
import itn.let.uss.umt.service.UserManageVO;
import lombok.extern.slf4j.Slf4j;

/**
 * 
 * @author 		: 이호영
 * @fileName 	: MsgSendUtils.java 
 * @date 		: 2024.09.23
 * @description : 메세지발송 데이터 다루는 유틸
 * =========================================================== 
 * DATE          AUTHOR   NOTE 
 * ----------------------------------------------------------- *
 * 2024.09.23    이호영          최초 생성
 * 
 * 
 * 
 */
@Slf4j
public final class MsgSendUtils {

	// 단문 메세지 타입
	public static final String SHORT_MSG_TYPE = "4";
	// 장문 메세지 타입
	public static final String LONG_MSG_TYPE = "6";
	// 이벤트 최저 잔액
	public static final double MIN_EVENT_REMAIN_CASH = 7.5; // 이벤트 최소 잔액
	

	/** 
	 * @methodName	: getMsgType 
	 * @author		: 이호영
	 * @date		: 2024.09.23 
	 * @description	: msgType 재정의
	 * @param mjonMsgVO
	 * @param smsTxtByte
	 * @return 
	 * @throws UnsupportedEncodingException 
	 */
	public static String getMsgTypeWithByteValidation(MjonMsgSendVO sendVO, String p_smsTxt) throws UnsupportedEncodingException {
		

		//	// 내문자저장함에 저장 후 문자를 발송하는 경우 문자 타입이 숫자가 아닌 문자로 넘어와서 변경 처리함
		//	if ("P".equals(msgType) || "L".equals(msgType)) {
		//	    msgType = "6";
		//	} else if ("S".equals(msgType)) {
		//	    msgType = "4";
		//	}
			
		int smsTxtByte = MjonCommon.getSmsTxtBytes(p_smsTxt);
		String msgType = SHORT_MSG_TYPE;
	
		// 1. 2000 Byte 초과는 에러 처리
		if (smsTxtByte > 2000) {
			return "INVALID";
		}

		// 2. 첨부파일 여부 확인 (첨부파일이 있으면 장문으로 설정)
		if (StringUtils.isNotEmpty(sendVO.getFilePath1())) {
			msgType = LONG_MSG_TYPE;
		}
		// 3. 문자 길이에 따라 메시지 타입 설정 (90 Byte 초과는 장문)
		else if (smsTxtByte > 90) {
			msgType = LONG_MSG_TYPE;
		}
		return msgType;
	}

	public static float getValidPrice(Float personalPrice, Float defaultPrice) {
	    return (personalPrice != null && personalPrice > 0) ? personalPrice : defaultPrice;
	}

	
	/** 
	* @methodName	: determinePriceByMsgType 
	* @author		: 이호영
	* @date		: 2024.09.24 
	* @description	: 문자 메시지 타입에 따라 단가를 결정하는 메서드
	* @param mjonMsgVO
	* @param shortPrice
	* @param longPrice
	* @param picturePrice
	* @param picture2Price
	* @param picture3Price
	* @return 
	*/
	public static float determinePriceByMsgType(MjonMsgVO mjonMsgVO, float shortPrice, float longPrice, 
									float picturePrice, float picture2Price, float picture3Price) {
		float price = 0;
		String msgType = mjonMsgVO.getMsgType();
	
		if (SHORT_MSG_TYPE.equals(msgType)) {
			price = shortPrice;
		} else if (LONG_MSG_TYPE.equals(msgType)) {
			// 파일 첨부 여부에 따라 장문 단가 설정
			if (mjonMsgVO.getFileName3() != null) {
				price = picture3Price;
			} else if (mjonMsgVO.getFileName2() != null) {
				price = picture2Price;
			} else if (mjonMsgVO.getFileName1() != null) {
				price = picturePrice;
			} else {
				price = longPrice;
			}
		}
		return price;
	}

	
	/** 
	 * @methodName	: isReplacementRequired 
	 * @author		: 이호영
	 * @date		: 2024.11.12 
	 * @description	: 치환데이터가 있는지 확인
	 * @param mjonMsgVO
	 * @return 
	 */
	public static boolean isRepleasYN(MjonMsgVO mjonMsgVO) {
		
		// 치환 구문 패턴 리스트
		String[] placeholders = {"\\[\\*이름\\*\\]", "\\[\\*1\\*\\]", "\\[\\*2\\*\\]", "\\[\\*4\\*\\]", "\\[\\*3\\*\\]"};
		
		for (String placeholder : placeholders) {
			Pattern pattern = Pattern.compile(placeholder);
			Matcher matcher = pattern.matcher(mjonMsgVO.getSmsTxt());
			// 해당 패턴이 존재하면 true 반환
			if (matcher.find()) {
				return true;
			}
		}
		return false;
	
	}


	/** 
	 * @methodName	: populateReplacementLists 
	 * @author		: 이호영
	 * @date		: 2024.09.26 
	 * @description	: 배열에 데이터를 채우는 메서드 
	 * 				1. 치환 문자열 데이터 확인 및 문자 치환
	 * 				2. 스팸 문자 체크
	 * 				3. 메세지 타입 구하기
	 * 				4. 제목 셋팅 : 타입에 따라 분기
	 * 				5. 이미지 갯수 셋팅
	 * 				6. 예약 및 분할에 따른 시간 설정
	 * 				7. 전송사 코드 셋팅
	 * @param mjonMsgVO
	 * @param lists
	 * @param statusResponse
	 * @param agentSendCounts 
	 * @param sendRateList 
	 * @param isHolidayNotified 
	 * @return call by reference
	 * @throws Exception 
	 */
	public static Boolean populateSendLists(MjonMsgVO mjonMsgVO, List<MjonMsgSendVO> mjonMsgSendListVO
			, StatusResponse statusResponse, List<String> resultSpamTxt
			, Map<String, Integer> agentSendCounts, List<MjonMsgVO> sendRateList, boolean isHolidayNotified, UserManageVO userManageVO) throws Exception{
		
		
		
		log.info(" :: populateSendLists :: ");

		String smishingYn = userManageVO.getSmishingYn();
		String exceptSpamYn = userManageVO.getExceptSpamYn();


		// 예약 시간 기본값 설정
		Date now = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

		// ReqDate가 비어 있으면 현재 시간으로 설정, 그렇지 않으면 ReqDate로 설정
		// 화면에서 예약문자면 예약시간을 regDate로 설정한다.
		Date baseDate;
		if (StringUtils.isEmpty(mjonMsgVO.getReqDate())) {
		    mjonMsgVO.setReqDate(sdf.format(now));  // ReqDate에 현재 시간 설정
		    baseDate = now;
		} else {
		    baseDate = sdf.parse(mjonMsgVO.getReqDate()); // ReqDate를 baseDate로 설정
		}

		Calendar calendar = Calendar.getInstance();
		calendar.setTime(baseDate);  // calendar에 baseDate 설정
		int counter = 0;  // 분할 건수 카운터

		// 데이터 모두 스팸 체크를 안하고 건별로 갯수를 정해서 스팸체크를 한다.
		int spamChkSize = getSpamChkSize(mjonMsgSendListVO.size());
		int sampleCounter = 0;

		

		// 치환 구문과 필드 getter 매핑
		Map<String, Function<MjonMsgSendVO, String>> placeholders = new HashMap<>();
		placeholders.put("[*이름*]", MjonMsgSendVO::getName);
		placeholders.put("[*1*]", MjonMsgSendVO::getRep1);
		placeholders.put("[*2*]", MjonMsgSendVO::getRep2);
		placeholders.put("[*3*]", MjonMsgSendVO::getRep3);
		placeholders.put("[*4*]", MjonMsgSendVO::getRep4);

		boolean hasPerformedSpamCheck = false; // 치환 문자가 없는 경우, 스팸 체크가 한 번만 수행되도록 제어
		boolean hasPerformedMsgType = false; // 치환 문자가 없는 경우, 메세지 타입 체크 한번
		boolean hasPerformedDelayYn = false; // 치환 문자가 없는 경우, 
		
		String msgKind = mjonMsgVO.getMsgKind();
		String smsTxtTemp = mjonMsgVO.getSmsTxt();
		String mmsSubject = mjonMsgVO.getMmsSubject();
		Boolean replaceYN = getReplaceYN(smsTxtTemp);
		
		String msgTypeResult = null;
		
		

		
		
		for (MjonMsgSendVO sendVO : mjonMsgSendListVO) {
			
			sendVO.setCallFrom(mjonMsgVO.getCallFrom());
			sendVO.setCallTo(sendVO.getPhone());
			sendVO.setUserId(mjonMsgVO.getUserId());

			String smsTxt = smsTxtTemp;
			// 치환 문자면
			if(replaceYN) {
				
				// 각 치환 구문을 확인하고 치환할 값이 없으면 오류 반환
				for (Map.Entry<String, Function<MjonMsgSendVO, String>> entry : placeholders.entrySet()) {
					String placeholder = entry.getKey();
					String value = entry.getValue().apply(sendVO);
					if (smsTxt.contains(placeholder)) {
						if (StringUtils.isEmpty(value)) {
//							statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "치환 문구중 " + placeholder + " 데이터가 없습니다.");
							StatusResponse.statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "치환 문구중 " + placeholder + " 데이터가 없습니다.", "STAT_1040");
							return false;
						}
						smsTxt = smsTxt.replace(placeholder, value);
//						log.info(" + smsTxt [{}]", smsTxt);
						
					}
				}
			}

			String smsSpamChkTxt = smsTxt;
			if(StringUtils.isNotEmpty(smsTxt)) {
				smsSpamChkTxt = smsTxt.replaceAll(String.valueOf((char) 13), "");
			}

			// 이미지 셋팅
			setImagePathsForMsgSendVO(mjonMsgVO, sendVO);
			
			
			// == 치환 여부에 따라 처리 로직 분기 == 
			// 치환 문자가 아닌 경우
			if (!replaceYN) {
				// 스팸 체크와 메시지 타입 체크 각각 한 번만 수행
				if (!hasPerformedSpamCheck && "N".equals(exceptSpamYn)) {
					checkSpamAndSetStatus(mjonMsgVO, smsSpamChkTxt, resultSpamTxt, isHolidayNotified);
					hasPerformedSpamCheck = true;
				}
				if (!hasPerformedMsgType) {
 					msgTypeResult = getMsgTypeWithByteValidation(sendVO, smsTxt);
					if ("INVALID".equals(msgTypeResult)) {
//						statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다.");
						StatusResponse.statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다.", "STAT_1050");
						return false;
					}
					hasPerformedMsgType = true;
				}
			} 
			else  
			{// 치환 문자인 경우
				// 스팸 체크는 `spamChkSize`만큼 반복 수행
				if (sampleCounter < spamChkSize && !"Y".equals(mjonMsgVO.getSpamStatus()) && "N".equals(exceptSpamYn)) {
					checkSpamAndSetStatus(mjonMsgVO, smsSpamChkTxt, resultSpamTxt, isHolidayNotified);
					sampleCounter++;
				}

				// 메시지 타입 체크는 매번 수행
				msgTypeResult = getMsgTypeWithByteValidation(sendVO, smsTxt);
				if ("INVALID".equals(msgTypeResult)) {
//					statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다.");
					StatusResponse.statusResponseSet(statusResponse, HttpStatus.BAD_REQUEST, "문자 치환 후 전송 문자 길이를 초과하였습니다.", "STAT_1050");
					return false;
				}
			}
			
			
			
			
			sendVO.setSmsTxt(smsTxt);
			sendVO.setMsgType(msgTypeResult);
			
			// 제목 셋팅
			if (LONG_MSG_TYPE.equals(msgTypeResult)){
				String mmsTitleTemp = "";
				// 제목이 비어 있고 전송할 SMS 텍스트가 존재하는 경우에만 처리
				if (StringUtils.isEmpty(mmsSubject) && StringUtils.isNotEmpty(smsTxt)) {
					// SMS 텍스트를 줄 단위로 나누기
					mmsTitleTemp = getMmsgSubject(smsTxt, msgKind);
					// 설정된 제목을 현재 메시지 객체에 적용
					sendVO.setSubject(mmsTitleTemp);

				}else {
					// 설정된 제목을 현재 메시지 객체에 적용
					sendVO.setSubject(mmsSubject);
				}

			}

			

			/* @isHolidayNotified
			 *  - 관리자 알림 설정으로 인해 
			 *  - 해당 시간이면 지연 미처리
			 * @smishingYn
			 *  - 회원 별 '스미싱 온' 상태값
			 *  - Y면 알림, 지연 처리해야 함
			 * */
			if("Y".equalsIgnoreCase(smishingYn) && isHolidayNotified) {
				mjonMsgVO.setSpamStatus("Y");
				mjonMsgVO.setSmishingYn("Y");
				mjonMsgVO.setDelayYn("Y");
			}
			
			// 지연 여부 처리
			if (( "Y".equalsIgnoreCase(smishingYn) || "Y".equalsIgnoreCase(mjonMsgVO.getDelayYn()))
					&& !hasPerformedDelayYn && isHolidayNotified) {
				calendar.add(Calendar.MINUTE, 30); // 모든 시간을 30분 뒤로 미룸
				// TEST
//				calendar.add(Calendar.MINUTE, 5); // 모든 시간을 30분 뒤로 미룸
				hasPerformedDelayYn = true;
			}
			
			// 예약 여부 확인
			if ("Y".equalsIgnoreCase(mjonMsgVO.getReserveYn())) {
				// 분할 발송일 경우
				if ("on".equalsIgnoreCase(mjonMsgVO.getDivideChk())) {
					if (counter == Integer.parseInt(mjonMsgVO.getDivideCnt()))  { // 지정된 건수마다 간격 추가
						counter = 0;
						calendar.add(Calendar.MINUTE, Integer.parseInt(mjonMsgVO.getDivideTime()));
					}
					counter++;
				}
				// 예약 시간 설정
				sendVO.setReqDate(sdf.format(calendar.getTime())); // 분할된 시간 설정 또는 기본 예약 시간 사용
			} else {
				// 예약 여부가 N일 경우에도 기본 예약 시간 설정
				sendVO.setReqDate(sdf.format(calendar.getTime())); 
			}
			
			// 전송사 코드 셋팅
			String agentCode = "00".equals(mjonMsgVO.getAgentCode())
					? MsgSendUtils.assignAgentBasedOnCount(agentSendCounts, sendRateList)
					: mjonMsgVO.getAgentCode();
			sendVO.setAgentCode(agentCode);
			
		}
		// Group TB에 넣어줄 제목
		// 치환안된 sms 데이터로 넣어야함
		log.info(" mjonMsgSendListVO size :: [{}]", mjonMsgSendListVO.size());
		if (LONG_MSG_TYPE.equals(mjonMsgSendListVO.get(0).getMsgType())){
			mjonMsgVO.setMmsSubject(getMmsgSubject(smsTxtTemp, msgKind)); 
		}
		
		return true;
 
	}
	
	/** 
	 * @methodName	: setImagePathsForMsgSendVO 
	 * @author		: 이호영
	 * @date		: 2024.11.26 
	 * @description	: vo에 이미지 셋팅
	 * @param mjonMsgVO
	 * @param sendVO 
	 */
	private static void setImagePathsForMsgSendVO(MjonMsgVO mjonMsgVO, MjonMsgSendVO sendVO) {
		int fileCount = Integer.parseInt(mjonMsgVO.getFileCnt());

		switch (fileCount) {
			case 3:
				sendVO.setFilePath3(mjonMsgVO.getFileName3());
			case 2:
				sendVO.setFilePath2(mjonMsgVO.getFileName2());
			case 1:
				sendVO.setFilePath1(mjonMsgVO.getFileName1());
				break;
			default:
				// fileCount가 0이거나 설정할 파일이 없는 경우
				break;
		}
		sendVO.setFileCnt(mjonMsgVO.getFileCnt());
	}

	private static void checkSpamAndSetStatus(MjonMsgVO mjonMsgVO, String personalizedSmsTxt, List<String> resultSpamTxt, boolean isHolidayNotified) throws Exception {
		

		mjonMsgVO.setSpamStatus("N");
		mjonMsgVO.setDelayYn("N");
		
		if(StringUtils.isNotEmpty(personalizedSmsTxt)) {
			
			String resultParser = ComGetSpamStringParser.getSpamTextParse(personalizedSmsTxt).trim();
			int spmCnt = 0;
			String spmFilterTxt = "";
	
			for (String spmTxt : resultSpamTxt) {
				String parserStr = ComGetSpamStringParser.getSpamTextParse(spmTxt).trim();
				if (resultParser.contains(parserStr)) {
					spmCnt++;
					spmFilterTxt += spmTxt + ",";
				}
			}
	
			if (spmCnt > 0) { // 스팸 문자가 포함된 경우
	
				if (StringUtil.getWordRight(spmFilterTxt.trim(), 1).equals(",")) {
					// 처음부터 idx 만큼 잘라낸 나머지 글자 
					spmFilterTxt = StringUtil.getWordLeft(spmFilterTxt.trim(), 1); 	
					
				}
				
				/* @isHolidayNotified
				 *  - 관리자 알림 설정으로 인해 
				 *  - 해당 시간이면 지연 미처리
				 * */
				mjonMsgVO.setSpamStatus("Y");
				if(isHolidayNotified) {
					mjonMsgVO.setDelayYn("Y");
				}
			}
		}
	}

	public static Boolean getReplaceYN(String smsTxtTemplate) {// 여러 치환 구문이 포함된 정규식 패턴

	    if (smsTxtTemplate == null) {
	        return false; // null일 경우 false 반환
	    }
	    
		Boolean replaceYN = false;
		
		Pattern pattern = Pattern.compile("\\[\\*이름\\*\\]|\\[\\*1\\*\\]|\\[\\*2\\*\\]|\\[\\*3\\*\\]|\\[\\*4\\*\\]");
		Matcher matcher = pattern.matcher(smsTxtTemplate);

		// 정규식 패턴에 해당하는 치환 구문이 존재하는지 확인
		if (matcher.find()) {
			replaceYN = true;
		}
		return replaceYN;
	}

	/** 
	 * @methodName	: getSpamChkSize 
	 * @author		: 이호영
	 * @date		: 2024.11.13 
	 * @description	: 수신자 건수별로 스팸체크하는 갯수 설정 
	 * @param size
	 * @return 
	 */
	private static int getSpamChkSize(int size) {
		int chkSize = 1; // 기본 샘플 크기

		// 수신자 수에 따른 샘플 크기 결정
		if (size > 100 && size <= 1000) {
			chkSize = 10;
		} else if (size > 1000) {
			chkSize = 50;
		}
		
		return chkSize;
	}

	/**
	 * 특정 플레이스홀더를 리스트에서 가져온 값으로 치환합니다.
	 * 
	 * @param smsTxt		문자 내용
	 * @param placeholder	치환할 플레이스홀더 (예: [*이름*])
	 * @param list			치환할 데이터 리스트
	 * @param index			현재 인덱스
	 * @return 치환된 문자 내용
	 */
	private static String replacePlaceholder(String smsTxt, String placeholder, String[] list, int index) {
		if (smsTxt.contains(placeholder)) {
			if (list.length > index && StringUtil.isNotEmpty(list[index])) {
				smsTxt = smsTxt.replaceAll(placeholder, StringUtil.getString(list[index].replaceAll("§", ",")));
			} else {
				smsTxt = smsTxt.replaceAll(placeholder, "");
			}
		}
		return smsTxt;
	}

	// 배열 인덱스를 안전하게 접근하는 메서드
	private static String getSafeValue(String[] array, int index) {
	    return (array != null && array.length > index) ? array[index] : " ";
	}

	// 치환 처리에 특수문자 제거 로직이 포함된 메서드
	private static String getReplacementValue(String[] array, int index, String placeholder) {
	    if (array != null && array.length > index && StringUtil.isNotEmpty(array[index])) {
	        return StringUtil.getString(array[index].replaceAll("§", ","));
	    } else {
	        return " ";
	    }
	}
	
	

	public static void statusResponseSet (StatusResponse statusResponse, HttpStatus httpStatus, String msg ) {
		statusResponse.setStatus(httpStatus);
		statusResponse.setMessage(msg);
		
	}
	

	public static StatusResponse validateFilesForMessageSending(int fileCount, MjonMsgVO mjonMsgVO) {
		if (fileCount > 0) {
			boolean allFilesAreNull = Stream
					.of(mjonMsgVO.getFileName1(), mjonMsgVO.getFileName2(), mjonMsgVO.getFileName3())
					.allMatch(Objects::isNull);

			if (allFilesAreNull) {
				return new StatusResponse(HttpStatus.NO_CONTENT, "문자 메세지 이미지 추가에 오류가 발생하여 문자 발송이 취소 되었습니다.");
			}
		}
		return null; // 유효성 검사를 통과한 경우
	}


	/** 
	 * @methodName	: validateReplacementData 
	 * @author		: 이호영
	 * @date		: 2024.09.25
	 * @description	: 치환문자가 사용될 때 데이터가 올바른지 확인하는 메서드
	 * @param mjonMsgVO
	 * @param list
	 * @return boolean
	 */
	/*
	 * public static boolean validateReplacementData(MjonMsgVO mjonMsgVO,
	 * List<MjonMsgSendVO> list) { // 치환 구문과 필드 getter 매핑 Map<String,
	 * Function<MjonMsgSendVO, String>> placeholders = new HashMap<>();
	 * placeholders.put("\\[\\*이름\\*\\]", MjonMsgSendVO::getName);
	 * placeholders.put("\\[\\*1\\*\\]", MjonMsgSendVO::getRep1);
	 * placeholders.put("\\[\\*2\\*\\]", MjonMsgSendVO::getRep2);
	 * placeholders.put("\\[\\*3\\*\\]", MjonMsgSendVO::getRep3);
	 * placeholders.put("\\[\\*4\\*\\]", MjonMsgSendVO::getRep4);
	 * 
	 * // smsTxt 에서 필요한 치환 구문이 포함되어 있는지 확인 String smsTxt = mjonMsgVO.getSmsTxt();
	 * for (Map.Entry<String, Function<MjonMsgSendVO, String>> entry :
	 * placeholders.entrySet()) { if
	 * (Pattern.compile(entry.getKey()).matcher(smsTxt).find()) { // 해당 치환 구문이 존재할
	 * 경우 모든 수신자에서 필드 값 확인 for (MjonMsgSendVO recipient : list) { if
	 * (StringUtils.isEmpty(entry.getValue().apply(recipient))) { return false; //
	 * 데이터가 없는 경우 } } } } // 모든 치환 구문이 유효한 데이터를 가지고 있으면 true 반환 return true; }
	 */
	
	// 배열이 비어있는지 확인하는 메서드
	public static boolean isEmpty(String[] array) {
		return array == null || array.length == 0;
	}

	public static void setPriceforVO(MjonMsgVO mjonMsgVO, List<MjonMsgSendVO> mjonMsgSendVOList, JoinSettingVO sysJoinSetVO,
			MberManageVO mberManageVO) {
		
		// 사용자 단가 정보 설정 (협의 단가가 없을 경우 시스템 단가를 적용)
		float shortPrice = MsgSendUtils.getValidPrice(mberManageVO.getShortPrice(), sysJoinSetVO.getShortPrice());
		float longPrice = MsgSendUtils.getValidPrice(mberManageVO.getLongPrice(), sysJoinSetVO.getLongPrice());
		float picturePrice = MsgSendUtils.getValidPrice(mberManageVO.getPicturePrice(), sysJoinSetVO.getPicturePrice());
		float picture2Price = MsgSendUtils.getValidPrice(mberManageVO.getPicture2Price(), sysJoinSetVO.getPicture2Price());
		float picture3Price = MsgSendUtils.getValidPrice(mberManageVO.getPicture3Price(), sysJoinSetVO.getPicture3Price());

		
		// 각 메시지 타입에 따라 사용자 단가 설정 및 총 단가 계산
		float totalPrice = 0.0f;

		for (MjonMsgSendVO sendVO : mjonMsgSendVOList) {
			String msgType = sendVO.getMsgType();
			String eachPrice;
	
			switch (msgType) {
				case SHORT_MSG_TYPE: // 단문 메시지 타입
					eachPrice = Float.toString(shortPrice);
					break;
		
				case LONG_MSG_TYPE: // 장문 또는 이미지 메시지 타입
					eachPrice = getPicturePrice(sendVO, longPrice, picturePrice, picture2Price, picture3Price);
					break;
		
				default:
					// 기본값이 필요하다면 추가 가능
					throw new IllegalArgumentException("setPriceforVO :: type Exception ");
			}
	
			sendVO.setEachPrice(eachPrice);
//			log.info(" eachPrice :: [{}]", eachPrice);
	
			// 각 가격을 합산
			totalPrice += Float.parseFloat(eachPrice);
		}
		mjonMsgVO.setTotalPrice(totalPrice);
		
	}
	
	
	/** 
	 * @methodName	: setPriceforVO 
	 * @author		: 이호영
	 * @date		: 2024.12.02 
	 * @description	: total금액 구하기
	 * @param mjonMsgSendVOList
	 * @return 
	 */
	public static float setPriceforVO(List<MjonMsgSendVO> mjonMsgSendVOList) {

		float totalPrice = 0.0f;
		for (MjonMsgSendVO sendVO : mjonMsgSendVOList) {
			String priceStr = sendVO.getEachPrice();
			if(StringUtils.isNotEmpty(priceStr)) {
				totalPrice += Float.parseFloat(priceStr);
			}
			
		}
		
		return totalPrice;
		
	}
	

	/**
	 * 이미지 파일 경로를 기준으로 적절한 가격을 반환하는 헬퍼 메소드.
	 */
	private static String getPicturePrice(MjonMsgSendVO sendVO, float longPrice, float picturePrice, float picture2Price, float picture3Price) {
		
		switch (sendVO.getFilePath3() != null ? "FILE3" :
			sendVO.getFilePath2() != null ? "FILE2" :
			sendVO.getFilePath1() != null ? "FILE1" : "NONE") {
		case "FILE3":
			return Float.toString(picture3Price);
		case "FILE2":
			return Float.toString(picture2Price);
		case "FILE1":
			return Float.toString(picturePrice);
		default:
			return Float.toString(longPrice);
		}
		
	}
	
	/** 
	 * @methodName	: getOptimalMsgList 
	 * @author		: 이호영
	 * @date		: 2024.11.26 
	 * @description	: 주어진 이벤트 정보와 메시지 리스트를 기반으로, 잔액 내에서 최적의 메시지 리스트를 생성하고 결과를 반환합니다.
	 *                만약 잔액이 부족하거나 이벤트가 종료된 상태라면, 이벤트 종료 정보를 포함하여 반환합니다.
	 * 
	 * @param eventMberInfo  이벤트 정보 객체 (MjonEventVO). 이벤트 상태 및 잔액 정보를 포함.
	 * @param mjonMsgSendVOList  최적화를 수행할 메시지 리스트 (List<MjonMsgSendVO>).
	 * @return OptimalMsgResultDTO  최적 메시지 리스트와 이벤트 상태 정보를 포함한 DTO 객체.
	 * @throws Exception  메시지 단가 계산 오류 또는 기타 처리 중 발생한 예외를 상위로 전달.
	 */
	public static OptimalMsgResultDTO getOptimalMsgList(
			MjonEventVO eventMberInfo,
			List<MjonMsgSendVO> mjonMsgSendVOList) throws Exception {

		// 최적의 메시지 리스트를 담을 리스트
		List<MjonMsgSendVO> optimalList = new ArrayList<>();

		// 이벤트 정보가 없는 경우
		if (eventMberInfo == null) {
			return OptimalMsgResultDTO.builder()
					.optimalMsgList(optimalList)
					.eventInfo(null)
					.build();
		}

		// 이벤트 잔액과 종료 조건 확인
		double remainCash = eventMberInfo.getEventRemainCash();
		String eventEndDate = eventMberInfo.getEventEndDate();

		if (isEventExpired(remainCash, eventEndDate)) {
			MjonEventVO returnEventMberInfo = terminateEvent(eventMberInfo, remainCash);
			return OptimalMsgResultDTO.builder()
					.optimalMsgList(optimalList)
					.eventInfo(returnEventMberInfo)
					.build();
		}

		// 단가 설정
		float eventShortPrice = Float.parseFloat(eventMberInfo.getEventShortPrice());
		float eventLongPrice = Float.parseFloat(eventMberInfo.getEventLongPrice());
		float eventPicturePrice = Float.parseFloat(eventMberInfo.getEventPicturePrice());
		float eventPicture2Price = Float.parseFloat(eventMberInfo.getEventPicture2Price());
		float eventPicture3Price = Float.parseFloat(eventMberInfo.getEventPicture3Price());

		// 최적의 메시지 리스트 생성
		double sum = 0.0;
		Iterator<MjonMsgSendVO> iterator = mjonMsgSendVOList.iterator();

		while (iterator.hasNext()) {
			MjonMsgSendVO msg = iterator.next();

			// 단가 계산 및 예외 처리
			String eachPrice = getEachPriceOrThrow(msg, eventShortPrice, eventLongPrice, eventPicturePrice, eventPicture2Price, eventPicture3Price);

			float floatEachPrice = Float.parseFloat(eachPrice);

			// 최적의 메시지 리스트에 추가
			if (sum + floatEachPrice <= remainCash) {
				sum += floatEachPrice;
				
				msg.setEachPrice(eachPrice);
				msg.setEventYn("Y");
				
				optimalList.add(msg);
				iterator.remove();
			} else {
				break; // 예산 초과로 추가 불가능
			}
		}

		// 이벤트 잔액 처리하는 매소드 terminateEvent
//		double remainAmt = targetCash - sum; 
//		MjonEventVO returnEventMberInfo = terminateEvent(eventMberInfo, remainAmt);
		return OptimalMsgResultDTO.builder()
				.optimalMsgList(optimalList)
//				.eventInfo(returnEventMberInfo)
				.build();

		// 결과 반환
//		return OptimalMsgResultDTO.builder()
//				.optimalMsgList(optimalList)
//				.eventInfo(null)
//				.build();
	}
	
	/** 
	 * @methodName	: getEachPriceOrThrow 
	 * @author		: 이호영
	 * @date		: 2024.11.26 
	 * @description	: 
	 * @param msg
	 * @param shortPrice
	 * @param longPrice
	 * @param picturePrice
	 * @param picture2Price
	 * @param picture3Price
	 * @return 
	 */
	private static String getEachPriceOrThrow(
			MjonMsgSendVO msg,
			float shortPrice,
			float longPrice,
			float picturePrice,
			float picture2Price,
			float picture3Price) {

		switch (msg.getMsgType()) {
			case SHORT_MSG_TYPE:
				return Float.toString(shortPrice);
			case LONG_MSG_TYPE:
				return getPicturePrice(msg, longPrice, picturePrice, picture2Price, picture3Price);
			default:
				throw new IllegalArgumentException("Unsupported message type: " + msg.getMsgType());
		}
	}

	private static boolean isEventExpired(double targetCash, String eventEndDate) throws Exception {
		return !MJUtil.getCompareDate(eventEndDate) || targetCash < MIN_EVENT_REMAIN_CASH;
	}
	
	/** 
	 * @methodName	: terminateEvent 
	 * @author		: 이호영
	 * @date		: 2024.11.26 
	 * @description	: 이벤트 종료 VO 생성
	 * @param eventMberInfo
	 * @param remainCash
	 * @return 
	 */
	public static MjonEventVO terminateEvent(MjonEventVO eventMberInfo, double remainCash) {
		// TODO Auto-generated method stub

		// 이벤트 상태를 종료로 변경
		MjonEventVO returnEventMberInfo = new MjonEventVO();
		returnEventMberInfo.setEventInfoId(eventMberInfo.getEventInfoId());
		returnEventMberInfo.setMberId(eventMberInfo.getMberId());
		returnEventMberInfo.setEventRemainCash(remainCash);
		returnEventMberInfo.setEventStatus("Y");
		
		if (remainCash < MIN_EVENT_REMAIN_CASH) {
			returnEventMberInfo.setEventStatus("E");
			returnEventMberInfo.setEventMemo("발송 최소 금액("+ MIN_EVENT_REMAIN_CASH +") 부족 혹은 이벤트 종료일 초과되어 이벤트 종료 시킴");	
		}
		return returnEventMberInfo;
	}

	/** 
	 * @methodName	: handleHotlineAgentRate 
	 * @author		: 이호영
	 * @date		: 2024.11.26 
	 * @description	: 전용 전송사 비율이 0이면 대표 전송사로 수정
	 * @param userInfo
	 * @param tmp
	 * @param hotlineAgentCode
	 * @return 
	 */
	public static String handleHotlineAgentRate(MberManageVO userInfo, MjonMsgAgentStsVO tmp, String hotlineAgentCode) {
		String sendRate = tmp.getSendRate();
		String repAgent = tmp.getRepAgent();
		String useYn = tmp.getUseYn();

		// 블라인드 코드가 'N'인 경우 전송 비율 및 사용 가능 여부 확인
		if ("N".equals(userInfo.getBlineCode())) {
			// 전송 비율이 0이거나 사용 불가인 경우 대표 전송사로 변경
			if ("0".equals(sendRate) || "N".equals(useYn)) {
				hotlineAgentCode = repAgent;
			}
		}

		return hotlineAgentCode; // 변경된 hotlineAgentCode 반환
	}


	/** 
	 * @methodName	: initializeAgentSendCounts 
	 * @author		: 이호영
	 * @date		: 2024.11.26 
	 * @description	: 전송사별 코드로 map 초기화 하기
	 * @return 
	 */
	public static Map<String, Integer> initializeAgentSendCounts() {
		Map<String, Integer> agentSendCounts = new HashMap<>();
		agentSendCounts.put("01", 0); // 아이하트
		agentSendCounts.put("02", 0); // 현대 퓨쳐넷
		agentSendCounts.put("03", 0); // 아이엠오
		agentSendCounts.put("04", 0); // 비즈뿌리오
		agentSendCounts.put("05", 0); // 제이제이
		agentSendCounts.put("07", 0); // 인비토
		return agentSendCounts;
	}


	/** 
	 * @methodName	: calculateSendCounts 
	 * @author		: 이호영
	 * @date		: 2024.11.26 
	 * @description	: agentSendCounts에 있는 에이젼트 map에 전송사 비율에 따라 카운트 put 하기
	 * @param agentSendCounts
	 * @param sendRate
	 * @param finalSize 
	 */
	public static void calculateSendCounts(Map<String, Integer> agentSendCounts, MjonMsgVO sendRate, int finalSize) {
		String agentCode = sendRate.getAgentCode();
		float sendRatePercentage = Float.parseFloat(sendRate.getSendRate());

		int sendCount = Math.round(finalSize * sendRatePercentage);

		if (agentSendCounts.containsKey(agentCode)) {
			agentSendCounts.put(agentCode, sendCount);
		}
	}

	/** 
	 * @methodName	: assignAgentBasedOnCount 
	 * @author		: 이호영
	 * @date		: 2024.11.26 
	 * @description	: initializeAgentSendCounts()에서 만든 map으로 발송 agent 데이터 Set
	 * @param agentSendCounts
	 * @param sendRateList
	 * @return 
	 */
	public static String assignAgentBasedOnCount(Map<String, Integer> agentSendCounts, List<MjonMsgVO> sendRateList) {
		// 전송사 코드 순서대로 확인 :: initializeAgentSendCounts 메소드와 맞춰야함
		List<String> agentCodes = Arrays.asList("01", "02", "03", "04", "05", "07");

		// 전송 가능한 코드 찾기
		for (String agentCode : agentCodes) {
			if (agentSendCounts.get(agentCode) > 0) {
				agentSendCounts.put(agentCode, agentSendCounts.get(agentCode) - 1);
				return agentCode;
			}
		}

		// 모든 코드의 전송 가능 횟수가 0이면 대표 전송사로 할당
		return sendRateList.get(0).getRepAgent();
	}

	/** 
	 * @methodName	: getMmsgSubject 
	 * @author		: 이호영
	 * @date		: 2024.12.02 
	 * @description	: 타이틀 생성 
	 * @param smsTxt
	 * @param msgKind
	 * @return 
	 */
	public static String getMmsgSubject(String smsTxt, String msgKind) {
		
		String mmsTitleTemp = "";
		if(StringUtils.isEmpty(smsTxt)) {
			return mmsTitleTemp;
		}
		// SMS 텍스트를 줄 단위로 나누기
		String[] split = smsTxt.split("\n");

		if (split.length > 0) {
			// "C" 메시지 종류인 경우 두 번째 줄, 그렇지 않으면 첫 번째 줄을 제목으로 설정
			mmsTitleTemp = "C".equals(msgKind) && split.length > 1 ? split[1].trim() : split[0].trim();

			// 제목 글자 수를 20자로 제한 (초과 시 잘라냄)
			mmsTitleTemp = mmsTitleTemp.length() > 20 ? mmsTitleTemp.substring(0, 20) : mmsTitleTemp;
		}
		return mmsTitleTemp;
	}

	/** 
	 * @methodName	: eventYN 
	 * @author		: 이호영
	 * @date		: 2025.02.10 
	 * @description	: 이벤트 확인 
	 * @param eventMberInfo
	 * @return 
	 */
	public static Boolean isNotEvent(MjonEventVO eventMberInfo) {
		

		if (eventMberInfo == null 
				|| "E".equals(eventMberInfo.getEventStatus())
				|| org.apache.commons.lang3.StringUtils.isEmpty(eventMberInfo.getEventEndDate()) 
				) {
			return true;
		}
		return false;
	}





}
