package com.madeu.push;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.madeu.util.CommonConst;
import jakarta.servlet.http.HttpServlet;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
public class FCMProvider2 extends HttpServlet {
	// 여러 명(최대 500명)에게 보내기 위해 String pushKey -> List<String> pushKeys 변환

	private static final long serialVersionUID = 1L;
	
	// 환경설정 파일 구성
	private static String FCM_GOOGLE_CREDENTIAL_FILE_URL ="";//FCM 사이트 요청
	private static String FCM_GOOGLE_CREDENTIAL_FILE_URL_TYPE; //
	private static String FCM_PROJECT_ID = "";//FCM 사이트 요청
	private static String FCM_PROJECT_ID_TYPE;

	@Value("${fcm.google-credential-file-url-type}")
	public void setFcmGoogleCredentialFileUrlType(String value) {
		FCM_GOOGLE_CREDENTIAL_FILE_URL_TYPE = value;
	}

	@Value("${fcm.project-id-type}")
	public void setFcmProjectIdType(String value) {
		FCM_PROJECT_ID_TYPE = value;
	}


	private static String FCM_BASE_URL = "https://fcm.googleapis.com";
	private static String FCM_SEND_ENDPOINT = "/v1/projects/" + FCM_PROJECT_ID + "/messages:send";
	private static String FCM_MESSAGE_KEY = "message";
	
	//private static String HOST = "http://android.apis.google.com/c2dm/send";
    //private static String AUTH = "DQAAAO8AAAA1ytC3f4wb2e1zndam4UDEco9UP9XjLhetZ6YAxDbgvH8MKhAG7_GpQk8sKZLuTchzQvDJR4wtK0FZc0CzCH5dKJVcTmJTZma6RbdgH2JTPtofy5sGPUYta_364ROKASaUEhoi2G5ohJEdPm4zL1EcJPmroCxxOLl9JTV4tq1b_rJzGjVNwgw1SIXavyTofFQZqUOCGT1reGy7Mao4yAHAF-DI_4jp_UCM4E1yVjI-itw-3utPETdcEBqcIpeStctH7IZRMu7eopXsMCW2cJgLCxZWMHVod9Ot93P-lEJwq_U40PwE2-ONSUQ1coHRh50";
	
	public void providerMulti(List<Map<String, Object>> userList, String title, String content, HashMap<String, Object> keyParam) throws Exception{
		log.debug("FCMProvider - providerMulti start");

		FCM_GOOGLE_CREDENTIAL_FILE_URL 	= FCM_GOOGLE_CREDENTIAL_FILE_URL_TYPE;
		FCM_PROJECT_ID 					= FCM_PROJECT_ID_TYPE;
		FCM_SEND_ENDPOINT 				= "/v1/projects/" + FCM_PROJECT_ID + "/messages:send";

		sendMulticastFcm(userList, title, content,1, keyParam);
		
		log.debug("FCMProvider - providerMulti end");
	}
	
	public static void sendMulticastFcm(List<Map<String, Object>> userList, String title, String content, int badge, HashMap<String, Object> keyParam){

		FCM_GOOGLE_CREDENTIAL_FILE_URL 	= FCM_GOOGLE_CREDENTIAL_FILE_URL_TYPE;
		FCM_PROJECT_ID 					= FCM_PROJECT_ID_TYPE;
		FCM_SEND_ENDPOINT 				= "/v1/projects/" + FCM_PROJECT_ID + "/messages:send";

		log.debug("sendMulticastFcm START");

		try {
			// 기기 타입별로 토큰 그룹화
            Map<String, List<String>> deviceGroups = userList.stream()
                .collect(Collectors.groupingBy(
                    user -> String.valueOf(user.get("phoneType")),
                    Collectors.mapping(
                        user -> String.valueOf(user.get("pushKey")),
                        Collectors.toList()
                    )
                ));


            // Android 사용자들에게 전송
            if (deviceGroups.containsKey("A")) {
                log.debug("Sending to Android devices: " + deviceGroups.get("A").size() + " devices");
                sendTypeSpecificMessage("A", deviceGroups.get("A"), title, content, badge, keyParam);
            }

            // iOS 사용자들에게 전송
            if (deviceGroups.containsKey("I")) {
                log.debug("Sending to iOS devices: " + deviceGroups.get("I").size() + " devices");
                sendTypeSpecificMessage("I", deviceGroups.get("I"), title, content, badge, keyParam);
            }

			/*//GOOGLE FCM GitHub 참조
			//https://github.com/firebase/quickstart-java/blob/0dec937756dc72c7350e782f73f79a63233bb9a2/messaging/src/main/java/com/google/firebase/quickstart/Messaging.java#L62-L66
			JsonObject notificationMessage = buildNotificationMessage(userList, builder, dataJson);
			prettyPrint(notificationMessage);

			HttpURLConnection connection = getConnection();
			connection.setDoOutput(true);
			DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
			outputStream.write(notificationMessage.toString().getBytes("UTF-8"));
			
			outputStream.flush();
			outputStream.close();
			
			int responseCode = connection.getResponseCode();
			log.debug("responseCode : " + responseCode);
			// 성공, 실패 시 메시지 확인 및 처리 
			if(responseCode == 200) {
				
				String response = inputstreamToString(connection.getInputStream());
				log.debug("Message sent to Firebase for delivery, response:"+response);
			
			}else{
				//여긴 에러 디비에 N
				String responseError = inputstreamToString(connection.getErrorStream());
				log.debug("responseError : " +responseError);
					    	
				JSONParser parser = new JSONParser();
				Object obj = parser.parse(responseError);
				JSONObject jsonObj = (JSONObject) obj;
				log.debug("jsonObj : " +jsonObj);
				
				JSONObject jsonObjItem = (JSONObject)jsonObj.get("error");
				log.debug("jsonObjItem : " +jsonObjItem);
				
				int errorCode = Integer.parseInt(String.valueOf(jsonObjItem.get("code")));
				log.debug("error Code : " +errorCode);
				
				String errorStatus = (String)jsonObjItem.get("status");
				log.debug("error Status : " +errorStatus);
				
				String message = String.valueOf(jsonObjItem.get("message"));
				log.debug("error message : " +message);
				

			 	String response = inputstreamToString(connection.getErrorStream());
				log.debug("Unable to send message to Firebase:"+response);

			}*/
		
		}catch (Exception e) {
		
			log.debug("FCMS Send Fail...");
			log.debug("Status Code       :"+CommonConst.E_FCM_SEND);
			log.debug("Exception Message :"+e.getMessage());
			//log.debug(String.format("CODE : %d | MESSAGE : %s | DETAIL : ",CommonConst.E_FCM_SEND,e.getMessage()),e);
		} 
		
		log.debug("sendMulticastFcm END");
	}

    private static void sendTypeSpecificMessage(String phoneType, List<String> tokens, String title, String content, int badge, HashMap<String, Object> keyParam) throws Exception {

		JsonArray messages = new JsonArray();

//		for(String token : tokens){
			// 메시지 생성
			JsonObject message = new JsonObject();

			// 기본 메시지 데이터 생성
			JsonObject dataJson = createDataJson(title, content, badge, keyParam);
			JsonObject notification = createNotification(title, content);

			// condition을 사용하여 여러 토큰을 처리
			StringBuilder condition = new StringBuilder();
			for (int i = 0; i < tokens.size(); i++) {
				if (i > 0) {
					condition.append(" || ");
				}
				condition.append("'").append(tokens.get(i)).append("' in topics");
			}

			// condition 설정
			message.addProperty("condition", condition.toString());

			message.addProperty("token", tokens.get(0));
			message.add("data", dataJson);

			// 기기 타입별 설정 추가
			if ("A".equals(phoneType)) {
				JsonObject androidConfig = new JsonObject();
				androidConfig.addProperty("priority", "high");
				androidConfig.addProperty("direct_boot_ok", true);

				JsonObject androidNotification = new JsonObject();
				androidNotification.addProperty("sound", "default");
				androidNotification.addProperty("click_action", "FLUTTER_NOTIFICATION_CLICK");

				androidConfig.add("notification", androidNotification);
				message.add("android", androidConfig);

			} else if ("I".equals(phoneType)) {
				JsonObject apnsConfig = new JsonObject();
				JsonObject aps = new JsonObject();
				aps.addProperty("badge", 1);
				aps.addProperty("sound", "default");
				aps.addProperty("content-available", 1);

				JsonObject payload = new JsonObject();
				payload.add("aps", aps);

				apnsConfig.add("payload", payload);
				message.add("apns", apnsConfig);
				message.add("notification", notification);
			}

			messages.add(message);
//		}


        // 최종 요청 메시지 구성
        JsonObject requestJson = new JsonObject();
        requestJson.add(FCM_MESSAGE_KEY, messages);

        // FCM 전송
        sendFcmRequest(requestJson);
    }

    private static JsonObject createDataJson(String title, String content, int badge, HashMap<String, Object> keyParam) {
        JsonObject dataJson = new JsonObject();
        dataJson.addProperty("title", title);
        dataJson.addProperty("body", content);
        dataJson.addProperty("badge", String.valueOf(badge));
        dataJson.addProperty("sound", "default");

        if (keyParam != null) {
            for (String key : keyParam.keySet()) {
                dataJson.addProperty(key, String.valueOf(keyParam.get(key)));
            }
        }

        return dataJson;
    }

    private static JsonObject createNotification(String title, String content) {
        JsonObject notification = new JsonObject();
        notification.addProperty("title", title);
        notification.addProperty("body", content);
        return notification;
    }

    private static void addAndroidConfig(JsonObject message) {
        JsonObject androidConfig = new JsonObject();
        androidConfig.addProperty("priority", "high");
        androidConfig.addProperty("direct_boot_ok", true);

        JsonObject notification = new JsonObject();
        notification.addProperty("sound", "default");
        notification.addProperty("click_action", "FLUTTER_NOTIFICATION_CLICK");

        androidConfig.add("notification", notification);
        message.add("android", androidConfig);
    }

    private static void addIosConfig(JsonObject message, JsonObject notification) {
        JsonObject apnsConfig = new JsonObject();
        JsonObject aps = new JsonObject();
        aps.addProperty("badge", 1);
        aps.addProperty("sound", "default");
        aps.addProperty("content-available", 1);

        JsonObject payload = new JsonObject();
        payload.add("aps", aps);

        apnsConfig.add("payload", payload);
        message.add("apns", apnsConfig);
        message.add("notification", notification);
    }

    private static void sendFcmRequest(JsonObject requestJson) throws Exception {
        prettyPrint(requestJson);

        HttpURLConnection connection = getConnection();
        connection.setDoOutput(true);

        try {
			DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
			outputStream.write(requestJson.toString().getBytes("UTF-8"));

			outputStream.flush();
			outputStream.close();

			int responseCode = connection.getResponseCode();
			log.debug("responseCode : " + responseCode);
			// 성공, 실패 시 메시지 확인 및 처리
			if(responseCode == 200) {

				String response = inputstreamToString(connection.getInputStream());
				log.debug("Message sent to Firebase for delivery, response:"+response);

			}else{
				//여긴 에러 디비에 N
				String responseError = inputstreamToString(connection.getErrorStream());
				log.debug("responseError : " +responseError);

				JSONParser parser = new JSONParser();
				Object obj = parser.parse(responseError);
				JSONObject jsonObj = (JSONObject) obj;
				log.debug("jsonObj : " +jsonObj);

				JSONObject jsonObjItem = (JSONObject)jsonObj.get("error");
				log.debug("jsonObjItem : " +jsonObjItem);

				int errorCode = Integer.parseInt(String.valueOf(jsonObjItem.get("code")));
				log.debug("error Code : " +errorCode);

				String errorStatus = (String)jsonObjItem.get("status");
				log.debug("error Status : " +errorStatus);

				String message = String.valueOf(jsonObjItem.get("message"));
				log.debug("error message : " +message);

				/*
			 	String response = inputstreamToString(connection.getErrorStream());
				log.debug("Unable to send message to Firebase:"+response);
				*/
			}
        }catch (Exception e) {

			log.debug("FCMS Send Fail...");
			log.debug("Status Code       :"+CommonConst.E_FCM_SEND);
			log.debug("Exception Message :"+e.getMessage());
			//log.debug(String.format("CODE : %d | MESSAGE : %s | DETAIL : ",CommonConst.E_FCM_SEND,e.getMessage()),e);
		}

		log.debug("sendFCM END");
    }

    private static void handleErrorResponse(HttpURLConnection connection) throws Exception {
        String responseError = inputstreamToString(connection.getErrorStream());
        log.error("Failed to send message: " + responseError);

        JSONParser parser = new JSONParser();
        JSONObject jsonObj = (JSONObject) parser.parse(responseError);
        JSONObject error = (JSONObject) jsonObj.get("error");

        log.error("Error Code: " + error.get("code"));
        log.error("Error Status: " + error.get("status"));
        log.error("Error Message: " + error.get("message"));
    }

	/**
	 * Create HttpURLConnection that can be used for both retrieving and publishing.
	 *
	 * @return Base HttpURLConnection.
	 * @throws IOException
	 */
	private static HttpURLConnection getConnection() throws IOException {

		log.debug("FCM_GOOGLE_CREDENTIAL_FILE_URL : "+FCM_GOOGLE_CREDENTIAL_FILE_URL);
		log.debug("FCM_PROJECT_ID                 : "+FCM_PROJECT_ID);
		log.debug("FCM_SEND_ENDPOINT              : "+FCM_SEND_ENDPOINT);

		// [START use_access_token]
		URL url = new URL(FCM_BASE_URL + FCM_SEND_ENDPOINT);
		HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
		
		String getAccessToken = getAccessToken();

		log.debug("getAccessToken  : " + getAccessToken);
		
		httpURLConnection.setRequestProperty("Authorization", "Bearer " + getAccessToken);
		httpURLConnection.setRequestProperty("Content-Type", "application/json; UTF-8");
		return httpURLConnection;
		// [END use_access_token]
	}
	
	/**
	 * Retrieve a valid access token that can be use to authorize requests to the FCM REST
	 * API.
	 *
	 * @return Access token.
	 * @throws IOException
	 */
	// [START retrieve_access_token]
	private static String getAccessToken() throws IOException {
		GoogleCredential googleCredential = GoogleCredential.fromStream(new FileInputStream(FCM_GOOGLE_CREDENTIAL_FILE_URL))
			.createScoped(Arrays.asList("https://www.googleapis.com/auth/firebase", 
					"https://www.googleapis.com/auth/cloud-platform", 
					"https://www.googleapis.com/auth/firebase.readonly"));
		googleCredential.refreshToken();
		return googleCredential.getAccessToken();
	}
	// [END retrieve_access_token]

	/**
	 * Read contents of InputStream into String.
	 *
	 * @param inputStream InputStream to read.
	 * @return String containing contents of InputStream.
	 * @throws IOException
	 */
	private static String inputstreamToString(InputStream inputStream) throws IOException {
		StringBuilder stringBuilder = new StringBuilder();
		Scanner scanner = new Scanner(inputStream);
		while (scanner.hasNext()) {
			stringBuilder.append(scanner.nextLine());
		}
		return stringBuilder.toString();
	}
	
	/**
	 * Construct the body of a notification message request.
	 *
	 * @return JSON of notification message.
	 */
	private static JsonObject buildNotificationMessage(List<Map<String, Object>> userList, JsonObject msg , JsonObject dataJson) {
		//log.debug("msg      :"+msg);
		//log.debug("dataJson :"+dataJson);

		// 여러 개 발송을 위해 array 생성
		JsonArray messages = new JsonArray();

		int userListSize = userList.size();
		if(0<userListSize){
			for(int i=0; i<userListSize; i++){
				String pushKey   = String.valueOf(userList.get(i).get("pushKey"));
				String phoneType = String.valueOf(userList.get(i).get("phoneType"));

				JsonObject jMessage = new JsonObject();
				jMessage.add("data",dataJson);
				jMessage.addProperty("token", pushKey);

				if(("A").equals(phoneType)) {
					// android Json
					JsonObject androidJson = new JsonObject();
					androidJson.addProperty("sound", "default");
					androidJson.addProperty("click_action", "");

					JsonObject androidMessage = new JsonObject();
					//androidMessage.add("notification", androidJson);
					androidMessage.addProperty("direct_boot_ok", true);
					jMessage.add("android",androidMessage);

				}else if(("I").equals(phoneType)) {
					// ios Json
					JsonObject iosDetailJson = new JsonObject();
					iosDetailJson.addProperty("badge", 1);
					iosDetailJson.addProperty("sound", "default");

					JsonObject iosJson = new JsonObject();
					iosJson.add("aps", iosDetailJson);

					JsonObject iosMessage = new JsonObject();
					iosMessage.add("payload", iosJson);
					jMessage.add("apns",iosMessage);

					jMessage.add("notification", msg);
				}

				messages.add(jMessage);
			}
		}

		JsonObject jFcm = new JsonObject();
		jFcm.add(FCM_MESSAGE_KEY, messages);

		return jFcm;
	}
	
	/**
	 * Pretty print a JsonObject.
	 *
	 * @param jsonObject JsonObject to pretty print.
	 */
	private static void prettyPrint(JsonObject jsonObject) {
		Gson gson = new GsonBuilder().setPrettyPrinting().create();
		log.debug(gson.toJson(jsonObject));
	}
}