package com.caliverse.admin.global.common.utils;

import java.nio.charset.StandardCharsets;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.*;

import com.caliverse.admin.domain.entity.DiffStatus;
import com.caliverse.admin.global.common.code.CommonCode;
import com.caliverse.admin.global.common.constants.CommonConstants;
import com.caliverse.admin.global.common.exception.RestApiException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import com.caliverse.admin.domain.dao.admin.AdminMapper;
import com.caliverse.admin.domain.entity.Admin;
import com.caliverse.admin.global.common.code.ErrorCode;

import lombok.RequiredArgsConstructor;

import com.google.protobuf.ByteString;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Component
@RequiredArgsConstructor
public class CommonUtils {
    private static final Logger logger = LoggerFactory.getLogger(CommonUtils.class);
    private static UserDetails userDetails;
    private static AdminMapper adminMapper;

    public static Admin getAdmin(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() instanceof UserDetails) {
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            return (Admin) userDetails;
        }
        return null;
    }

    public static String getClientIp() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            String ip = request.getHeader("X-Forwarded-For");
            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
            if ("0:0:0:0:0:0:0:1".equals(ip)) {
                return "127.0.0.1";
            }
            return ip;
        }
        return null;
    }

    public static String getCreateGuId() {
        return UUID.randomUUID().toString();
    }

    public static String objectToString(Object object){
        if (object == null) {
            return "";
        } else if (object instanceof Integer) {
            return Integer.toString((Integer) object);
        } else if (object instanceof Boolean) {
            return Boolean.toString((boolean)object);
        } else {
            return object.toString();
        }
    }

    public static Integer objectToInteger(Object object){
        if (object == null) {
            return 0;
        }

        if (object instanceof Integer) {
            return (Integer) object;
        }

        if (object instanceof Number) {
            return ((Number) object).intValue();
        }

        if (object instanceof Boolean) {
            return ((Boolean) object) ? 1 : 0;
        }

        try {
            // String이나 다른 타입의 경우 문자열로 변환 후 처리
            String strValue = String.valueOf(object).trim();
            if (strValue.isEmpty()) {
                return 0;
            }

            // 소수점이 있는 숫자 문자열 처리
            if (strValue.contains(".")) {
                return (int) Double.parseDouble(strValue);
            }

            return Integer.parseInt(strValue);
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    public static Double objectToDouble(Object object) {
        if (object == null) {
            return 0.0;
        }

        if (object instanceof Double) {
            return (Double) object;
        }

        if (object instanceof Number) {
            return ((Number) object).doubleValue();
        }

        if (object instanceof Boolean) {
            return ((Boolean) object) ? 1.0 : 0.0;
        }

        try {
            // String이나 다른 타입의 경우 문자열로 변환 후 처리
            String strValue = String.valueOf(object).trim();
            if (strValue.isEmpty()) {
                return 0.0;
            }

            return Double.parseDouble(strValue);
        } catch (NumberFormatException e) {
            return 0.0;
        }
    }

    public static Long objectToLong(Object object) {
        if (object == null) {
            return 0L;
        } else if (object instanceof Long) {
            return (Long) object;
        } else if (object instanceof Boolean) {
            return (Boolean) object ? 1L : 0L;
        } else if (object instanceof String) {
            try {
                return Long.parseLong((String) object);
            } catch (NumberFormatException e) {
                return 0L;
            }
        } else if (object instanceof Integer) {
            return ((Integer) object).longValue();
        } else {
            return 0L;
        }
    }

    public static int[] objectToIntArray(Object object){
        if (object == null) {
            return new int[0];
        } else if (object instanceof List) {
            List<Integer> list = (List<Integer>) object;
            int[] intArray = list.stream().mapToInt(i -> i).toArray();
            return intArray;
        } else {
            return new int[0];
        }
    }

    public static String[] objectToStringArray(Object object){
        if (object == null) {
            return new String[0];
        } else if (object instanceof List) {
            List<String> list = (List<String>) object;
            String[] stringArray = list.toArray(new String[0]);
            return stringArray;
        } else {
            return new String[0];
        }
    }
    
    public static Map<String, String> pageSetting(Map<String, String> map){
        if(map.get("page_no") != null && map.get("page_size")!=null){
            int pageNo = Integer.parseInt(map.get("page_no"));
            int pageSize =  Integer.parseInt(map.get("page_size"));

            int offset = (pageNo - 1) * pageSize;

            map.put("pageSize", Integer.valueOf(pageSize).toString());
            map.put("offset", Integer.valueOf(offset).toString());
        }
        return map;
    }

    public static String setFileName(String fileName){
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
        String timestamp = now.format(formatter);

        // 파일 확장자 추출
        String fileExtension = fileName.substring(fileName.lastIndexOf("."));

        String newFileName = fileName.replace(fileExtension, "_" + timestamp + fileExtension);
        return newFileName;
    }

    public static LocalDateTime stringToDateTime(String str){
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
        return LocalDateTime.parse(str, formatter);
    }

    public static String convertIsoByDatetime(String isoDate){
        Instant instant = Instant.parse(isoDate);
        LocalDateTime localDateTime = instant.atZone(ZoneOffset.UTC).toLocalDateTime();

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return localDateTime.format(formatter);
    }

    public static List<LocalDate> dateRange(String startDt, String endDt){
        // 시작 날짜와 종료 날짜를 LocalDate로 변환
        LocalDate startDate = LocalDate.parse(startDt);
        LocalDate endDate = LocalDate.parse(endDt);
        // 날짜 범위를 얻기 위한 리스트 생성
        List<LocalDate> dateRange = new ArrayList<>();

        // 시작 날짜부터 종료 날짜까지 날짜를 리스트에 추가
        LocalDate currentDate = startDate;
        while (!currentDate.isAfter(endDate)) {
            dateRange.add(currentDate);
            currentDate = currentDate.plusDays(1);
        }
        return dateRange;
    }

    /**
     * input : 2023-09-03
     * output: "2023-09-03 00:00:00"
     */
    public static String startOfDay(LocalDate localDate){
        LocalDateTime startOfDay = localDate.atStartOfDay();
        String formattedDateTime = startOfDay.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        return formattedDateTime;
    }
    /**
     * input : 2023-09-03
     * output: "2023-09-03 23:59:59"
     */
    public static String endOfDay(LocalDate localDate){
        LocalDateTime endOfDay = localDate.atTime(LocalTime.MAX);
        String formattedDateTime = endOfDay.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        return formattedDateTime;
    }

    public static int calculateMonths(LocalDateTime startDt, LocalDateTime endDt) {
        int startYear = startDt.getYear();
        int endYear = endDt.getYear();
        int startMonth = startDt.getMonthValue();
        int endMonth = endDt.getMonthValue();

        return (endYear - startYear) * 12 + (endMonth - startMonth) + 1;
    }

    public static ErrorCode isValidNickname(String nickname){
        // 첫번째 글자가 숫자인지 체크 한다.
        if (Pattern.matches("[0-9]", Character.toString(nickname.charAt(0)))) {
            return ErrorCode.NICKNAME_NUMBER_ERROR;
        }

        // 특수문자가 포함되어 있는지 체크 한다.
        if (isContainSpecialChars(nickname)) {
            return ErrorCode.NICKNAME_SPECIALCHAR_ERROR;
        }

        // 길이 체크
        if(nickname.length() < 2 || nickname.length() > 12){
            return ErrorCode.NICKNAME_LANGTH_ERROR;
        }

        // 금지어 여부 체크 추후

        return ErrorCode.SUCCESS;
    }

    public static boolean isContainSpecialChars(String values) {
        Pattern pattern = Pattern.compile("[^a-zA-Z0-9ㄱ-ㅎ가-힣\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f]");
        Matcher matcher = pattern.matcher(values);

        return matcher.find();
    }

    public static Map<String, Object> stringByObject(String string) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.readValue(string, new TypeReference<>() {});
        } catch (JsonProcessingException e) {
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.DYNAMODB_CONNECTION_ERROR.getMessage());
        }
    }

    public static Integer stringToInt(String string) {
        try{
            return Integer.parseInt(string);
        }catch (NumberFormatException e){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.SENDMAIL_ERROR.getMessage());
        }
    }

    public static long intervalToMillis(String interval) {
        String[] parts = interval.split(":");
        long hour = Long.parseLong(parts[0]);
        long minute = Long.parseLong(parts[1]);
        long second = Long.parseLong(parts[2]);

        return TimeUnit.HOURS.toMillis(hour) + TimeUnit.MINUTES.toMillis(minute) + TimeUnit.SECONDS.toMillis(second);
    }

    public static ByteString stringEncoding(String text){
        return ByteString.copyFrom(text.getBytes(StandardCharsets.UTF_8));
    }
    public static String stringToByte(String text){
        return Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
    }

    public static String convertUTCDate(LocalDateTime date){
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(CommonConstants.FORMAT_DATE_ISO_DATETIME_MILLIS);
        return date.atOffset(ZoneOffset.UTC).format(formatter);
    }

    public static LocalTime stringToTime(String time){
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
        return LocalTime.parse(time, formatter);
    }

    public static String getDiffStatus(int cnt, int pre_cnt){
        return cnt - pre_cnt > 0 ? DiffStatus.UP.getStatus() :
                cnt - pre_cnt < 0 ? DiffStatus.DOWN.getStatus() :
                        DiffStatus.EQUAL.getStatus();
    }

    public static String getDiffRate(int cnt, int pre_cnt){
        // 이전 값이 0인 경우
        if (pre_cnt == 0) {
            if (cnt == 0) {
                return "0";
            }
            return String.valueOf(cnt * 100);
        }

        double changeRate = Math.abs((double) (cnt - pre_cnt) / pre_cnt * 100);
        return String.valueOf((int) Math.round(changeRate));
    }

    public static int getLengthOfLastMonth(LocalDate date){
        LocalDate localDate = date.minusMonths(1);
        return localDate.lengthOfMonth();
    }
}
