package com.kamco.cd.kamcoback.postgres.repository.log;

import static com.kamco.cd.kamcoback.postgres.entity.QAuditLogEntity.auditLogEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QErrorLogEntity.errorLogEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMenuEntity.menuEntity;

import com.kamco.cd.kamcoback.log.dto.ErrorLogDto;
import com.kamco.cd.kamcoback.log.dto.EventStatus;
import com.kamco.cd.kamcoback.log.dto.EventType;
import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Objects;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;

public class ErrorLogRepositoryImpl extends QuerydslRepositorySupport
    implements ErrorLogRepositoryCustom {

  private final JPAQueryFactory queryFactory;
  private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");

  public ErrorLogRepositoryImpl(JPAQueryFactory queryFactory) {
    super(AuditLogEntity.class);
    this.queryFactory = queryFactory;
  }

  @Override
  public Page<ErrorLogDto.Basic> findLogByError(ErrorLogDto.ErrorSearchReq searchReq) {
    Pageable pageable = searchReq.toPageable();
    List<ErrorLogDto.Basic> foundContent =
        queryFactory
            .select(
                Projections.constructor(
                    ErrorLogDto.Basic.class,
                    errorLogEntity.id.as("logId"),
                    Expressions.stringTemplate("{0}", "한국자산관리공사"), // serviceName
                    menuEntity.menuNm.as("menuName"),
                    memberEntity.employeeNo.as("loginId"),
                    memberEntity.name.as("userName"),
                    errorLogEntity.errorType.as("eventType"),
                    errorLogEntity.errorMessage.as(
                        "errorName"), // 기존에는 errorName 값이 있었는데 신규 테이블에는 없음. 에러 메세지와 동일
                    errorLogEntity.errorLevel.as("errorLevel"),
                    errorLogEntity.errorCode.as("errorCode"),
                    errorLogEntity.errorMessage.as("errorMessage"),
                    errorLogEntity.stackTrace.as("errorDetail"),
                    Expressions.stringTemplate(
                        "to_char({0}, 'YYYY-MM-DD')", errorLogEntity.createdDate)))
            .from(errorLogEntity)
            .leftJoin(auditLogEntity)
            .on(errorLogEntity.id.eq(auditLogEntity.errorLogUid))
            .leftJoin(menuEntity)
            .on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
            .leftJoin(memberEntity)
            .on(errorLogEntity.handlerUid.eq(memberEntity.id))
            .where(
                eventStatusEqFailed(),
                eventEndedAtBetween(searchReq.getStartDate(), searchReq.getEndDate()),
                eventTypeEq(searchReq.getEventType()),
                errorLevelEq(searchReq.getErrorLevel()))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .orderBy(errorLogEntity.createdDate.desc())
            .fetch();

    Long countQuery =
        queryFactory
            .select(errorLogEntity.id.countDistinct())
            .from(errorLogEntity)
            .leftJoin(auditLogEntity)
            .on(errorLogEntity.id.eq(auditLogEntity.errorLogUid))
            .leftJoin(menuEntity)
            .on(auditLogEntity.menuUid.eq(menuEntity.menuUid))
            .leftJoin(memberEntity)
            .on(errorLogEntity.handlerUid.eq(memberEntity.id))
            .where(
                eventStatusEqFailed(),
                eventEndedAtBetween(searchReq.getStartDate(), searchReq.getEndDate()),
                eventTypeEq(searchReq.getEventType()),
                errorLevelEq(searchReq.getErrorLevel()))
            .fetchOne();

    return new PageImpl<>(foundContent, pageable, countQuery);
  }

  private BooleanExpression eventEndedAtBetween(LocalDate startDate, LocalDate endDate) {
    if (Objects.isNull(startDate) || Objects.isNull(endDate)) {
      return null;
    }
    LocalDateTime startDateTime = startDate.atStartOfDay();
    LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay();
    return auditLogEntity
        .createdDate
        .goe(ZonedDateTime.from(startDateTime))
        .and(auditLogEntity.createdDate.lt(ZonedDateTime.from(endDateTime)));
  }

  private BooleanExpression eventStatusEqFailed() {
    return auditLogEntity.eventStatus.eq(EventStatus.FAILED);
  }

  private BooleanExpression eventTypeEq(EventType eventType) {
    if (Objects.isNull(eventType)) {
      return null;
    }
    return auditLogEntity.eventType.eq(eventType);
  }

  private BooleanExpression errorLevelEq(ErrorLogDto.LogErrorLevel level) {
    if (Objects.isNull(level)) {
      return null;
    }
    return errorLogEntity.errorLevel.eq(ErrorLogDto.LogErrorLevel.valueOf(level.name()));
  }
}
