/**
 * ============================================================
 * 개체(Cow) 서비스
 * ============================================================
 *
 * 사용 페이지: 개체 목록 페이지 (/cow)
 *
 * 주요 기능:
 * 1. 기본 개체 목록 조회 (findAll, findByFarmId)
 * 2. 개체 단건 조회 (findOne, findByCowId)
 * 3. 랭킹 적용 개체 목록 조회 (findAllWithRanking)
 *    - GENOME: 35개 형질 EBV 가중 평균
 * 4. 개체 CRUD (create, update, remove)
 * ============================================================
 */
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 { FilterEngineService } from '../shared/filter/filter-engine.service';
import {
  RankingRequestDto,
  RankingCriteriaType,
  TraitRankingCondition,
} from './dto/ranking-request.dto';
import { isValidGenomeAnalysis } from '../common/config/GenomeAnalysisConfig';

/**
 * 낮을수록 좋은 형질 목록 (부호 반전 필요)
 * - 등지방두께: 지방이 얇을수록(EBV가 낮을수록) 좋은 형질
 * - 선발지수 계산 시 EBV 부호를 반전하여 적용
 */
const NEGATIVE_TRAITS = ['등지방두께'];

/**
 * 개체(소) 관리 서비스
 *
 * 담당 기능:
 * - 개체 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>,

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

  // ============================================================
  // 기본 조회 메서드
  // ============================================================

  /**
   * 전체 개체 목록 조회
   *
   * @returns 삭제되지 않은 모든 개체 목록
   * - farm 관계 데이터 포함
   * - 등록일(regDt) 기준 내림차순 정렬 (최신순)
   */
  async findAll(): Promise<CowModel[]> {
    return this.cowRepository.find({
      where: { delDt: IsNull() },   // 삭제되지 않은 데이터만
      relations: ['farm'],          // 농장 정보 JOIN
      order: { regDt: 'DESC' },     // 최신순 정렬
    });
  }

  /**
   * 농장별 개체 목록 조회
   *
   * @param farmNo - 농장 PK 번호
   * @returns 해당 농장의 모든 개체 목록 (최신순)
   */
  async findByFarmId(farmNo: number): Promise<CowModel[]> {
    return this.cowRepository.find({
      where: { fkFarmNo: farmNo, delDt: IsNull() },
      relations: ['farm'],
      order: { regDt: 'DESC' },
    });
  }

  /**
   * 개체 PK로 단건 조회
   *
   * @param id - 개체 PK 번호 (pkCowNo)
   * @returns 개체 정보 (farm 포함)
   * @throws NotFoundException - 개체를 찾을 수 없는 경우
   */
  async findOne(id: number): Promise<CowModel> {
    const cow = await this.cowRepository.findOne({
      where: { pkCowNo: id, delDt: IsNull() },
      relations: ['farm'],
    });
    if (!cow) {
      throw new NotFoundException(`Cow #${id} not found`);
    }
    return cow;
  }

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

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

  /**
   * 랭킹 적용 개체 목록 조회 (메인 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: 필터 조건에 맞는 개체 목록 조회
    const cows = await this.getFilteredCows(filterOptions);

    // Step 3: 랭킹 기준에 따라 분기 처리
    switch (criteriaType) {
      // 유전체 형질 기반 랭킹 (35개 형질 EBV 가중 평균)
      case RankingCriteriaType.GENOME:
        return this.applyGenomeRanking(cows, rankingOptions.traitConditions || []);

      // 기본값: 랭킹 없이 순서대로 반환
      default:
        return {
          items: cows.map((cow, index) => ({
            entity: cow,
            rank: index + 1,
            sortValue: 0,
          })),
          total: cows.length,
          criteriaType,
        };
    }
  }

  /**
   * 필터 조건에 맞는 개체 목록 조회 (Private)
   *
   * @param filterOptions - 필터/정렬/페이지네이션 옵션
   * @returns 필터링된 개체 목록
   */
  private async getFilteredCows(filterOptions?: any): Promise<CowModel[]> {
    // QueryBuilder로 기본 쿼리 구성
    const queryBuilder = this.cowRepository
      .createQueryBuilder('cow')
      .leftJoinAndSelect('cow.farm', 'farm')  // 농장 정보 JOIN
      .where('cow.delDt IS NULL');            // 삭제되지 않은 데이터만

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

    // FilterEngine 사용하여 동적 필터 적용
    if (filterOptions?.filters) {
      const result = await this.filterEngineService.executeFilteredQuery(
        queryBuilder,
        filterOptions,
      );
      return result.data;
    }

    // 필터 없으면 전체 조회 (최신순)
    return queryBuilder.orderBy('cow.regDt', 'DESC').getMany();
  }

  // ============================================================
  // 유전체(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: TraitRankingCondition[],
  ): Promise<any> {
    // 35개 전체 형질 (기본값)
    const ALL_TRAITS = [
      '12개월령체중',
      '도체중', '등심단면적', '등지방두께', '근내지방도',
      '체고', '십자', '체장', '흉심', '흉폭', '고장', '요각폭', '좌골폭', '곤폭', '흉위',
      '안심weight', '등심weight', '채끝weight', '목심weight', '앞다리weight',
      '우둔weight', '설도weight', '사태weight', '양지weight', '갈비weight',
      '안심rate', '등심rate', '채끝rate', '목심rate', '앞다리rate',
      '우둔rate', '설도rate', '사태rate', '양지rate', '갈비rate',
    ];

    // 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 = '분석불가';
          if (latestRequest) {
            if (latestRequest.chipSireName !== '일치') {
              unavailableReason = '부 불일치';
            } else if (latestRequest.chipDamName === '불일치') {
              unavailableReason = '모 불일치';
            } else if (latestRequest.chipDamName === '이력제부재') {
              unavailableReason = '모 이력제부재';
            }
          }
          return { entity: { ...cow, unavailableReason }, 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) {
          return { entity: { ...cow, unavailableReason: '형질정보없음' }, 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: 응답 데이터 구성
        return {
          entity: {
            ...cow,
            anlysDt: latestRequest.requestDt,  // 분석일자 추가
          },
          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: 점수 기준 내림차순 정렬
    const sorted = cowsWithScore.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 9: 순위 부여 후 반환
    return {
      items: sorted.map((item, index) => ({
        ...item,
        rank: index + 1,  // 1부터 시작하는 순위
      })),
      total: sorted.length,
      criteriaType: RankingCriteriaType.GENOME,
    };
  }

  // ============================================================
  // CRUD 메서드
  // ============================================================

  /**
   * 새로운 개체 생성
   *
   * @param data - 생성할 개체 데이터
   * @returns 생성된 개체 엔티티
   */
  async create(data: Partial<CowModel>): Promise<CowModel> {
    const cow = this.cowRepository.create(data);
    return this.cowRepository.save(cow);
  }

  /**
   * 개체 정보 수정
   *
   * @param id - 개체 PK 번호
   * @param data - 수정할 데이터
   * @returns 수정된 개체 엔티티
   * @throws NotFoundException - 개체를 찾을 수 없는 경우
   */
  async update(id: number, data: Partial<CowModel>): Promise<CowModel> {
    await this.findOne(id);  // 존재 여부 확인
    await this.cowRepository.update(id, data);
    return this.findOne(id);  // 수정된 데이터 반환
  }

  /**
   * 개체 삭제 (Soft Delete)
   *
   * 실제 삭제가 아닌 delDt 컬럼에 삭제 시간 기록
   *
   * @param id - 개체 PK 번호
   * @throws NotFoundException - 개체를 찾을 수 없는 경우
   */
  async remove(id: number): Promise<void> {
    const cow = await this.findOne(id);  // 존재 여부 확인
    await this.cowRepository.softRemove(cow);
  }
}
