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

import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.Dashboard;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SearchGeoReq;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataGeomEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalSttcEntity;
import com.kamco.cd.kamcoback.postgres.entity.QModelMngEntity;
import com.kamco.cd.kamcoback.postgres.entity.QModelVerEntity;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class InferenceResultRepositoryImpl implements InferenceResultRepositoryCustom {

  private final JPAQueryFactory queryFactory;
  private final QModelMngEntity tmm = QModelMngEntity.modelMngEntity;
  private final QModelVerEntity tmv = QModelVerEntity.modelVerEntity;
  private final QMapSheetAnalEntity mapSheetAnalEntity = QMapSheetAnalEntity.mapSheetAnalEntity;
  private final QMapSheetAnalDataEntity mapSheetAnalDataEntity =
      QMapSheetAnalDataEntity.mapSheetAnalDataEntity;
  private final QMapSheetAnalDataGeomEntity mapSheetAnalDataGeomEntity =
      QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity;
  private final QMapSheetAnalSttcEntity mapSheetAnalSttcEntity =
      QMapSheetAnalSttcEntity.mapSheetAnalSttcEntity;

  /**
   * 분석결과 목록 조회
   *
   * @param searchReq
   * @return
   */
  @Override
  public Page<InferenceResultDto.AnalResList> getInferenceResultList(
      InferenceResultDto.SearchReq searchReq) {
    Pageable pageable = searchReq.toPageable();
    // "0000" 전체조회
    BooleanBuilder builder = new BooleanBuilder();
    if (searchReq.getStatCode() != null && !"0000".equals(searchReq.getStatCode())) {
      builder.and(mapSheetAnalEntity.analState.eq(searchReq.getStatCode()));
    }

    // 제목
    if (searchReq.getTitle() != null) {
      builder.and(mapSheetAnalEntity.analTitle.like("%" + searchReq.getTitle() + "%"));
    }

    List<InferenceResultDto.AnalResList> content =
        queryFactory
            .select(
                Projections.constructor(
                    InferenceResultDto.AnalResList.class,
                    mapSheetAnalEntity.id,
                    mapSheetAnalEntity.analTitle,
                    mapSheetAnalEntity.analMapSheet,
                    mapSheetAnalEntity.detectingCnt,
                    mapSheetAnalEntity.analStrtDttm,
                    mapSheetAnalEntity.analEndDttm,
                    mapSheetAnalEntity.analSec,
                    mapSheetAnalEntity.analPredSec,
                    mapSheetAnalEntity.analState,
                    Expressions.stringTemplate(
                        "fn_code_name({0}, {1})", "0002", mapSheetAnalEntity.analState),
                    mapSheetAnalEntity.gukyuinUsed))
            .from(mapSheetAnalEntity)
            .where(builder)
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .orderBy(mapSheetAnalEntity.id.desc())
            .fetch();

    long total =
        queryFactory
            .select(mapSheetAnalEntity.id)
            .from(mapSheetAnalEntity)
            .where(builder)
            .fetchCount();

    return new PageImpl<>(content, pageable, total);
  }

  /**
   * 분석결과 요약정보
   *
   * @param id
   * @return
   */
  @Override
  public Optional<InferenceResultDto.AnalResSummary> getInferenceResultSummary(Long id) {

    // 1. 최신 버전 UID를 가져오는 서브쿼리
    JPQLQuery<Long> latestVerUidSub =
        JPAExpressions.select(tmv.id.max()).from(tmv).where(tmv.modelUid.eq(tmm.id));

    Optional<InferenceResultDto.AnalResSummary> content =
        Optional.ofNullable(
            queryFactory
                .select(
                    Projections.constructor(
                        InferenceResultDto.AnalResSummary.class,
                        mapSheetAnalEntity.id,
                        mapSheetAnalEntity.analTitle,
                        tmm.modelNm.concat(" ").concat(tmv.modelVer).as("modelInfo"),
                        mapSheetAnalEntity.targetYyyy,
                        mapSheetAnalEntity.compareYyyy,
                        mapSheetAnalEntity.analMapSheet,
                        mapSheetAnalEntity.analStrtDttm,
                        mapSheetAnalEntity.analEndDttm,
                        mapSheetAnalEntity.analSec,
                        mapSheetAnalEntity.analPredSec,
                        mapSheetAnalEntity.resultUrl,
                        mapSheetAnalEntity.detectingCnt,
                        mapSheetAnalEntity.accuracy,
                        mapSheetAnalEntity.analState,
                        Expressions.stringTemplate(
                            "fn_code_name({0}, {1})", "0002", mapSheetAnalEntity.analState)))
                .from(mapSheetAnalEntity)
                .leftJoin(tmm)
                .on(mapSheetAnalEntity.modelUid.eq(tmm.id))
                .leftJoin(tmv)
                .on(tmv.modelUid.eq(tmm.id).and(tmv.id.eq(latestVerUidSub)))
                .where(mapSheetAnalEntity.id.eq(id))
                .fetchOne());
    return content;
  }

  /**
   * 분석결과 상세 class name별 탐지 개수
   *
   * @param id
   * @return
   */
  @Override
  public List<Dashboard> getDashboard(Long id) {
    return queryFactory
        .select(
            Projections.constructor(
                Dashboard.class,
                mapSheetAnalSttcEntity.id.classAfterCd,
                mapSheetAnalSttcEntity.classAfterCnt.sum()))
        .from(mapSheetAnalSttcEntity)
        .where(mapSheetAnalSttcEntity.id.analUid.eq(id))
        .groupBy(mapSheetAnalSttcEntity.id.classAfterCd)
        .orderBy(mapSheetAnalSttcEntity.id.classAfterCd.asc())
        .fetch();
  }

  @Override
  public List<MapSheetAnalDataEntity> listAnalyGeom(Long id) {
    QMapSheetAnalDataEntity analy = QMapSheetAnalDataEntity.mapSheetAnalDataEntity;
    return queryFactory.selectFrom(analy).where(analy.analUid.eq(id)).fetch();
  }

  /**
   * 분석결과 상세 목록
   *
   * @param searchReq
   * @return
   */
  @Override
  public Page<MapSheetAnalDataGeomEntity> listInferenceResultWithGeom(
      List<Long> ids, SearchGeoReq searchReq) {

    // 분석 차수
    QMapSheetAnalDataGeomEntity detectedEntity =
        QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity;
    Pageable pageable = searchReq.toPageable();

    // 검색조건
    JPAQuery<MapSheetAnalDataGeomEntity> query =
        queryFactory
            .selectFrom(detectedEntity)
            .where(
                detectedEntity.dataUid.in(ids),
                eqTargetClass(detectedEntity, searchReq.getTargetClass()),
                eqCompareClass(detectedEntity, searchReq.getCompareClass()),
                containsMapSheetNum(detectedEntity, searchReq.getMapSheetNum()));

    // count
    long total = query.fetchCount();

    // Pageable에서 정렬 가져오기, 없으면 기본 정렬(createdDttm desc) 사용
    List<OrderSpecifier<?>> orders = getOrderSpecifiers(pageable.getSort());
    if (orders.isEmpty()) {
      orders.add(detectedEntity.createdDttm.desc());
    }

    List<MapSheetAnalDataGeomEntity> content =
        query
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .orderBy(orders.toArray(new OrderSpecifier[0]))
            .fetch();

    return new PageImpl<>(content, pageable, total);
  }

  /**
   * 분석결과 상세 목록
   *
   * @param searchGeoReq
   * @return
   */
  @Override
  public Page<InferenceResultDto.Geom> getInferenceGeomList(Long id, SearchGeoReq searchGeoReq) {
    Pageable pageable = searchGeoReq.toPageable();
    BooleanBuilder builder = new BooleanBuilder();

    // 추론결과 id
    builder.and(mapSheetAnalEntity.id.eq(id));

    // 기준년도 분류
    if (searchGeoReq.getTargetClass() != null && !searchGeoReq.getTargetClass().equals("")) {
      builder.and(
          mapSheetAnalDataGeomEntity
              .classAfterCd
              .toLowerCase()
              .eq(searchGeoReq.getTargetClass().toLowerCase()));
    }

    // 비교년도 분류
    if (searchGeoReq.getCompareClass() != null && !searchGeoReq.getCompareClass().equals("")) {
      builder.and(
          mapSheetAnalDataGeomEntity
              .classBeforeCd
              .toLowerCase()
              .eq(searchGeoReq.getCompareClass().toLowerCase()));
    }

    // 분석도엽
    if (searchGeoReq.getMapSheetNum() != null && !searchGeoReq.getMapSheetNum().isEmpty()) {
      List<Long> mapSheetNum = searchGeoReq.getMapSheetNum();
      builder.and(mapSheetAnalDataGeomEntity.mapSheetNum.in(mapSheetNum));
    }

    List<InferenceResultDto.Geom> content =
        queryFactory
            .select(
                Projections.constructor(
                    InferenceResultDto.Geom.class,
                    mapSheetAnalDataGeomEntity.compareYyyy,
                    mapSheetAnalDataGeomEntity.targetYyyy,
                    mapSheetAnalDataGeomEntity.classBeforeCd,
                    mapSheetAnalDataGeomEntity.classBeforeProb,
                    mapSheetAnalDataGeomEntity.classAfterCd,
                    mapSheetAnalDataGeomEntity.classAfterProb,
                    mapSheetAnalDataGeomEntity.mapSheetNum,
                    mapSheetAnalDataGeomEntity.geom,
                    mapSheetAnalDataGeomEntity.geomCenter))
            .from(mapSheetAnalEntity)
            .join(mapSheetAnalDataEntity)
            .on(mapSheetAnalDataEntity.analUid.eq(mapSheetAnalEntity.id))
            .join(mapSheetAnalDataGeomEntity)
            .on(mapSheetAnalDataGeomEntity.dataUid.eq(mapSheetAnalDataEntity.id))
            .where(builder)
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetch();

    long total =
        queryFactory
            .select(mapSheetAnalDataGeomEntity.id)
            .from(mapSheetAnalEntity)
            .join(mapSheetAnalDataEntity)
            .on(mapSheetAnalDataEntity.analUid.eq(mapSheetAnalEntity.id))
            .join(mapSheetAnalDataGeomEntity)
            .on(mapSheetAnalDataGeomEntity.dataUid.eq(mapSheetAnalDataEntity.id))
            .where(builder)
            .fetchCount();

    return new PageImpl<>(content, pageable, total);
  }

  /**
   * 추론된 5000:1 도엽 목록
   *
   * @param id
   * @return
   */
  @Override
  public List<Long> getSheets(Long id) {
    return queryFactory
        .select(mapSheetAnalDataEntity.mapSheetNum)
        .from(mapSheetAnalEntity)
        .join(mapSheetAnalDataEntity)
        .on(mapSheetAnalDataEntity.analUid.eq(mapSheetAnalEntity.id))
        .where(mapSheetAnalEntity.id.eq(id))
        .groupBy(mapSheetAnalDataEntity.mapSheetNum)
        .fetch();
  }

  /** Pageable의 Sort를 QueryDSL OrderSpecifier로 변환 */
  @SuppressWarnings({"unchecked", "rawtypes"})
  private List<OrderSpecifier<?>> getOrderSpecifiers(Sort sort) {
    List<OrderSpecifier<?>> orders = new ArrayList<>();

    if (sort.isSorted()) {
      QMapSheetAnalDataGeomEntity entity = QMapSheetAnalDataGeomEntity.mapSheetAnalDataGeomEntity;

      for (Sort.Order order : sort) {
        Order direction = order.isAscending() ? Order.ASC : Order.DESC;
        String property = order.getProperty();

        // 유효한 필드만 처리
        switch (property) {
          case "classBeforeCd" -> orders.add(new OrderSpecifier(direction, entity.classBeforeCd));
          case "classBeforeProb" ->
              orders.add(new OrderSpecifier(direction, entity.classBeforeProb));
          case "classAfterCd" -> orders.add(new OrderSpecifier(direction, entity.classAfterCd));
          case "classAfterProb" -> orders.add(new OrderSpecifier(direction, entity.classAfterProb));
          case "mapSheetNum" -> orders.add(new OrderSpecifier(direction, entity.mapSheetNum));
          case "compareYyyy" -> orders.add(new OrderSpecifier(direction, entity.compareYyyy));
          case "targetYyyy" -> orders.add(new OrderSpecifier(direction, entity.targetYyyy));
          case "area" -> orders.add(new OrderSpecifier(direction, entity.area));
          case "createdDttm" -> orders.add(new OrderSpecifier(direction, entity.createdDttm));
          case "updatedDttm" -> orders.add(new OrderSpecifier(direction, entity.updatedDttm));
            // 유효하지 않은 필드는 무시
          default -> {}
        }
      }
    }

    return orders;
  }

  private BooleanExpression eqTargetClass(
      QMapSheetAnalDataGeomEntity detectedEntity, String targetClass) {
    return targetClass != null && !targetClass.isEmpty()
        ? detectedEntity.classAfterCd.toLowerCase().eq(targetClass.toLowerCase())
        : null;
  }

  private BooleanExpression eqCompareClass(
      QMapSheetAnalDataGeomEntity detectedEntity, String compareClass) {
    return compareClass != null && !compareClass.isEmpty()
        ? detectedEntity.classBeforeCd.toLowerCase().eq(compareClass.toLowerCase())
        : null;
  }

  private BooleanExpression containsMapSheetNum(
      QMapSheetAnalDataGeomEntity detectedEntity, List<Long> mapSheet) {
    if (mapSheet == null || mapSheet.isEmpty()) {
      return null;
    }

    return detectedEntity.mapSheetNum.in(mapSheet);
  }
}
