/*
 * Copyright (c) 2024 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 path from 'path';
import fs from 'fs';
import mocha from 'mocha';
import sinon from 'sinon';
import { expect } from 'chai';

import {
	BUILD_ON,
	JSBUNDLE,
} from '../../lib/pre_define';
import {
	resetComponentCollection,
	componentCollection
} from '../../lib/validate_ui_syntax';
import { 
	transformLog 
} from '../../lib/process_ui_syntax';
import {
	componentInfo,
	resetUtils,
	storedFileInfo
} from '../../lib/utils';
import main, {
	partialUpdateConfig, 
	projectConfig, 
	readAppResource, 
	resetGlobalProgram, 
	resetMain, 
	resources, 
	sdkConfigs,
	systemModules
} from '../../main';
import { 
	etsChecker 
} from '../../lib/fast_build/ets_ui/rollup-plugin-ets-checker';
import { 
	etsTransform 
} from '../../lib/fast_build/ets_ui/rollup-plugin-ets-typescript';
import processStructComponentV2 from '../../lib/process_struct_componentV2';
import { 
	RollUpPluginMock 
} from './helpers/mockRollupContext';
import { 
	PartialUpdateConfig, 
	ProjectConfig 
} from './helpers/projectConfig';
import { 
	UT_VALIDATE_PAGES,
	UT_VALIDATE_PAGES_JSBUNDLE,
	UT_VALIDATE_PAGES_PREVIEW
} from './helpers/pathConfig';
import { 
	parseFileNameFromPath, 
	parseLog, 
	processExecInStr, 
	sourceReplace 
} from './helpers/parser';
import { 
	scanFileNames
} from './helpers/utils';
import { 
	Logger,
	LogInfo 
} from './helpers/logger';
import { 
	ARKUI_SUBSYSTEM_CODE, 
	TSC_SYSTEM_CODE 
} from './helpers/common';

const PROJECT_ROOT: string = path.resolve(__dirname, '../../test/transform_ut');
const DEFAULT_PROJECT: string = 'application';
const TEST_CASES_PATH: string = path.resolve(PROJECT_ROOT, DEFAULT_PROJECT, 'entry/src/main/ets/pages');
const SYS_CONFIG_PATH: string = path.resolve(PROJECT_ROOT, DEFAULT_PROJECT, 'entry/src/main/ets/test/common');
const ERROR_COLLECTION_PATH: string = path.resolve(__dirname, '../../test/transform_ut_error.json');
const MAIN_PAGES: string[] = [
	...UT_VALIDATE_PAGES,
	...UT_VALIDATE_PAGES_PREVIEW,
	...UT_VALIDATE_PAGES_JSBUNDLE
].map((p) => `pages/utForValidate/${p}`);

const systemModuleSet: Set<string> = new Set();
scanFileNames(SYS_CONFIG_PATH, systemModuleSet);

mocha.describe('test UT for validate testcases [non-preview mode]', function () {
	this.timeout(60000);

	mocha.before(function () {
		resetUtils();
		resetGlobalProgram();
		resetMain();
		this.rollup = new RollUpPluginMock();
		this.rollup.build(PROJECT_ROOT, DEFAULT_PROJECT, MAIN_PAGES);

		// enable logger for etsTransform roll-up plugin
		this.rollup.share.flushLogger();
		this.rollup.share.setEnableLogger(true);
		this.rollup.share.setAllowedLoggerPrefix(['etsTransform', TSC_SYSTEM_CODE, ARKUI_SUBSYSTEM_CODE]);

		this.globalProjectConfig = new ProjectConfig();
		this.globalProjectConfig.setPreview(false);
		this.globalProjectConfig.setIgnoreWarning(true);
		this.globalProjectConfig.scan(PROJECT_ROOT, DEFAULT_PROJECT, MAIN_PAGES);
		this.globalProjectConfig.mockCompileContextInfo(`${PROJECT_ROOT}/${DEFAULT_PROJECT}`, MAIN_PAGES);
		this.globalProjectConfig.concat(RollUpPluginMock.mockArkProjectConfig(PROJECT_ROOT, DEFAULT_PROJECT, false));

		this.rollup.share.projectConfig.concat(this.globalProjectConfig);
		Object.assign(projectConfig, this.globalProjectConfig);
		readAppResource(projectConfig.appResource);

		this.globalPartialUpdateConfig = new PartialUpdateConfig();
		this.globalPartialUpdateConfig.setPartialUpdateMode(true);
		this.globalPartialUpdateConfig.mockDisableArkTSLinter();
		Object.assign(partialUpdateConfig, this.globalPartialUpdateConfig);

		Object.assign(main, { 
			sdkConfigs: [
				...sdkConfigs
					.filter((sdkConfig) => !sdkConfig['apiPath'].includes(SYS_CONFIG_PATH))
					.map((sdkConfig) => {
						sdkConfig['apiPath'].push(SYS_CONFIG_PATH);
						return sdkConfig;
					}
				),
			],
			systemModules: [...systemModules, ...systemModuleSet]
		});

		this.etsCheckerPlugin = etsChecker();
		this.etsTransformPlugin = etsTransform();

		// disable writing to local files
		sinon.stub(fs, 'writeSync');

		// run etsChecker once
		const buildStart = this.etsCheckerPlugin.buildStart.bind(this.rollup);
		buildStart();
	});

	mocha.after(() => {
		this.rollup?.share?.flushLogger();
		delete this.rollup;
		delete this.globalProjectConfig;
		delete this.globalPartialUpdateConfig;
		delete this.etsCheckerPlugin;
		delete this.etsTransformPlugin;

		resetUtils();
		resetGlobalProgram();
		resetMain();
		sinon.restore();
	});

	mocha.beforeEach(function () {
		resources.app["media"] = {icon:16777222};
		resources.app["font"] = {song:16777223};

		process.env.rawFileResource = './';
		process.env.compileMode = 'moduleJson';
		process.env.compiler = BUILD_ON;
		process.env.compileTool = 'rollup';

		transformLog.errors = [];
		componentInfo.id = 0;
		componentCollection.customComponents.clear();
		resetComponentCollection();
		storedFileInfo.setCurrentArkTsFile();

		// disable ignoreWarning to get log info
		Object.assign(projectConfig, { ignoreWarning: false });
	});

	mocha.afterEach(function () {
		this.rollup?.share?.flushLogger();
		processStructComponentV2.resetStructMapInEts();
	});

	[...UT_VALIDATE_PAGES, ...UT_VALIDATE_PAGES_JSBUNDLE].forEach((utPage, index) => {
		mocha.it(`1-${index + 1}: test ${utPage}`, function (done) {
			if (UT_VALIDATE_PAGES_JSBUNDLE.includes(utPage)) {
				Object.assign(projectConfig, { compileMode: JSBUNDLE });
			}

			const sourceFilePath: string = path.resolve(TEST_CASES_PATH, `utForValidate/${utPage}.ets`);
			const sourceCode: string = fs.readFileSync(sourceFilePath, 'utf-8');

			storedFileInfo.addFileCacheInfo(sourceFilePath);

			const transform = this.etsTransformPlugin.transform.bind(this.rollup);
			const errorCollection: object = JSON.parse(fs.readFileSync(ERROR_COLLECTION_PATH, 'utf-8'));
			const errorKey: string = parseFileNameFromPath(sourceFilePath);
			const errorVals: object = errorCollection[errorKey] ?? {};

			const expectResults: LogInfo[] = Array.isArray(errorVals) ? errorVals : [errorVals];

			let expectErrorInfos: LogInfo[] = [];
			let expectWarnInfos: LogInfo[] = [];
			expectResults.filter(e => e.type).forEach((e) => {
				if (e.type === 'ERROR') {
					expectErrorInfos.push({ ...e, message: processExecInStr(e.message.trim()) });
				} else {
					expectWarnInfos.push({ ...e, message: processExecInStr(e.message.trim()) });
				}
			});

			transform(sourceReplace(sourceCode), sourceFilePath)
				.then(_ => {
					const logger: Logger = Logger.mergeLoggers(
						'etsTransform', 
						TSC_SYSTEM_CODE, 
						ARKUI_SUBSYSTEM_CODE
					);

					const errorInfos: LogInfo[] = logger.getErrorInfos();
					const warnInfos: LogInfo[] = logger.getWarnInfos();

					expect(errorInfos.length === expectErrorInfos.length).to.be.true;
					expect(warnInfos.length === expectWarnInfos.length).to.be.true;

					let _findIndex: number = 0;
					let _sumCount: number = expectErrorInfos.length;
					errorInfos.forEach((err) => {
						const message: string = parseLog(err.cause ?? err.message);
						const findIndex: number = expectErrorInfos
							.slice(_findIndex)
							.findIndex((value) => value.message === message);
						expect(findIndex !== -1).to.be.true;
						if (err.code) {
							expect(expectErrorInfos[_findIndex + findIndex].code === err.code).to.be.true;
							expect(expectErrorInfos[_findIndex + findIndex].solutions ?? []).to.deep.equal(err.solutions ?? []);
						}
						_sumCount -= 1;
						_findIndex = findIndex + 1;
					});
					expect(_sumCount === 0).to.be.true;
					
					_findIndex = 0;
					_sumCount = expectWarnInfos.length;
					warnInfos.forEach((err) => {
						const message: string = parseLog(err.cause ?? err.message);
						const findIndex: number = expectWarnInfos
							.slice(_findIndex)
							.findIndex((value) => value.message === message);
						expect(findIndex !== -1).to.be.true;
						_sumCount -= 1;
						_findIndex = findIndex + 1;
					});
					expect(_sumCount === 0).to.be.true;

					done();
				})
				.catch(err => done(err));
		});
	});
});