package com.kamco.cd.kamcoback.label.service;

import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetInspector;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.DailyHistory;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics;
import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService;
import java.time.LocalDate;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@Transactional
public class LabelAllocateService {

  private static final int STAGNATION_THRESHOLD = 10; // 정체 판단 기준 (3일 평균 처리량)
  private static final int BATCH_SIZE = 100; // 배정 배치 크기

  private final LabelAllocateCoreService labelAllocateCoreService;

  public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) {
    this.labelAllocateCoreService = labelAllocateCoreService;
  }

  /**
   * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직
   *
   * @param labelerAutoType 라벨러 자동/수동 배정 타입
   * @param inspectorAutoType 검수자 자동/수동 배정 타입
   * @param stage 회차
   * @param targetUsers 라벨러 목록
   * @param targetInspectors 검수자 목록
   */
  @Transactional
  public ApiResponseDto.ResponseObj allocateAsc(
      String labelerAutoType,
      String inspectorAutoType,
      Integer stage,
      List<TargetUser> targetUsers,
      List<TargetInspector> targetInspectors,
      Integer compareYyyy,
      Integer targetYyyy) {
    Long lastId = null;

    // geom 잔여건수 조회
    Long chargeCnt =
        labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy);
    if (chargeCnt <= 0) {
      return new ApiResponseDto.ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 배정완료된 회차 입니다.");
    }

    Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum();
    if (!Objects.equals(chargeCnt, totalDemand)) {
      return new ApiResponseDto.ResponseObj(
          ApiResponseCode.BAD_REQUEST, "총 잔여건수와 요청 값의 합계가 맞지 않습니다.");
    }

    List<AllocateInfoDto> allIds =
        labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage);
    int index = 0;
    for (TargetUser target : targetUsers) {
      int end = index + target.getDemand();
      List<AllocateInfoDto> sub = allIds.subList(index, end);

      labelAllocateCoreService.assignOwner(sub, target.getUserId(), compareYyyy, targetYyyy, stage);
      index = end;
    }

    // 검수자에게 userCount명 만큼 할당
    List<LabelAllocateDto.Basic> list =
        labelAllocateCoreService.findAssignedLabelerList(compareYyyy, targetYyyy, stage);
    int from = 0;

    for (TargetInspector inspector : targetInspectors) {
      int to = Math.min(from + inspector.getUserCount(), list.size());

      if (from >= to) {
        break;
      }

      List<UUID> assignmentUids =
          list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList();

      labelAllocateCoreService.assignInspectorBulk(assignmentUids, inspector.getInspectorUid());

      from = to;
    }

    return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "배정이 완료되었습니다.");
  }

  public List<UserList> availUserList(String role) {
    return labelAllocateCoreService.availUserList(role);
  }

  /**
   * 작업자 목록 및 3일치 통계 조회
   *
   * @param analUid 분석 ID
   * @param workerType 작업자 유형 (LABELER/INSPECTOR)
   * @param searchName 이름 검색
   * @param searchEmployeeNo 사번 검색
   * @param sortType 정렬 조건
   * @return 작업자 목록 및 통계
   */
  public WorkerListResponse getWorkerStatistics(
      Long analUid,
      String workerType,
      String searchName,
      String searchEmployeeNo,
      String sortType) {

    // 작업 진행 현황 조회
    var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid);

    // 작업자 통계 조회
    List<WorkerStatistics> workers =
        labelAllocateCoreService.findWorkerStatistics(
            analUid, workerType, searchName, searchEmployeeNo, sortType);

    // 각 작업자별 3일치 처리량 조회
    LocalDate today = LocalDate.now();
    for (WorkerStatistics worker : workers) {
      Long day1Count =
          labelAllocateCoreService.findDailyProcessedCount(
              worker.getWorkerId(), workerType, today.minusDays(1), analUid);
      Long day2Count =
          labelAllocateCoreService.findDailyProcessedCount(
              worker.getWorkerId(), workerType, today.minusDays(2), analUid);
      Long day3Count =
          labelAllocateCoreService.findDailyProcessedCount(
              worker.getWorkerId(), workerType, today.minusDays(3), analUid);

      long average = (day1Count + day2Count + day3Count) / 3;

      DailyHistory history =
          DailyHistory.builder()
              .day1Ago(day1Count)
              .day2Ago(day2Count)
              .day3Ago(day3Count)
              .average(average)
              .build();

      worker.setHistory(history);

      // 정체 여부 판단 (3일 평균이 STAGNATION_THRESHOLD 미만일 때)
      if (average < STAGNATION_THRESHOLD) {
        worker.setIsStagnated(true);
      }
    }

    return WorkerListResponse.builder().progressInfo(progressInfo).workers(workers).build();
  }

  public InferenceDetail findInferenceDetail(
      Integer compareYyyy, Integer targetYyyy, Integer stage) {
    return labelAllocateCoreService.findInferenceDetail(compareYyyy, targetYyyy, stage);
  }

  public ApiResponseDto.ResponseObj allocateMove(
      String autoType,
      Integer stage,
      List<TargetUser> targetUsers,
      Integer compareYyyy,
      Integer targetYyyy) {
    Long lastId = null;

    Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum();

    if (chargeCnt <= 0) {
      return new ApiResponseDto.ResponseObj(ApiResponseCode.BAD_REQUEST, "이관할 데이터를 입력해주세요.");
    }

    List<Long> allIds =
        labelAllocateCoreService.fetchNextMoveIds(
            lastId, chargeCnt, compareYyyy, targetYyyy, stage);
    int index = 0;
    for (TargetUser target : targetUsers) {
      int end = index + target.getDemand();
      List<Long> sub = allIds.subList(index, end);

      labelAllocateCoreService.assignOwnerMove(sub, target.getUserId());
      index = end;
    }

    return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "이관을 완료하였습니다.");
  }

  public LabelerDetail findLabelerDetail(
      String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) {
    return labelAllocateCoreService.findLabelerDetail(userId, compareYyyy, targetYyyy, stage);
  }
}
