import {
  ADMIN_SUCCESS,
  DUPLICATE_UPLOAD_FILE_NAME_FOUND,
  NOT_FOUND_FILE,
  NOT_FOUND_RECOVERY_DATA,
  NOT_FOUND_REQUIRED_PARAMETER,
  UPLOAD_DATA_NOT_FOUND,
} from '@api/constants/admin.constant';
import {
  AdminViewer,
  isAdminViewerAllowed,
} from '@api/helpers/admin-viewer.helper';
import { formatDuplicateErrorMsg } from '@api/helpers/excelErrorFormat';
import { findDuplicate } from '@api/helpers/findDuplicate';
import { mysqlErrorWrapper } from '@lib/drizzle/src/drizzle.utill';
import {
  BadRequestException,
  Injectable,
  ForbiddenException,
} from '@nestjs/common';
import { CellValue, Workbook, stream } from 'exceljs';
import { Response } from 'express';
import { SubscribeService } from '../subscribe/subscribe/subscribe.service';
import {
  CreateMedicationStandardDto,
  DeleteMedicationStandardDto,
  GetMedicationStandardFlatDto,
  UpdateMedicationStandardDto,
} from './medication-standard.dto';
import { MedicationStandardRepository } from './medication-standard.repository';

@Injectable()
export class MedicationStandardService {
  constructor(
    private medicationStandardRepository: MedicationStandardRepository,
    private subscribeService: SubscribeService,
  ) {}

  /**
   * 정회원 경로(oddId)의 소유자 검증. memId를 인자로만 받고 쓰지 않아
   * oddId만 바꾸면 남의 데이터가 조회됐다. B2C(bodId)/고객사(codId)는 AnalysisTargetGuard 가 막는다.
   */
  private async assertOddOwner(
    oddId?: number,
    memId?: number,
    admin?: AdminViewer,
  ) {
    if (!oddId) return;
    const owner = await this.medicationStandardRepository.findOrderOwner(oddId);
    if (owner?.memId != null && Number(owner.memId) === Number(memId ?? -1)) {
      return;
    }
    //* 관리자 admin-view(iframe) 통과 경로(2026-07): 통합관리자 전체, 기업/조직관리자는 자기 기업 건만
    if (isAdminViewerAllowed(admin, owner?.agcId, oddId)) return;
    throw new ForbiddenException('접근 권한이 없습니다');
  }

  // ==================== 대분류 (Category) ====================
  async getCategoryList() {
    const list = await this.medicationStandardRepository.getCategoryList();
    return { success: true, list };
  }

  async createCategory(mcName: string) {
    // 공백 제거 후 중복 체크
    const isDuplicate =
      await this.medicationStandardRepository.checkDuplicateCategoryName(
        mcName,
      );
    if (isDuplicate) {
      throw new BadRequestException({
        code: 'DUPLICATE_NAME',
        msg: '중복된 분류명 또는 분류항목명으로 저장에 실패했습니다.',
      });
    }

    // 생성
    const categoryId =
      await this.medicationStandardRepository.createCategory(mcName);
    return { success: true, categoryId };
  }

  // ==================== 중분류 (Standard) ====================
  async getStandardFlatList(dto: GetMedicationStandardFlatDto) {
    const where = this.medicationStandardRepository.buildWhereCondition(dto);
    const orderBy = this.medicationStandardRepository.buildOrderBy(dto);

    const page = dto.page || 1;
    const limit = dto.limit || 10;
    const offset = (page - 1) * limit;

    const list = await this.medicationStandardRepository.getStandardFlatList(
      where,
      orderBy,
      limit,
      offset,
    );
    const count = await this.medicationStandardRepository.countStandard(where);

    return { count, list };
  }

  async getStandardDetail(mscId: number) {
    return await this.medicationStandardRepository.getStandardDetail(mscId);
  }

  async createStandard(dto: CreateMedicationStandardDto) {
    // 공백 제거 후 분류항목명(mscCode) 중복 검사
    const isDuplicate =
      await this.medicationStandardRepository.checkDuplicateStandardCode(
        dto.mscCode,
      );
    if (isDuplicate) {
      throw new BadRequestException({
        code: 'DUPLICATE_NAME',
        msg: '중복된 분류명 또는 분류항목명으로 저장에 실패했습니다.',
      });
    }

    const insertId = await this.medicationStandardRepository
      .createStandard({
        mscCategoryId: dto.mscCategoryId,
        mscCode: dto.mscCode,
        mscName: dto.mscName,
      })
      .catch((error) => {
        mysqlErrorWrapper(error);
      });

    return { success: true, mscId: insertId };
  }

  async updateStandard(mscId: number, dto: UpdateMedicationStandardDto) {
    // 현재 데이터 조회
    const current =
      await this.medicationStandardRepository.getStandardDetail(mscId);
    if (!current) {
      throw new BadRequestException('데이터를 찾을 수 없습니다.');
    }

    // 분류항목명(mscCode) 변경 시 중복 검사 (공백 제거 후 비교)
    if (dto.mscCode) {
      const normalizedNew = dto.mscCode.replace(/\s/g, '');
      const normalizedCurrent = (current.mscCode || '').replace(/\s/g, '');
      if (normalizedNew !== normalizedCurrent) {
        const isDuplicate =
          await this.medicationStandardRepository.checkDuplicateStandardCode(
            dto.mscCode,
            mscId,
          );
        if (isDuplicate) {
          throw new BadRequestException({
            code: 'DUPLICATE_NAME',
            msg: '중복된 분류명 또는 분류항목명으로 저장에 실패했습니다.',
          });
        }
      }
    }

    await this.medicationStandardRepository
      .updateStandard(mscId, {
        mscCategoryId: dto.mscCategoryId,
        mscCode: dto.mscCode,
        mscName: dto.mscName,
      })
      .catch((error) => {
        mysqlErrorWrapper(error);
      });

    return { success: true };
  }

  async deleteStandards(dto: DeleteMedicationStandardDto) {
    await this.medicationStandardRepository
      .deleteStandards(dto.ids)
      .catch((error) => {
        mysqlErrorWrapper(error);
      });

    return { success: true };
  }

  // ==================== 엑셀 기능 ====================
  /** 엑셀 다운로드 */
  async downloadExcel(dto: GetMedicationStandardFlatDto, res: Response) {
    const where = this.medicationStandardRepository.buildWhereCondition(dto);
    const list = await this.medicationStandardRepository.getAllList(where);

    const workbook = new stream.xlsx.WorkbookWriter({ stream: res });
    const worksheet = workbook.addWorksheet('투약로그관리');

    worksheet.columns = [
      { header: '분류명', key: 'categoryName', width: 40 },
      { header: '분류항목명', key: 'code', width: 60 },
      { header: '분류효능', key: 'name', width: 60 },
      { header: '등록일', key: 'createdAt', width: 20 },
    ];

    list.forEach((item: any) => {
      worksheet
        .addRow({
          categoryName: item.mcName || '',
          code: item.mscCode || '',
          name: item.mscName || '',
          createdAt: item.mscCreatedAt || '',
        })
        .commit();
    });

    worksheet.commit();
    await workbook.commit();
  }

  /** 업로드 양식 다운로드 */
  async downloadUploadForm(res: Response) {
    const dataGenerator = [
      {
        categoryName: '각성제, 흥분제',
        code: '아모닐정150밀리그램(아모다피닐(미분화))_(0.15g/1정)',
        name: '폐쇄성수면무호흡증, 기면증, 과다졸음 개선',
      },
      {
        categoryName: '간질환환용제',
        code: '지티비캡슐_(1캡슐)',
        name: '간 질환 치료/개선제',
      },
      {
        categoryName: '건위소화제',
        code: '판개롱정',
        name: '소화불량, 식욕부진 개선',
      },
    ];

    const workbook = new stream.xlsx.WorkbookWriter({ stream: res });
    const worksheet = workbook.addWorksheet('투약로그관리 업로드양식');

    worksheet.columns = [
      { header: '분류명(*필수)', key: 'categoryName', width: 40 },
      { header: '분류항목명(*필수)', key: 'code', width: 60 },
      { header: '분류효능(*필수)', key: 'name', width: 60 },
    ];

    dataGenerator.forEach((item: any) => {
      worksheet.addRow(item).commit();
    });

    worksheet.commit();
    await workbook.commit();
  }

  /** Excel 파일 업로드 */
  async uploadExcel(file: Express.Multer.File) {
    if (!file) throw new BadRequestException(NOT_FOUND_FILE);

    console.log('1. 파일 수신:', file.originalname, file.size);
    const uploadData = await this.extractDataFromExcel(file.buffer);
    console.log('2. 데이터 추출 완료:', {
      categories: uploadData.categories.length,
      standards: uploadData.standards.length,
    });
    await this.excelValidate(uploadData);
    console.log('3. 유효성 검사 완료');
    const result = await this.dataUpload(uploadData);
    console.log('4. 업로드 완료');
    return result;
  }

  /** Excel 데이터 추출 */
  private async extractDataFromExcel(buffer: Buffer) {
    const workbook = new Workbook();
    await workbook.xlsx.load(buffer as any);
    const sheet = workbook.getWorksheet(1);

    const uploadData: {
      categories: any[];
      standards: any[];
    } = {
      categories: [],
      standards: [],
    };

    const categoryNames = new Set<string>();

    sheet?.eachRow((row, rowNumber) => {
      if (rowNumber === 1) return; // 헤더 스킵

      const [categoryName, code, name] = (row.values as Array<CellValue>).slice(
        1,
      );

      if (!categoryName || !code || !name) {
        throw new BadRequestException(NOT_FOUND_REQUIRED_PARAMETER);
      }

      const categoryNameStr = categoryName.toString().trim();
      const codeStr = code.toString().trim();
      const nameStr = name.toString().trim();

      // 대분류 수집 (중복 방지)
      categoryNames.add(categoryNameStr);

      // 중분류 데이터 추가
      uploadData.standards.push({
        categoryName: categoryNameStr,
        mscCode: codeStr,
        mscName: nameStr,
      });
    });

    // Set을 배열로 변환하여 대분류 데이터 생성
    uploadData.categories = Array.from(categoryNames).map((name) => ({
      mcName: name,
    }));

    return uploadData;
  }

  /** 업로드 데이터 유효성 검사 */
  private async excelValidate(uploadData: any) {
    if (uploadData.categories.length === 0) {
      throw new BadRequestException(UPLOAD_DATA_NOT_FOUND);
    }

    // 대분류 중복 확인
    const categoryNames = uploadData.categories.map((item: any) => item.mcName);
    const categoryDuplicates = findDuplicate(categoryNames);
    if (categoryDuplicates.length > 0) {
      const sliceDuplicates = categoryDuplicates.slice(0, 3);
      throw new BadRequestException(
        formatDuplicateErrorMsg(
          sliceDuplicates,
          categoryDuplicates.length > 3 ? categoryDuplicates.length : 0,
          DUPLICATE_UPLOAD_FILE_NAME_FOUND,
        ),
      );
    }

    // 중분류 중복 확인 (코드 기준)
    const codes = uploadData.standards.map((item: any) => item.mscCode);
    const codeDuplicates = findDuplicate(codes);
    if (codeDuplicates.length > 0) {
      const sliceDuplicates = codeDuplicates.slice(0, 3);
      throw new BadRequestException(
        formatDuplicateErrorMsg(
          sliceDuplicates,
          codeDuplicates.length > 3 ? codeDuplicates.length : 0,
          DUPLICATE_UPLOAD_FILE_NAME_FOUND,
        ),
      );
    }

    return this;
  }

  /** 데이터 업로드 */
  private async dataUpload(
    data: any,
  ): Promise<{ code: string; message: string }> {
    console.log(
      '4. Repository로 전달할 데이터:',
      JSON.stringify(data, null, 2),
    );
    await this.medicationStandardRepository.uploadExcel(data).catch((error) => {
      console.error('Upload error:', error);
      throw new BadRequestException(`업로드 실패: ${error.message}`);
    });
    return ADMIN_SUCCESS;
  }

  // ==================== 5년 내 30일이상 투약 그룹 ====================

  /** 5년 내 30일이상 투약 카테고리 그룹 조회 */
  async getDosing30DayGroups(
    oddId?: number,
    bodId?: number,
    codId?: number,
    memId?: number,
  ) {
    const data = await this.medicationStandardRepository.getDosing30DayGroups(
      oddId,
      bodId,
      codId,
    );
    return { success: true, data };
  }

  /** 이전 데이터 복구 */
  async recovery() {
    const hasBackup = await this.medicationStandardRepository.doesExistBackup();
    if (!hasBackup) {
      throw new BadRequestException(NOT_FOUND_RECOVERY_DATA);
    }

    await this.medicationStandardRepository.recovery().catch((error) => {
      mysqlErrorWrapper(error);
    });

    return ADMIN_SUCCESS;
  }

  // ==================== 투약기준 추천 ====================

  /** 투약기준 추천 조회 */
  async getRecommendedMedication(
    oddId?: number,
    bodId?: number,
    codId?: number,
    memId?: number,
    admin?: AdminViewer,
  ) {
    await this.assertOddOwner(oddId, memId, admin);
    // 다른 분석 탭들(ANS001, ANS003 등)과 동일하게 구독 체크 제거
    // JWT 인증만으로 데이터 조회 가능
    const data = await this.getRecommendedMedicationForPdf(oddId, bodId, codId);

    return {
      success: true,
      data,
    };
  }

  /** 투약기준 추천 조회 (PDF용 - 권한 체크 없음) */
  async getRecommendedMedicationForPdf(
    oddId?: number,
    bodId?: number,
    codId?: number,
  ) {
    const rawData =
      await this.medicationStandardRepository.getRecommendedMedication(
        oddId,
        bodId,
        codId,
      );

    // 카테고리별로 그룹화
    const categoryMap = new Map<
      string,
      {
        totalDay: number;
        items: Array<{ itemName: string; efficacy: string; itemDay: number }>;
      }
    >();

    rawData.forEach((item) => {
      const categoryName = item.categoryName ?? '';
      const itemName = item.itemName ?? '';
      const efficacy = item.efficacy ?? '';
      const { days } = item;

      if (!categoryMap.has(categoryName)) {
        categoryMap.set(categoryName, {
          totalDay: 0,
          items: [],
        });
      }

      const category = categoryMap.get(categoryName)!;
      category.totalDay += Number(days);
      category.items.push({
        itemName,
        efficacy,
        itemDay: Number(days),
      });
    });

    // Map을 배열로 변환 및 totalDay 내림차순 정렬
    return Array.from(categoryMap.entries())
      .map(([categoryName, { totalDay, items }]) => ({
        categoryName,
        totalDay,
        items,
      }))
      .sort((a, b) => b.totalDay - a.totalDay);
  }

  //* 약품명 → 허가 효능효과 대조. 판정 응답의 빈 `indication` 을 메우는 쪽이 유일한 호출자다
  //* (`AnalyzeService.getDosing30DayDiagnosisGroups`).
  async findEfficacyByDrugNames(
    drugNames: string[],
  ): Promise<Map<string, string>> {
    return await this.medicationStandardRepository.findEfficacyByDrugNames(
      drugNames,
    );
  }
}
