package com.icomsys.main_vm.biz.common.login.service;

import com.google.gson.Gson;
import com.icomsys.main_vm.biz.advice.excep.CustomBadRequestException;
import com.icomsys.main_vm.biz.advice.excep.CustomNotFoundException;
import com.icomsys.main_vm.biz.common.common.service.LogService;
import com.icomsys.main_vm.biz.common.common.service.LogVO;
import com.icomsys.main_vm.biz.common.login.CinnamonToken;
import com.icomsys.main_vm.biz.common.login.TokenProvider;
import com.icomsys.main_vm.biz.common.login.req.LoginReq;
import com.icomsys.main_vm.biz.common.login.req.MainOprReq;
import com.icomsys.main_vm.biz.common.login.req.PwdUpdateReq;
import com.icomsys.main_vm.biz.common.login.res.LoginCheckReq;
import com.icomsys.main_vm.biz.common.login.res.OprmngCodeRes;
import com.icomsys.main_vm.biz.common.login.res.ServiceGroupRes;
import com.icomsys.main_vm.biz.common.login.res.UserVo;
import com.icomsys.main_vm.common.code.EnumToMap;
import com.icomsys.main_vm.common.code.MenuVo;
import com.icomsys.main_vm.common.code.enumresource.*;
import com.icomsys.main_vm.common.util.ClientIp;
import com.icomsys.main_vm.db.jpa.entity.conversation.TbIcsLog;
import com.icomsys.main_vm.db.jpa.entity.system.TbBotUser;
import com.icomsys.main_vm.db.jpa.repo.system.*;
import com.icomsys.main_vm.db.mybatis.alias.LoginVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

@Service
@Slf4j
@RequiredArgsConstructor
/**
 * LoginVo는 천천히 거둬낼것
 * UserVo로 변환
 */
public class LoginService {

    private final TokenProvider tokenProvider;

    private final AuthenticationManagerBuilder authenticationManagerBuilder;
    private final AuthenticationManager authenticationManager;
    private final PasswordEncoder passwordEncoder;
    private final HttpServletRequest httpServletRequest;
    private final HttpServletResponse httpServletResponse;
    private final MessageSource messageSource;
    private final TbBotUserRepo tbBotUserRepo;
    private final TbUserAuthGroupRepo tbUserAuthGroupRepo;
    private final TbServiceGrouopRepo tbServiceGrouopRepo;
    private final TbUserCustRepo tbUserCustRepo;
//    private final LogService logService;

    private final TbIcsLogRepo tbIcsLogRepo;

    private final ClientIp clientIp;

    //LoginVo의 경우 Egov 레거시매퍼의존성이 잡혀있어. req에 분리 불가능
    @Transactional
    public String LoginValidService(LoginReq dto, ModelMap model) {
        //기본 리턴은 로그인페이지
        String result = "adm/signin/signin";
        TbBotUser userSelect = null;

        TbBotUser user = tbBotUserRepo.findByUserIdAndUseYn(dto.getUserId(), "Y")
                .orElseThrow(() -> new UsernameNotFoundException("로그인 실패"));

        //전자정부
//        if (tbBotUserRepo.existsByUserIdAndPasswordAndUseFlagAndSolutionCode(dto.getUserId(), EgovFileScrty.encryptPassword(dto.getPassword(), dto.getUserId()), "Y", "vm")) {
//            userSelect = tbBotUserRepo.findByUserIdAndPasswordAndUseFlagAndSolutionCode(
//                    dto.getUserId(), EgovFileScrty.encryptPassword(dto.getPassword(), dto.getUserId()), "Y", "vm"
//            ).get();

        //스프링
        if (dto.ofValid(passwordEncoder, user.getPassword())) {
            Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(dto.getUserId(), dto.getPassword()));
            SecurityContextHolder.getContext().setAuthentication(authentication);
            SecurityContext securityContext = SecurityContextHolder.getContext();
            securityContext.setAuthentication(authentication);

            sessionSetting(user);

            LoginVO userResult = user.toLoginVO();
            return "forward:/adm/main/actionMain.do";
        } else {
            log.info("Fail!");
        }
        return result;
    }

    private void sessionSetting(TbBotUser tbu) {
        HttpSession session = httpServletRequest.getSession();
        session.setAttribute(SessionResource.LoginVO.getName(), tbu.toLoginVO());
        session.setAttribute(SessionResource.PolicyList.getName(), tbUserAuthGroupRepo.userPolicyListSelect(tbu.getUserSeq(), tbu.getLastUseServiceGroup()));
        session.setAttribute(SessionResource.UserVO.getName(), tbu.toUserVO());
        session.setMaxInactiveInterval(60 * 60);
//        session.setMaxInactiveInterval(30);

    }

    @Transactional
    public CinnamonToken tokenLogin(LoginCheckReq dto){
        // 1. Login ID/PW 를 기반으로 Authentication 객체 생성
        // 이때 authentication 는 인증 여부를 확인하는 authenticated 값이 false
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(dto.getUserId(), dto.getPassword());

        // 2. 실제 검증 (사용자 비밀번호 체크)이 이루어지는 부분
        // authenticate 매서드가 실행될 때 CustomUserDetailsService 에서 만든 loadUserByUsername 메서드가 실행
        try {
            Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);

            // 3. 인증 정보를 기반으로 JWT 토큰 생성
            CinnamonToken tokenInfo = tokenProvider.generateToken(authentication);
            return tokenInfo;
        } catch ( AuthenticationException e) {
            log.info(e.getMessage());
            throw e;
        }
    }


    public String actionMain(ModelMap model) {

        log.info("action main init ");

//        MenuManageVO menuManageVO = new MenuManageVO();
        List<MenuVo> menuVos = new ArrayList<>();
        String url = "";
//        LoginVO loginVO = (LoginVO) httpServletRequest.getSession().getAttribute(SessionResource.LoginVO.getName());
        UserVo userVo = (UserVo) httpServletRequest.getSession().getAttribute(SessionResource.UserVO.getName());
        log.info("action main session uservo- {}", new Gson().toJson(userVo));
//        if (loginVO != null && loginVO.getUserId() != null && !loginVO.getUserId().equals("")) {
        if (userVo != null && userVo.getUserId() != null && !userVo.getUserId().equals("")) {
            menuVos = authToMenuList(getUserPolicyList(userVo.getUserSeq(), userVo.getLastUseServiceGroup()), RoleResource.valueOf(userVo.getRoleVal()).getRoleValSeq().intValue())
                    .stream()
                    .sorted(Comparator.comparing(MenuVo::getSeq))
                    .distinct()
                    .collect(Collectors.toList());
            //윈도우 함수 쓰지않고 처리한 쿼리.
            for (MenuVo e : menuVos) {
                if (e.getUrl() != null && !e.getUrl().equals("") && !e.getUrl().equals("0")) {
                    url = "redirect:" + e.getUrl();
                    break;
                }
            }
        } else {
            // 로그인 세션이 없는 경우 오류(접근 불가) 화면으로 이동
            model.addAttribute("message", messageSource.getMessage("fail.common.login", null, Locale.getDefault()));
            Logout();
            url = "adm/signin/signin";
        }
        log.info("return url - {}", new Gson().toJson(url));
        return url;
    }

    public LoginVO getLoginVo() {
        LoginVO user = (LoginVO) httpServletRequest.getSession().getAttribute(SessionResource.LoginVO.getName());
        if (user == null || user.equals("")) {
            httpServletResponse.setStatus(401);
        }
        return user;
    }

    public UserVo getUserVo() {
        UserVo user = (UserVo) httpServletRequest.getSession().getAttribute(SessionResource.UserVO.getName());
//        if (user == null || user.equals("")) {
//            httpServletResponse.setStatus(401);
//        }
        return user;
    }

    public List<String> getSessionPolicy() {
        return (ArrayList) httpServletRequest.getSession().getAttribute(SessionResource.PolicyList.getName());
    }

    @Transactional
    public List<String> getUserPolicyList(Long seq, String lastService) {
        return tbUserAuthGroupRepo.userPolicyListSelect(seq, lastService);
    }

    public void Logout() {
        HttpSession session = httpServletRequest.getSession();
        session.setAttribute(SessionResource.LoginVO.getName(), null);
        session.setAttribute(SessionResource.PolicyList.getName(), null);
        session.setAttribute(SessionResource.UserVO.getName(), null);
    }

    public List<MenuVo> authToAuthList(List<String> authList, int roleValSeq) {
        return authList.stream().map(e -> {
                            log.info(e);
                            if (PolicyResource.valueOf(e).getMenu() != MenuResource.root && PolicyResource.valueOf(e).getRoleValSeq() < roleValSeq) {
                                log.info(PolicyResource.valueOf(e).getMenu().getMenuName());
                                return MenuVo.builder()
                                        .seq(PolicyResource.valueOf(e).getMenu().getMenuSeq())
                                        .seqUpper(PolicyResource.valueOf(e).getMenu().getMenuSeqUpper())
                                        .menuName(EnumToMap.spaceMap.get(PolicyResource.valueOf(e).getMenu().getLevel()) + PolicyResource.valueOf(e).getPolicyName())
                                        .authName(PolicyResource.valueOf(e).getName())
                                        .level(PolicyResource.valueOf(e).getMenu().getLevel())
                                        .url(PolicyResource.valueOf(e).getMenu().getMenuUrl())
                                        .build();
                            } else {
                                return MenuVo.builder().seq(PolicyResource.rootRead.getMenu().getMenuSeq())
                                        .seqUpper(PolicyResource.rootRead.getMenu().getMenuSeqUpper())
                                        .menuName(PolicyResource.valueOf(e).getPolicyName())
                                        .authName(e)
                                        .level(PolicyResource.rootRead.getMenu().getLevel())
                                        .url(PolicyResource.rootRead.getMenu().getMenuUrl())
                                        .build();
                            }

                        }
                )
                .sorted(Comparator.comparing(MenuVo::getSeq))
                .collect(Collectors.toList())
                ;
    }

    public List<MenuVo> authToMenuList(List<String> authList, int roleValSeq) {
        Map<Long, MenuVo> menuList = authList.stream().map(e -> {
                            if (PolicyResource.valueOf(e).getMenu() != MenuResource.root && PolicyResource.valueOf(e).getRoleValSeq() < roleValSeq) {
                                return MenuVo.builder()
                                        .seq(PolicyResource.valueOf(e).getMenu().getMenuSeq())
                                        .seqUpper(PolicyResource.valueOf(e).getMenu().getMenuSeqUpper())
                                        .menuName(EnumToMap.spaceMap.get(PolicyResource.valueOf(e).getMenu().getLevel()) + PolicyResource.valueOf(e).getMenu().getMenuName())
                                        .authName("")
                                        .level(PolicyResource.valueOf(e).getMenu().getLevel())
                                        .url(PolicyResource.valueOf(e).getMenu().getMenuUrl())
                                        .build();
                            } else {
                                return MenuVo.builder().seq(PolicyResource.rootRead.getMenu().getMenuSeq())
                                        .seqUpper(PolicyResource.rootRead.getMenu().getMenuSeqUpper())
                                        .menuName(EnumToMap.spaceMap.get(PolicyResource.rootRead.getMenu().getLevel()) + PolicyResource.rootRead.getMenu().getMenuName())
                                        .authName("")
                                        .level(PolicyResource.rootRead.getMenu().getLevel())
                                        .url(PolicyResource.rootRead.getMenu().getMenuUrl())
                                        .build();
                            }
                        }
                )
                .collect(Collectors.toMap(menuVo -> menuVo.getSeq(), menuVo -> menuVo, (o, o2) -> o));

        List<MenuVo> result = new ArrayList<>();
        List<Integer> next = new ArrayList<>();
        for (Long e : menuList.keySet().stream().sorted().collect(Collectors.toList())) {
            result.add(menuList.get(e));
            next.add(menuList.get(e).getLevel());
        }
        int temp = next.get(0);
        next.remove(0);
        next.add(temp);
        AtomicInteger index = new AtomicInteger();
        result.stream().forEach(menuVo ->
                menuVo.setNextLevel(next.get(index.getAndIncrement()))
        )
        ;
        return result;
    }

    @Transactional
    public List<MenuVo> selectMainMenuLeft() {
        //동적처리
//        return authToMenuList(getUserPolicyList(getUserVo().getUserSeq(), getUserVo().getLastUseServiceGroup()), RoleResource.valueOf(getUserVo().getRoleVal()).getRoleValSeq().intValue())
        //세션처리
        return authToMenuList(getSessionPolicy(), RoleResource.valueOf(getUserVo().getRoleVal()).getRoleValSeq().intValue())
                ;
    }

    //Test용

    @Transactional
    public List<MenuVo> selectMainMenuLeft(Long seq, String lastService, String role) {
        return authToMenuList(getUserPolicyList(seq, lastService), RoleResource.valueOf(role).getRoleValSeq().intValue());
    }

    @Transactional
    public List<ServiceGroupRes> getServiceGroup() {
        UserVo uv = getUserVo();
        List<ServiceGroupRes> result = tbServiceGrouopRepo.findByServiceGroupInAndServiceGroupUpper(tbUserCustRepo.findByUserSeq(uv.getUserSeq())
                        .stream().map(e -> e.getServiceGroup())
                        .collect(Collectors.toList()), uv.getLastUseServiceGroup())
                .stream().map(f -> new ServiceGroupRes().toServiceGrouopRes(f))
                .collect(Collectors.toList());
        return result;
    }

    @Transactional
    public void LastUserServiceGroupUpdate(Long userSeq, String updateService) throws CustomNotFoundException {
        tbBotUserRepo.findById(userSeq)
                .orElseThrow(() -> new CustomNotFoundException())
                .updateLastService(updateService)
        ;
    }

    @Transactional
    public List<OprmngCodeRes> getOprmngCode(String serviceType, String menuval) {
        List<OprmngCodeRes> result = tbServiceGrouopRepo.getOprmngCode(getUserVo(), serviceType, menuval);
        log.info("result - {}", new Gson().toJson(result));
        return result;
    }

    @Transactional
    public List<OprmngCodeRes> getMainOprmngCode() {
//        List<OprmngCodeRes> result = tbServiceGrouopRepo.getMainOprmngCode(getUserVo());

        List<OprmngCodeRes> resultMainOpr = new ArrayList<>();

        if (getUserVo().getRoleVal().equals(RoleResource.SYSTEMADMIN.getName())) {
            resultMainOpr = tbServiceGrouopRepo.findByServiceType("M").stream().map(e -> OprmngCodeRes.builder()
                            .serviceGroup(e.getServiceGroup())
                            .serviceGroupName(e.getServiceGroupName())
                            .build())
                    .collect(Collectors.toList());
        } else {

            List<String> serviceResult = tbUserCustRepo.findByUserSeq(getUserVo().getUserSeq())
                    .stream().map(e -> e.getServiceGroup()).collect(Collectors.toList());

            List<String> myMainService = tbServiceGrouopRepo.findByServiceGroupIn(serviceResult)
                    .stream().map(e -> e.getServiceGroupUpper())
                    .distinct()
                    .collect(Collectors.toList());

            resultMainOpr = tbServiceGrouopRepo.findByServiceGroupIn(myMainService)
                    .stream().map(e -> OprmngCodeRes.builder()
                            .serviceGroup(e.getServiceGroup())
                            .serviceGroupName(e.getServiceGroupName())
                            .build())
                    .collect(Collectors.toList());
        }

        log.info("resultMainOpr - {}", new Gson().toJson(resultMainOpr));

//        log.info("result - {}", new Gson().toJson(result));
        return resultMainOpr;
    }

    @Transactional
    public void getMainOprmngCodeUpdate(MainOprReq dto) throws CustomNotFoundException {
        TbBotUser result = tbBotUserRepo.findById(dto.getUserSeq()).orElseThrow(() -> new CustomNotFoundException())
                .updateLastService(dto.getServiceGroup());
        log.info("getMainOprmngCodeUpdate - {}", new Gson().toJson(result));
//        httpServletRequest.getSession().setAttribute(SessionResource.UserVO.getName(), null);
//        httpServletRequest.getSession().setAttribute(SessionResource.UserVO.getName(), result.toUserVO());
        sessionSetting(result);
    }

    public Map<String, String> jstreeParentConvertToPolicy() {
        return EnumToMap.PolicyToParentMap;
    }

    public List<OprmngCodeRes> getOprmngCodeAdmin(String serviceType) {

        List<OprmngCodeRes> result = tbServiceGrouopRepo.getOprmngCodeAdmin(getUserVo(), serviceType);
        log.info("result - {}", new Gson().toJson(result));
        return result;
    }

    @Transactional
    public ResponseEntity loginCheck(LoginCheckReq dto) throws CustomNotFoundException, CustomBadRequestException {
        TbBotUser user = tbBotUserRepo.findByUserId(dto.getUserId()).orElseThrow(() -> new CustomNotFoundException());
        if (user.getLoginCheck() >= 5) {
            throw new CustomBadRequestException();
        }
        if (user.getUseYn().equals("N")) {
            throw new CustomBadRequestException();
        }
        if (LocalDateTime.now().isAfter(user.getUpdateDate().plusDays(90))) {
            return ResponseEntity.status(401).build();
        }
        if (!passwordEncoder.matches(dto.getPassword(), user.getPassword())) {
            user.loginFailCheck();
            throw new CustomNotFoundException();
        } else {
//            logService.LogSave(LogVO.builder()
//                    .menuAuth(MenuResource.commonsystemusermanage)
//                    .actionCode(String.valueOf((ActionResource.login)))
//                    .serviceGroup("OURSTOREAI")
//                    .build());

            tbIcsLogRepo.save(TbIcsLog.builder()
                    .registDate(LocalDateTime.now())
                    .userId(user.getUserId())
                    .actionCode(ActionResource.login.name())
                    .menuAuth(MenuResource.commonsystemusermanage.name())
                    .ip(clientIp.getClientIp())
                    .serviceGroup(user.getLastUseServiceGroup())
                    .build()
            );

            user.resetLoginFailCheck();
        }
        return ResponseEntity.ok().build();
    }


    @Transactional
    public ResponseEntity PwdUpdate(PwdUpdateReq dto) throws CustomNotFoundException, CustomBadRequestException {
        TbBotUser user = tbBotUserRepo.findByUserId(dto.getId()).orElseThrow(() -> new CustomNotFoundException());
        if (user.getLoginCheck() >= 5) {
            throw new CustomBadRequestException();
        }
        if (!passwordEncoder.matches(dto.getPw0(), user.getPassword())) {
            user.loginFailCheck();
            throw new CustomNotFoundException();
        } else {
            user.updatePassword(passwordEncoder.encode(dto.getPw1()));
        }

        return ResponseEntity.ok().build();
    }
}
