/**
 * ============================================================
 * 개체(Cow) 서비스
 * ============================================================
 *
 * 사용 페이지: 개체 목록 페이지 (/cow), 개체 상세 페이지 (/cow/:cowNo)
 *
 * 주요 기능:
 * 1. 개체 단건 조회 (findByCowId)
 * 2. 랭킹 적용 개체 목록 조회 (findAllWithRanking)
 *    - GENOME: 35개 형질 EBV 가중 평균
 * ============================================================
 */
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, IsNull } from 'typeorm';
import { CowModel } from './entities/cow.entity';
import { GenomeRequestModel } from '../genome/entities/genome-request.entity';
import { GenomeTraitDetailModel } from '../genome/entities/genome-trait-detail.entity';
import { GeneDetailModel } from '../gene/entities/gene-detail.entity';
import { MptModel } from '../mpt/entities/mpt.entity';
import { FilterEngineService } from '../shared/filter/filter-engine.service';
import {
  RankingRequestDto,
  RankingCriteriaType,
  TraitRankingConditionDto,
} from './dto/ranking-request.dto';
import { isValidGenomeAnalysis, EXCLUDED_COW_IDS } from '../common/config/GenomeAnalysisConfig';
import { ALL_TRAITS, NEGATIVE_TRAITS } from '../common/const/TraitTypes';

/**
 * 개체(소) 관리 서비스
 *
 * 담당 기능:
 * - 개체 CRUD 작업
 * - 유전체 기반 랭킹 계산
 * - 필터링 및 정렬
 */
@Injectable()
export class CowService {
  constructor(
    // 개체(소) 테이블 Repository
    @InjectRepository(CowModel)
    private readonly cowRepository: Repository<CowModel>,

    // 유전체 분석 의뢰 Repository (형질 데이터 접근용)
    @InjectRepository(GenomeRequestModel)
    private readonly genomeRequestRepository: Repository<GenomeRequestModel>,

    // 유전체 형질 상세 Repository (EBV 값 접근용)
    @InjectRepository(GenomeTraitDetailModel)
    private readonly genomeTraitDetailRepository: Repository<GenomeTraitDetailModel>,

    // 유전자 상세 Repository (SNP 데이터 접근용)
    @InjectRepository(GeneDetailModel)
    private readonly geneDetailRepository: Repository<GeneDetailModel>,

    // 번식능력 Repository (MPT 데이터 접근용)
    @InjectRepository(MptModel)
    private readonly mptRepository: Repository<MptModel>,

    // 동적 필터링 서비스 (검색, 정렬, 페이지네이션)
    private readonly filterEngineService: FilterEngineService,
  ) { }

  // ============================================================
  // 개체 조회 메서드
  // ============================================================

  /**
   * 개체식별번호(cowId)로 단건 조회
   *
   * @param cowId - 개체식별번호 (예: KOR002119144049)
   * @returns 개체 정보 (farm 포함) + dataStatus (데이터 존재 여부)
   * @throws NotFoundException - 개체를 찾을 수 없는 경우
   */
  async findByCowId(cowId: string): Promise<CowModel & { dataStatus: { hasGenomeData: boolean; hasGeneData: boolean } }> {
    const cow = await this.cowRepository.findOne({
      where: { cowId: cowId, delDt: IsNull() },
      relations: ['farm'],
    });
    if (!cow) {
      throw new NotFoundException(`Cow with cowId ${cowId} not found`);
    }

    // 데이터 존재 여부 확인 (가벼운 COUNT 쿼리)
    const [genomeCount, geneCount] = await Promise.all([
      this.genomeTraitDetailRepository.count({
        where: { cowId, delDt: IsNull() },
        take: 1,
      }),
      this.geneDetailRepository.count({
        where: { cowId, delDt: IsNull() },
        take: 1,
      }),
    ]);

    return {
      ...cow,
      dataStatus: {
        hasGenomeData: genomeCount > 0,
        hasGeneData: geneCount > 0,
      },
    };
  }

  // ============================================================
  // 랭킹 적용 조회 메서드
  // ============================================================

  /**
   * 랭킹 적용 개체 목록 조회 (메인 API)
   *
   * POST /cow/ranking 에서 호출
   *
   * 기능:
   * 1. 필터 조건으로 개체 목록 조회
   * 2. 랭킹 기준(GENOME/GENE)에 따라 점수 계산
   * 3. 점수 기준 정렬 후 순위 부여
   *
   * @param rankingRequest - 필터 옵션 + 랭킹 옵션
   * @returns 순위가 적용된 개체 목록
   */
  async findAllWithRanking(rankingRequest: RankingRequestDto): Promise<any> {
    // Step 1: 요청에서 필터 옵션과 랭킹 옵션 추출
    const { filterOptions, rankingOptions } = rankingRequest;
    const { criteriaType } = rankingOptions;

    // Step 2: 필터 조건에 맞는 개체 목록 조회 (+ MPT cowId Set)
    const { cows, mptCowIdMap } = await this.getFilteredCows(filterOptions);

    // Step 3: 랭킹 기준에 따라 분기 처리
    switch (criteriaType) {
      // 유전체 형질 기반 랭킹 (35개 형질 EBV 가중 평균)
      // 지금은 유전체 형질만 기반으로 랭킹을 매기고 있음 추후 유전자와 유전체 복합 랭킹 변경될수있음
      // case 추가 예정
      case RankingCriteriaType.GENOME:
        return this.applyGenomeRanking(cows, rankingOptions.traitConditions || [], mptCowIdMap);

      // 기본값: 랭킹 없이 순서대로 반환
      default:
        return {
          items: cows.map((cow, index) => {
            const mptData = mptCowIdMap.get(cow.cowId);
            return {
              entity: {
                ...cow,
                hasMpt: mptCowIdMap.has(cow.cowId),
                mptTestDt: mptData?.testDt || null,
                mptMonthAge: mptData?.monthAge || null,
              },
              rank: index + 1,
              sortValue: 0,
            };
          }),
          total: cows.length,
          criteriaType,
        };
    }
  }

/**
     * 필터 조건에 맞는 개체 목록 조회 (Private)
     * 유전체 분석 의뢰/유전체 형질/유전자/번식능력(MPT) 데이터 중 하나라도 있는 개체 조회
     *
     * @param filterOptions - 필터/정렬/페이지네이션 옵션
     * @returns { cows: 필터링된 개체 목록, mptCowIdMap: MPT cowId -> { testDt, monthAge } Map }
     */
    private async getFilteredCows(filterOptions?: any): Promise<{ cows: CowModel[], mptCowIdMap: Map<string, { testDt: string; monthAge: number }> }> {
      // Step 1: 4가지 데이터 소스에서 cowId 수집 (병렬 처리)
      const [genomeRequestCowIds, genomeCowIds, geneCowIds, mptCowIds] = await Promise.all([
        // 유전체 분석 의뢰가 있고, 유효한 형질 데이터가 있는 개체의 cowId만 조회
        // (genomeScore가 null이지만 anlysDt가 있는 경우 제외)
        this.genomeRequestRepository
          .createQueryBuilder('request')
          .innerJoin('request.cow', 'cow')
          .innerJoin('request.traitDetails', 'trait', 'trait.delDt IS NULL AND trait.traitEbv IS NOT NULL')
          .select('DISTINCT cow.cowId', 'cowId')
          .where('request.delDt IS NULL')
          .andWhere('request.requestDt IS NOT NULL')
          .getRawMany(),
        // 유전체 형질 데이터가 있는 cowId (유효한 EBV 값이 있는 경우만)
        this.genomeTraitDetailRepository
          .createQueryBuilder('trait')
          .select('DISTINCT trait.cowId', 'cowId')
          .where('trait.delDt IS NULL')
          .andWhere('trait.traitEbv IS NOT NULL')
          .getRawMany(),
        // 유전자 데이터가 있는 cowId
        this.geneDetailRepository
          .createQueryBuilder('gene')
          .select('DISTINCT gene.cowId', 'cowId')
          .where('gene.delDt IS NULL')
          .getRawMany(),
        // 번식능력(MPT) 데이터가 있는 cowId와 최신 검사일/월령
        // cowId별 최신 검사일 기준으로 중복 제거 (GROUP BY)
        this.mptRepository
          .createQueryBuilder('mpt')
          .select('mpt.cowId', 'cowId')
          .addSelect('MAX(mpt.testDt)', 'testDt')
          .addSelect('MAX(mpt.monthAge)', 'monthAge')
          .where('mpt.delDt IS NULL')
          .groupBy('mpt.cowId')
          .getRawMany(),
      ]);

      // Step 2: cowId 통합 (중복 제거)
      const allCowIds = [...new Set([
        ...genomeRequestCowIds.map(c => c.cowId).filter(Boolean),
        ...genomeCowIds.map(c => c.cowId).filter(Boolean),
        ...geneCowIds.map(c => c.cowId).filter(Boolean),
        ...mptCowIds.map(c => c.cowId).filter(Boolean),
      ])];

      // MPT cowId -> { testDt, monthAge } Map 생성
      const mptCowIdMap = new Map<string, { testDt: string; monthAge: number }>(
        mptCowIds
          .filter(c => c.cowId)
          .map(c => [c.cowId, { testDt: c.testDt, monthAge: c.monthAge }])
      );

      // 데이터가 있는 개체가 없으면 빈 배열 반환 (단, 테스트 농장 예외)
      const TEST_FARM_NO = 26; // 코쿤 테스트 농장

      // farmNo 체크: filterOptions.farmNo 또는 filterOptions.filters에서 추출
      let isTestFarm = Number(filterOptions?.farmNo) === TEST_FARM_NO;
      if (!isTestFarm && filterOptions?.filters) {
        const farmFilter = filterOptions.filters.find(
          (f: { field: string; value: number | number[] }) => f.field === 'cow.fkFarmNo'
        );
        if (farmFilter) {
          const farmNos = Array.isArray(farmFilter.value) ? farmFilter.value : [farmFilter.value];
          // 숫자/문자열 모두 처리 (프론트에서 문자열로 올 수 있음)
          isTestFarm = farmNos.map(Number).includes(TEST_FARM_NO);
        }
      }

      if (allCowIds.length === 0 && !isTestFarm) {
        return { cows: [], mptCowIdMap };
      }

      // Step 3: 해당 cowId로 개체 조회
      const queryBuilder = this.cowRepository
        .createQueryBuilder('cow')
        .leftJoinAndSelect('cow.farm', 'farm')
        .where('cow.delDt IS NULL');

      // 테스트 농장(26번)은 tb_cow 전체 조회, 그 외는 데이터 있는 개체만
      if (!isTestFarm && allCowIds.length > 0) {
        queryBuilder.andWhere('cow.cowId IN (:...cowIds)', { cowIds: allCowIds });
      }

      // farmNo가 직접 전달된 경우 처리 (프론트엔드 호환성)
      if (filterOptions?.farmNo) {
        queryBuilder.andWhere('cow.fkFarmNo = :farmNo', {
          farmNo: filterOptions.farmNo
        });
      }

      // FilterEngine 사용하여 동적 필터 적용 (페이지네이션 없이 전체 조회)
      if (filterOptions?.filters) {
        const result = await this.filterEngineService.executeFilteredQuery(
          queryBuilder,
          {
            ...filterOptions,
            pagination: { page: 1, limit: 10000 }, // 전체 조회 (프론트에서 페이지네이션 처리)
          },
        );
        // cowId 기준 중복 제거 (tb_cow에 같은 cowId가 여러 row일 수 있음)
        const uniqueCows = Array.from(
          new Map(result.data.map((cow: CowModel) => [cow.cowId, cow])).values()
        );
        return { cows: uniqueCows, mptCowIdMap };
      }

      // 필터 없으면 전체 조회 (최신순)
      const cows = await queryBuilder.orderBy('cow.regDt', 'DESC').getMany();
      // cowId 기준 중복 제거
      const uniqueCows = Array.from(
        new Map(cows.map(cow => [cow.cowId, cow])).values()
      );
      return { cows: uniqueCows, mptCowIdMap };
    }

  // ============================================================
  // 유전체(GENOME) 랭킹 메서드
  // ============================================================

  /**
   * 유전체 형질 기반 랭킹 적용 (Private)
   *
   * 계산 방식: 선택한 형질들의 EBV 가중 평균
   * - 각 형질에 weight(가중치) 적용 가능
   * - 모든 선택 형질이 있어야 점수 계산
   *
   * @param cows - 필터링된 개체 목록
   * @param traitConditions - 형질별 가중치 조건 배열
   * @returns 순위가 적용된 개체 목록 / 리스트에 전달 /  농가/보은군 차트 (farmBreedVal, regionBreedVal)
   * @example
   * traitConditions = [
   *   { traitNm: '도체중', weight: 8 },
   *   { traitNm: '근내지방도', weight: 10 }
   * ]
   */
  private async applyGenomeRanking(
    cows: CowModel[],
    inputTraitConditions: TraitRankingConditionDto[],
    mptCowIdMap: Map<string, { testDt: string; monthAge: number }>,
  ): Promise<any> {
    // traitConditions가 비어있으면 35개 전체 형질 사용 (개체상세, 대시보드와 동일)
    const traitConditions = inputTraitConditions && inputTraitConditions.length > 0
      ? inputTraitConditions
      : ALL_TRAITS.map(traitNm => ({ traitNm, weight: 1 }));

    // 각 개체별로 점수 계산
    const cowsWithScore = await Promise.all(
      cows.map(async (cow) => {
        // Step 1: 해당 개체의 최신 유전체 분석 의뢰 조회 (친자감별 확인용)
        const latestRequest = await this.genomeRequestRepository.findOne({
          where: { fkCowNo: cow.pkCowNo, delDt: IsNull() },
          order: { 
            requestDt: 'DESC', 
            regDt: 'DESC' },
        });

        // Step 2: 친자감별 확인 - 유효하지 않으면 분석 불가
        if (!latestRequest || !isValidGenomeAnalysis(latestRequest.chipSireName, latestRequest.chipDamName, cow.cowId)) {
          // 분석불가 사유 결정
          let unavailableReason: string | null = null;

          // EXCLUDED_COW_IDS에 포함된 개체 (모근 오염/불량 등 기타 사유)
          if (EXCLUDED_COW_IDS.includes(cow.cowId)) {
            unavailableReason = '분석불가';
          } else if (!latestRequest || !latestRequest.chipSireName) {
            // latestRequest 없거나 chipSireName이 null → '-' 표시 (프론트에서 null은 '-'로 표시)
            unavailableReason = null;
          } else if (latestRequest.chipSireName === '분석불가' || latestRequest.chipSireName === '정보없음') {
            // 분석불가, 정보없음 → 분석불가
            unavailableReason = '분석불가';
          } else if (latestRequest.chipSireName !== '일치') {
            // 불일치 등 그 외 → 부 불일치
            unavailableReason = '부 불일치';
          } else if (latestRequest.chipDamName === '불일치') {
            unavailableReason = '모 불일치';
          } else if (latestRequest.chipDamName === '이력제부재') {
            unavailableReason = '모 이력제부재';
          }

          const mptData = mptCowIdMap.get(cow.cowId);
          return {
            entity: {
              ...cow,
              unavailableReason,
              hasMpt: mptCowIdMap.has(cow.cowId),
              mptTestDt: mptData?.testDt || null,
              mptMonthAge: mptData?.monthAge || null,
              anlysDt: latestRequest?.requestDt ?? null,
            },
            sortValue: null,
            details: [],
          };
        }

        // Step 3: cowId로 직접 형질 상세 데이터 조회 (35개 형질의 EBV 값)
        const traitDetails = await this.genomeTraitDetailRepository.find({
          where: { cowId: cow.cowId, delDt: IsNull() },
        });

        // 형질 데이터가 없으면 점수 null (친자는 일치하지만 형질 데이터 없음)
        if (traitDetails.length === 0) {
          const mptData = mptCowIdMap.get(cow.cowId);
          return {
            entity: {
              ...cow,
              unavailableReason: '형질정보없음',
              hasMpt: mptCowIdMap.has(cow.cowId),
              mptTestDt: mptData?.testDt || null,
              mptMonthAge: mptData?.monthAge || null,
            },
            sortValue: null,
            details: [],
          };
        }

        // Step 4: 가중 합계 계산 ====================================================
        let weightedSum = 0;    // 가중치 적용된 EBV 합계
        let totalWeight = 0;    // 총 가중치
        let hasAllTraits = true; // 모든 선택 형질 존재 여부
        const details: any[] = []; // 계산 상세 내역

        // 사용자가 선택한 각 형질에 대해 처리
        for (const condition of traitConditions) {
          // 형질명으로 해당 형질 데이터 찾기
          const trait = traitDetails.find((d) => d.traitName === condition.traitNm);
          const weight = condition.weight || 1;  // 가중치 (기본값: 1)

          if (trait && trait.traitEbv !== null) {
            // EBV 값이 있으면 가중치 적용하여 합산
            const ebv = Number(trait.traitEbv);
            // 등지방두께 등 낮을수록 좋은 형질은 부호 반전
            const isNegativeTrait = NEGATIVE_TRAITS.includes(condition.traitNm);
            const adjustedEbv = isNegativeTrait ? -ebv : ebv;
            weightedSum += adjustedEbv * weight;  // EBV × 가중치
            totalWeight += weight;        // 가중치 누적

            // 상세 내역 저장 (응답용)
            details.push({
              code: condition.traitNm,  // 형질명
              value: adjustedEbv,       // EBV 값 (부호 반전 적용)
              weight,                   // 적용된 가중치
            });
          } else {
            // 형질이 없으면 플래그 설정
            hasAllTraits = false;
          }
        }

        // Step 6: 최종 점수 계산 (가중 합계)
        // 모든 선택 형질이 있어야만 점수 계산
        const sortValue = (hasAllTraits && totalWeight > 0)
          ? weightedSum  // 가중 합계 (개체상세, 대시보드와 동일한 방식)
          : null;

        // Step 7: 응답 데이터 구성 (반환 값)
        const mptData = mptCowIdMap.get(cow.cowId);
        return {
          entity: {
            ...cow,
            anlysDt: latestRequest.requestDt,  // 분석일자 추가
            hasMpt: mptCowIdMap.has(cow.cowId), // MPT 검사 여부
            mptTestDt: mptData?.testDt || null, // MPT 검사일
            mptMonthAge: mptData?.monthAge || null, // MPT 월령
          },
          sortValue,        // 계산된 종합 점수 (선발지수)
          details,          // 점수 계산에 사용된 형질별 상세
          ranking: {
            requestNo: latestRequest.pkRequestNo,   // 분석 의뢰 번호
            requestDt: latestRequest.requestDt,     // 분석 의뢰일
            traits: traitDetails.map((d) => ({      // 전체 형질 데이터
              traitName: d.traitName,               // 형질명
              traitVal: d.traitVal,                 // 실측값
              traitEbv: d.traitEbv,                 // EBV (표준화육종가)
              traitPercentile: d.traitPercentile,  // 백분위
            })),
          },
        };
      }),
    );

    // ========================================
    // 백엔드 응답 예시
    // ========================================
    // {
    //   "items": [
    //     {
    //       "entity": { "cowId": "KOR123456", "cowNm": "뽀삐", ... },
    //       "sortValue": 85.5,     // 가중 평균 점수
    //       "rank": 1,             // 순위
    //       "ranking": {
    //         "requestNo": 100,
    //         "traits": [
    //           { "traitName": "도체중", "traitVal": 450, "traitEbv": 12.5 },
    //           { "traitName": "등심단면적", "traitVal": 95, "traitEbv": 8.3 }
    //         ]
    //       }
    //     }
    //   ],
    //   "total": 100,
    //   "criteriaType": "GENOME"
    // }

    // Step 8: genomeScore(sortValue)가 null/undefined이고 anlysDt가 있는 데이터 제외
    // 단, 부모불일치인 개체는 조회되어야 하므로 제외하지 않음
    const filteredCows = cowsWithScore.filter((item) => {
      const sortValue = item.sortValue;
      const anlysDt = (item.entity as any).anlysDt;
      const unavailableReason = (item.entity as any).unavailableReason;
      
      // 부모불일치 관련 사유인 경우는 조회 유지
      const isParentMismatch = unavailableReason === '부 불일치' || 
                               unavailableReason === '모 불일치' || 
                               unavailableReason === '모 이력제부재';
      
      // sortValue가 null/undefined이고 anlysDt가 있지만, 부모불일치가 아닌 경우만 제외
      if ((sortValue === null || sortValue === undefined) && anlysDt && !isParentMismatch) {
        return false;
      }
      return true;
    });

    // Step 9: 점수 기준 내림차순 정렬
    const sorted = filteredCows.sort((a, b) => {
      // null 값은 맨 뒤로
      if (a.sortValue === null && b.sortValue === null) return 0;
      if (a.sortValue === null) return 1;
      if (b.sortValue === null) return -1;
      // 점수 높은 순 (내림차순)
      return b.sortValue - a.sortValue;
    });

    // Step 10: 순위 부여 후 반환
    return {
      items: sorted.map((item, index) => ({
        ...item,
        rank: index + 1,  // 1부터 시작하는 순위
      })),
      total: sorted.length,
      criteriaType: RankingCriteriaType.GENOME,
    };
  }

}
