package com.eactive.eai.rms.data.entity.onl.apim.portaluser;

import com.eactive.apim.portal.portaluser.entity.PortalUser;
import com.eactive.apim.portal.portaluser.entity.PortalUserEnums;
import com.eactive.apim.portal.portaluser.entity.QPortalUser;
import com.eactive.apim.portal.portaluser.repository.PortalUserRepository;
import com.eactive.apim.portal.template.entity.QMessageRequest;
import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.JPAExpressions;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

@Service
@Transactional(transactionManager = "transactionManagerForEMS")
public class PortalUserService extends AbstractEMSDataSerivce<PortalUser, String, PortalUserRepository> {

    public Page<PortalUser> findAll(Pageable pageable, PortalUserUISearch portalUserUISearch) {
        QPortalUser qPortalUser = QPortalUser.portalUser;

        BooleanBuilder predicate = new BooleanBuilder();

        if (StringUtils.isNotBlank(portalUserUISearch.getSearchUserStatus())) {
            predicate.and(qPortalUser.userStatus.eq(PortalUserEnums.UserStatus.valueOf(portalUserUISearch.getSearchUserStatus())));
        } else if (!portalUserUISearch.isIncludeRemoved()) {
            predicate.and(qPortalUser.userStatus.ne(PortalUserEnums.UserStatus.REMOVED));
        }

        if (StringUtils.isNotBlank(portalUserUISearch.getSearchApprovalStatus())) {
            predicate.and(qPortalUser.approvalStatus.eq(PortalUserEnums.ApprovalStatus.valueOf(portalUserUISearch.getSearchApprovalStatus())));
        }

        if (StringUtils.isNotBlank(portalUserUISearch.getSearchUserId())) {
            // login_id / email_addr 는 PersonalDataEncryptConverter 로 암호화 저장되는 컬럼이다.
            // 암호화는 부분문자열을 보존하지 않으므로 LIKE/contains 검색은 절대 매칭되지 않는다.
            // 결정적(deterministic) 암호화라 정확일치(eq)만 가능하므로, login_id 와 email_addr 둘 다 정확일치로 OR 검색한다.
            String searchUserId = portalUserUISearch.getSearchUserId().trim();
            BooleanBuilder loginIdPredicate = new BooleanBuilder()
                    .or(qPortalUser.loginId.eq(searchUserId))
                    .or(qPortalUser.emailAddr.eq(searchUserId));
            // 이메일 형식 값(예: user+1@unit-test.com)은 form-urlencoded 디코딩 과정에서
            // '+' 가 공백으로 치환되어 전달될 수 있다(프록시/컨테이너 단계라 앱에서 인코딩만으로 막기 어려움).
            // login_id/email 에는 공백이 올 수 없으므로, 공백 포함 검색어는 '+' 치환 값도 함께 매칭한다.
            if (searchUserId.indexOf(' ') >= 0) {
                String plusRestored = searchUserId.replace(' ', '+');
                loginIdPredicate.or(qPortalUser.loginId.eq(plusRestored))
                        .or(qPortalUser.emailAddr.eq(plusRestored));
            }
            predicate.and(loginIdPredicate);
        }

        if (StringUtils.isNotBlank(portalUserUISearch.getSearchUserName())) {
            predicate.and(qPortalUser.userName.containsIgnoreCase(portalUserUISearch.getSearchUserName()));
        }

        if (StringUtils.isNotBlank(portalUserUISearch.getSearchOrgId())) {
            predicate.and(qPortalUser.portalOrg.id.eq(portalUserUISearch.getSearchOrgId()));
        }
        if (StringUtils.isNotBlank(portalUserUISearch.getSearchMobileNumber())) {
            // mobile_number 도 암호화 저장 컬럼이라 부분문자열이 보존되지 않는다.
            // LIKE/contains 는 매칭되지 않으므로 정확일치(eq)로만 검색한다.
            predicate.and(qPortalUser.mobileNumber.eq(portalUserUISearch.getSearchMobileNumber().trim()));
        }

        if (StringUtils.isNotBlank(portalUserUISearch.getSearchRoleCode())) {
            predicate.and(qPortalUser.roleCode.eq(PortalUserEnums.RoleCode.valueOf(portalUserUISearch.getSearchRoleCode())));
        }

        // 휴대폰번호 중복(2명 이상 공유) 사용자만 조회
        if (portalUserUISearch.isOnlyDuplicateMobile()) {
            // 자바 리스트 .in() 은 컨버터가 IN 파라미터를 암호화하므로 레거시 평문 데이터와 매칭되지 않는다.
            // 대신 서브쿼리 .in() 으로 컬럼 대 컬럼(SQL 내부) 비교를 하여 평문/암호문 저장 방식과 무관하게 동작시킨다.
            QPortalUser dupUser = new QPortalUser("dupUser");
            predicate.and(qPortalUser.mobileNumber.in(
                    JPAExpressions.select(dupUser.mobileNumber)
                            .from(dupUser)
                            .where(dupUser.mobileNumber.isNotNull()
                                    .and(dupUser.userStatus.ne(PortalUserEnums.UserStatus.REMOVED)))
                            .groupBy(dupUser.mobileNumber)
                            .having(dupUser.count().gt(1L))));
        }

        return repository.findAll(predicate, pageable);
    }

    /**
     * 휴대폰번호가 중복되는 사용자 총 인원수(중복 그룹에 속한 사용자 수의 합)를 반환한다.
     */
    public long countDuplicateMobileUsers() {
        QPortalUser qPortalUser = QPortalUser.portalUser;
        // Oracle 빈 문자열=NULL 이슈로 isNotNull 만 사용 (findDuplicateMobileNumbers 와 동일 기준)
        List<Long> duplicateGroupCounts = getJPAQueryFactory()
                .select(qPortalUser.count())
                .from(qPortalUser)
                .where(qPortalUser.mobileNumber.isNotNull()
                        .and(qPortalUser.userStatus.ne(PortalUserEnums.UserStatus.REMOVED)))
                .groupBy(qPortalUser.mobileNumber)
                .having(qPortalUser.count().gt(1L))
                .fetch();
        return duplicateGroupCounts.stream().mapToLong(Long::longValue).sum();
    }

    public boolean existsByLoginId(String loginId) {
        BooleanExpression predicate = QPortalUser.portalUser.loginId.eq(loginId);
        return repository.exists(predicate);
    }

    public Optional<PortalUser> findCorporateManagerByOrgId(String orgid) {
        QPortalUser qPortalUser = QPortalUser.portalUser;

        BooleanExpression predicate = qPortalUser.portalOrg.id.eq(orgid)
                .and(qPortalUser.roleCode.eq(PortalUserEnums.RoleCode.ROLE_CORP_MANAGER))
                .and(qPortalUser.userStatus.ne(PortalUserEnums.UserStatus.REMOVED));

        return Optional.ofNullable(getJPAQueryFactory()
                .selectFrom(qPortalUser)
                .where(predicate)
                .orderBy(qPortalUser.createdDate.asc())
                .fetchFirst());
    }

    public PortalUser findByUserId(String userId) {
        if (StringUtils.isBlank(userId)) {
            return null;
        }

        QPortalUser qPortalUser = QPortalUser.portalUser;

        return getJPAQueryFactory()
                .selectFrom(qPortalUser)
                .where(qPortalUser.id.eq(userId))
                .orderBy(qPortalUser.createdDate.desc())
                .fetchFirst();
    }

    public long countByOrgId(String orgId) {
        return getJPAQueryFactory().selectFrom(QPortalUser.portalUser)
                .where(QPortalUser.portalUser.portalOrg.id.eq(orgId))
                .fetchCount();
    }

    public long countMessageRequestByUserId(String userId) {
        return getJPAQueryFactory().selectFrom(QMessageRequest.messageRequest)
                .where(QMessageRequest.messageRequest.user.id.eq(userId))
                .fetchCount();
    }
}
