/*
 * Copyright (c) 2025-2026 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as fs from 'node:fs';
import * as path from 'node:path';
import { runWithIOLimit, mapWithLimit, PROJECTS_PER_DIR_CONCURRENCY } from './IoLimiter';
import { Logger } from '../../Logger';
import type { ProblemInfo } from '../../ProblemInfo';
import * as mk from '../../utils/consts/MapKeyConst';
import { countFiles } from './CountFile';
import { countNapiFiles } from './CountNapiFile';
import type { ProblemNumbersInfo } from './ProblemNumbersInfo';
import type { ProblemStatisticsInfo } from './ProblemStatisticsInfo';
import type { RuleDetailedErrorInfo } from './RuleDetailedErrorInfo';
import type { StatisticsReportInPutInfo } from './StatisticsReportInPutInfo';
import type { TimeRecorder } from './TimeRecorder';
import { WorkLoadInfo } from './WorkLoadInfo';
import { getAllLinterRules } from '../../utils/functions/ConfiguredRulesProcess';

export function getProblemStatisticsInfo(
  problemNumbers: ProblemNumbersInfo,
  ruleToNumbersMap: Map<string, number>,
  ruleToAutoFixedNumbersMap: Map<string, number>,
  timeRecorder: TimeRecorder,
  WorkLoadInfo?: WorkLoadInfo
): ProblemStatisticsInfo {
  const problemNumberMap: Map<string, number> = new Map();
  problemNumberMap.set(mk.TOTAL_PROBLEMS, problemNumbers.totalProblemNumbers);
  problemNumberMap.set(mk.ONE_POINT_ONE_PROBLEMS, problemNumbers.arkOnePointOneProblemNumbers);
  problemNumberMap.set(mk.ONE_POINT_TWO_PROBLEMS, problemNumbers.arkOnePointTWOProblemNumbers);
  problemNumberMap.set(mk.CAN_BE_AUTO_FIXED_PROBLEMS_NUMBERS, problemNumbers.canBeAutoFixedproblemNumbers);
  problemNumberMap.set(mk.NEED_TO_NAMUAL_FIX_PROBLEM_NUMBERS, problemNumbers.needToManualFixproblemNumbers);

  const scanTime = timeRecorder.getScanTime();
  const migrationTime = timeRecorder.getMigrationTime();
  const usedTimeMap: Map<string, string> = new Map();
  usedTimeMap.set(mk.SCAN_TIME, scanTime);
  usedTimeMap.set(mk.MIGRATION_TIME, migrationTime);

  const detailRuleProblemsMap: Map<string, RuleDetailedErrorInfo> = new Map();
  ruleToNumbersMap.forEach((value, key) => {
    const ruleDetailedErrorInfo: RuleDetailedErrorInfo = {
      rule: key,
      problemNumbers: value,
      canBeAutoFixedMumbers: ruleToAutoFixedNumbersMap.get(key) ?? 0
    };
    detailRuleProblemsMap.set(key, ruleDetailedErrorInfo);
  });

  const problemStatisticsInfo: ProblemStatisticsInfo = {
    problems: Object.fromEntries(problemNumberMap),
    usedTime: Object.fromEntries(usedTimeMap),
    WorkLoadInfo: WorkLoadInfo,
    eachRuleProblemsDetail: Array.from(detailRuleProblemsMap.values())
  };
  return problemStatisticsInfo;
}

export function getArktsOnePointOneProlemNumbers(problems: ProblemInfo[]): number {
  let problemNumbersForATSOnePointOne: number = 0;
  const signForOne: string = 's2d';
  problems.forEach((problem) => {
    if (problem.rule !== undefined) {
      if (problem.rule.includes(signForOne)) {
        problemNumbersForATSOnePointOne = problemNumbersForATSOnePointOne + 1;
      }
    }
  });
  return problemNumbersForATSOnePointOne;
}

export function accumulateRuleNumbers(
  problems: ProblemInfo[],
  ruleToNumbersMap: Map<string, number>,
  ruleToAutoFixedNumbersMap: Map<string, number>
): void {
  const regex = /.*\(([^)]+)\)[^(]*$/;
  problems.forEach((problem) => {
    if (problem.rule !== undefined) {
      const match = problem.rule.match(regex);
      if (match?.[1]?.trim()) {
        if (problem.autofix && problem.autofixable) {
          const currentNumber = ruleToAutoFixedNumbersMap.get(match[1]) || 0;
          ruleToAutoFixedNumbersMap.set(match[1], currentNumber + 1);
        }
        const currentNumber = ruleToNumbersMap.get(match[1]) || 0;
        ruleToNumbersMap.set(match[1], currentNumber + 1);
      }
    }
  });
}

export async function generateReportFile(reportName: string, reportData, reportPath?: string): Promise<void> {
  let reportFilePath = path.join(reportName);
  if (reportPath !== undefined) {
    reportFilePath = path.join(path.normalize(reportPath), reportName);
  }
  try {
    await runWithIOLimit(() => {
      return fs.promises.mkdir(path.dirname(reportFilePath), { recursive: true });
    });
    await runWithIOLimit(() => {
      return fs.promises.writeFile(reportFilePath, JSON.stringify(reportData, null, 2));
    });
  } catch (error) {
    Logger.error(`Error generating report file:${error}`);
  }
}

export function generateReportFileSync(reportName: string, reportData: any, reportPath?: string): void {
  let reportFilePath = reportName;
  if (reportPath !== undefined) {
    reportFilePath = path.join(path.normalize(reportPath), reportName);
  }
  try {
    fs.mkdirSync(path.dirname(reportFilePath), { recursive: true });
    fs.writeFileSync(reportFilePath, JSON.stringify(reportData, null, 2));
  } catch (error) {
    Logger.error(`Error generating report file:${error}`);
  }
}

export function getMapValueSum(map: Map<any, number>): number {
  let result = 0;
  for (const value of map.values()) {
    result += value;
  }
  return result;
}

export async function generateScanProbelemStatisticsReport(
  statisticsReportInPutInfo: StatisticsReportInPutInfo
): Promise<void> {
  const canBeAutoFixedproblemNumbers = getMapValueSum(statisticsReportInPutInfo.ruleToAutoFixedNumbersMap);
  const problemNumbers: ProblemNumbersInfo = {
    totalProblemNumbers: statisticsReportInPutInfo.totalProblemNumbers,
    arkOnePointOneProblemNumbers: statisticsReportInPutInfo.arkOnePointOneProblemNumbers,
    arkOnePointTWOProblemNumbers:
      statisticsReportInPutInfo.totalProblemNumbers - statisticsReportInPutInfo.arkOnePointOneProblemNumbers,
    canBeAutoFixedproblemNumbers: canBeAutoFixedproblemNumbers,
    needToManualFixproblemNumbers: statisticsReportInPutInfo.totalProblemNumbers - canBeAutoFixedproblemNumbers
  };
  const projectFolderList = statisticsReportInPutInfo.cmdOptions.linterOptions.projectFolderList!;
  const workLoadInfo = await getWorkLoadInfo(projectFolderList);
  workLoadInfo.calculateFixRate(problemNumbers);
  const statisticsReportData = getProblemStatisticsInfo(
    problemNumbers,
    getProcessedRuleToNumbersMap(statisticsReportInPutInfo.ruleToNumbersMap, statisticsReportInPutInfo.allLinterRules),
    statisticsReportInPutInfo.ruleToAutoFixedNumbersMap,
    statisticsReportInPutInfo.timeRecorder,
    workLoadInfo
  );
  await generateReportFile(
    statisticsReportInPutInfo.statisticsReportName,
    statisticsReportData,
    statisticsReportInPutInfo.cmdOptions.outputFilePath
  );
}

function getProcessedRuleToNumbersMap(
  ruleToNumbersMap: Map<string, number>,
  allLinterRules: string[]
): Map<string, number> {
  const processedRuleToNumbersMap: Map<string, number> = new Map();
  const homecheckRuleToNumbersMap: Map<string, number> = ruleToNumbersMap;
  allLinterRules.forEach((ruleName) => {
    const ruleNumber = ruleToNumbersMap.get(ruleName) || 0;
    homecheckRuleToNumbersMap.delete(ruleName);
    processedRuleToNumbersMap.set(ruleName, ruleNumber);
  });

  homecheckRuleToNumbersMap.forEach((number, ruleName) => {
    processedRuleToNumbersMap.set(ruleName, number);
  });

  return processedRuleToNumbersMap;
}

export function generateMigrationStatisicsReport(
  lintResults: Map<string, ProblemInfo[]> | undefined,
  timeRecorder: TimeRecorder,
  outputFilePath?: string
): void {
  const ruleToNumbersMap: Map<string, number> = new Map();
  const ruleToAutoFixedNumbersMap: Map<string, number> = new Map();
  let arkOnePointOneProblemNumbers: number = 0;

  if (lintResults === undefined) {
    return;
  }
  const problemsInfoAfterAutofixed = lintResults;
  for (const problems of problemsInfoAfterAutofixed.values()) {
    accumulateRuleNumbers(problems, ruleToNumbersMap, ruleToAutoFixedNumbersMap);
    arkOnePointOneProblemNumbers = arkOnePointOneProblemNumbers + getArktsOnePointOneProlemNumbers(problems);
  }

  let totalProblemNumbers: number = 0;
  for (const problems of problemsInfoAfterAutofixed.values()) {
    totalProblemNumbers += problems.length;
  }

  const canBeAutoFixedproblemNumbers = getMapValueSum(ruleToAutoFixedNumbersMap);

  const problemNumbers: ProblemNumbersInfo = {
    totalProblemNumbers: totalProblemNumbers,
    arkOnePointOneProblemNumbers: arkOnePointOneProblemNumbers,
    arkOnePointTWOProblemNumbers: totalProblemNumbers - arkOnePointOneProblemNumbers,
    canBeAutoFixedproblemNumbers: canBeAutoFixedproblemNumbers,
    needToManualFixproblemNumbers: totalProblemNumbers - canBeAutoFixedproblemNumbers
  };

  const statisticsReportData = getProblemStatisticsInfo(
    problemNumbers,
    getProcessedRuleToNumbersMap(ruleToNumbersMap, getAllLinterRules()),
    ruleToAutoFixedNumbersMap,
    timeRecorder
  );
  const statisticsReportName: string = 'migration-results-statistics.json';
  generateReportFileSync(statisticsReportName, statisticsReportData, outputFilePath);
}

export async function getWorkLoadInfo(projectPathList: string[]): Promise<WorkLoadInfo> {
  const workloadInfo = new WorkLoadInfo();
  const projectResults = await mapWithLimit(projectPathList, PROJECTS_PER_DIR_CONCURRENCY, async (projectPath) => {
    const normalizedPath = path.normalize(projectPath);
    const countFilesResults = await countFiles(normalizedPath);
    const countNapiFileResults = await countNapiFiles(normalizedPath);

    const getLines = (lang: keyof typeof countFilesResults): number => {
      return countFilesResults[lang]?.lineCount || 0;
    };

    return {
      normalizedPath,
      arkTSCodeLines: getLines('ArkTS') + getLines('ArkTS Test'),
      cAndCPPCodeLines: getLines('C/C++'),
      napiCodeLines: countNapiFileResults.napiLines,
      jsCodeLines: getLines('JavaScript'),
      tsCodeLines: getLines('TypeScript'),
      jsonCodeLines: getLines('JSON'),
      xmlCodeLines: getLines('XML')
    };
  });

  projectResults.forEach((result) => {
    workloadInfo.addFloderResult(result);
  });
  return workloadInfo;
}