import fs from 'fs';
import path from 'path';
import FileUtils from '../utils/FileUtils';
import MD5Utils from '../utils/MD5Utils';
import { PackageScript } from './PackageScript';

const START_VERSION = 1000;
const VERSION_SUPPORT = 50;
type PackageRecord = { md5: MD5Utils.FileMD5[]/*, package: PackageData*/ };
type PackageData = { version: number, size: number, files: number, md5: string };
type Config = { version: number, packages: PackageData[] };
type MainConfig = { key: string, version: number, supports: number[] };


export type Result = {
    project: string,
    mode: string,
    url: string,
    xxtea: string,
    zipCompressJs: boolean,
    isolate: boolean,
    key: string,
    added: number, changed: number, deleted: number,
    frameworkVersion: string,
    packageVersion: number,
    supportVersion: number[],
    size: number
}

function readExistPackageRecords(versionCacheDir: string): number[] {
    const versions: number[] = [];
    const versionFiles = fs.readdirSync(versionCacheDir);
    versionFiles.forEach(versionFile => {
        const versionFilePath = path.join(versionCacheDir, versionFile)
        const stat = fs.statSync(versionFilePath);
        if (stat.isDirectory()) return;
        const versionString = path.basename(versionFile, path.extname(versionFile));
        const version = parseInt(versionString);
        versions.push(version);
    })
    return versions.sort((a, b) => a - b).slice(-VERSION_SUPPORT);
}

function readPackageRecord(version: number, versionCacheDir: string): PackageRecord | null {
    const infoFilePath: string = path.join(versionCacheDir, `${version}.json`);
    if (!fs.existsSync(infoFilePath)) return null;
    const subpackageInfoContent = fs.readFileSync(infoFilePath);
    const info: PackageRecord = JSON.parse(subpackageInfoContent.toString());
    return info;
}

function writePackageRecord(version: number, record: PackageRecord, versionCacheDir: string): void {
    const content = JSON.stringify(record);
    const infoFilePath = path.join(versionCacheDir, `${version}.json`);
    FileUtils.mkdir(path.dirname(infoFilePath));
    fs.writeFileSync(infoFilePath, content);
}

function loadMainConfig(url: string, outputDir: string, frameworkVersion: string): MainConfig[] {

    const localCachePath = path.join(outputDir, `subgame_config_${frameworkVersion}.json`);
    if (fs.existsSync(localCachePath)) {
        const mainConfig = JSON.parse(fs.readFileSync(localCachePath).toString());
        return mainConfig;
    }

    const configCache = path.join(process.env.HOME || process.env.USERPROFILE, '.sgp', 'config', `${MD5Utils.md5(url)}.json`);
    if (fs.existsSync(configCache)) {
        const mainConfig = JSON.parse(fs.readFileSync(configCache).toString());
        return mainConfig;
    }

    return null;
}

function writeMainConfig(url: string, mainConfigs: MainConfig[]): void {
    const configCacheDir = path.join(process.env.HOME || process.env.USERPROFILE, '.sgp', 'config');
    FileUtils.mkdir(configCacheDir);
    fs.writeFileSync(path.join(configCacheDir, `${MD5Utils.md5(url)}.json`), JSON.stringify(mainConfigs));
}

export default async function PackageAndroid(projectDir: string, key: string, frameworkVersion: string, mode: string, url: string, xxtea: string, zipCompressJs: boolean,
    isolate: boolean = false): Promise<Result | null> {
    console.log(`-----------------------------------------------------Handle package [${key}] start-----------------------------------------------------`);



    const outputDir = path.join(projectDir, 'build', 'subgame', 'android');
    console.log(`Make package [${key}] zip dir: ${outputDir}`);
    FileUtils.mkdir(outputDir);

    const versionCacheDir = path.join(projectDir, 'version', key, mode);
    console.log(`Make package [${key}] info dir: ${versionCacheDir}`);
    FileUtils.mkdir(versionCacheDir);

    const buildDir = path.join(projectDir, 'build', 'jsb-default');
    const bundleDir = path.join(buildDir, 'assets', key);



    PackageScript(key, buildDir, isolate, xxtea, zipCompressJs);

    const mainConfigs: MainConfig[] = loadMainConfig(url, outputDir, frameworkVersion);
    if (!mainConfigs) throw new Error(`load main config fail`);


    //读取版本缓存
    const existVersions = readExistPackageRecords(versionCacheDir);
    const haveExistVersion = existVersions && existVersions.length;

    //创建上一版本数据
    const previousVersion = haveExistVersion ? existVersions[existVersions.length - 1] : START_VERSION;
    const previousRecord: PackageRecord = haveExistVersion ? readPackageRecord(previousVersion, versionCacheDir) : { md5: [] };
    console.log(`Package [${key}] previous version: ${previousVersion}`);

    //读取本次构建MD5
    const md5 = MD5Utils.md5Dir(bundleDir, null);

    //对比前一版本
    const md5Compare = MD5Utils.md5Compare(md5, previousRecord.md5);
    console.log(`Package [${key}] compare with last: added=${md5Compare.added.length}, deleted:${md5Compare.deleted.length}, same:${md5Compare.same.length}, changed:${md5Compare.changed.length}`);

    const packageResult: Result = {
        project: projectDir,
        key, frameworkVersion, mode, url, xxtea, zipCompressJs, isolate,
        packageVersion: previousVersion,
        supportVersion: existVersions,
        added: md5Compare.added.length,
        changed: md5Compare.changed.length,
        deleted: md5Compare.deleted.length,
        size: -1
    };

    if (md5Compare.changed.length || md5Compare.added.length) {
        console.log(`Package [${key}] changed`);


        const zipCacheOutputDir = path.join(outputDir, 'cache');
        console.log(`Make package [${key}] zip cache dir: ${zipCacheOutputDir}`);

        FileUtils.rm(zipCacheOutputDir);
        FileUtils.mkdir(zipCacheOutputDir);

        const packages: PackageData[] = [];
        const newVersion = previousVersion + 1;

        for (const versionItem of existVersions) {
            console.log(`Pick package [${key}] assets for version ${versionItem} to ${newVersion}`);
            const versionInfo = readPackageRecord(versionItem, versionCacheDir);
            if (!versionInfo) {
                console.warn(`ackage [${key}] skip exits version ${versionItem}, no version info.`);
                continue;
            }

            //对比文件
            const versionCompareResult = MD5Utils.md5Compare(md5, versionInfo.md5);
            console.log(`Package [${key}] compare with version ${versionItem}: added=${versionCompareResult.added.length}, deleted:${versionCompareResult.deleted.length} , same:${versionCompareResult.same.length}, changed:${versionCompareResult.changed.length}`);

            //只压缩changed和added
            const files = versionCompareResult.changed.concat(versionCompareResult.added);
            const zipCacheDir = path.join(zipCacheOutputDir, `${versionItem}_${newVersion}`);
            FileUtils.mkdir(zipCacheDir);
            files.forEach(value => {
                const outPath = path.join(zipCacheDir, value.path)
                FileUtils.mkdir(path.dirname(outPath));
                fs.copyFileSync(path.join(bundleDir, value.path), outPath);
            });
            const zipOutFile = path.join(outputDir, `${key}_${versionItem}_${newVersion}.zip`);
            const fileCount = FileUtils.fileCount(zipCacheDir, true);
            await FileUtils.zipdir(zipCacheDir, zipOutFile);
            FileUtils.rm(zipCacheDir);
            const zipStat = fs.statSync(zipOutFile);
            const zipMD5 = MD5Utils.md5Dir(zipOutFile)[0].md5;
            packages.push({ version: versionItem, size: zipStat.size, files: fileCount, md5: zipMD5 });
        }

        FileUtils.rm(zipCacheOutputDir);


        const zipOutFile = path.join(outputDir, `${key}_${newVersion}.zip`);
        const fileCount = FileUtils.fileCount(bundleDir, true);

        await FileUtils.zipdir(bundleDir, zipOutFile);
        const zipStat = fs.statSync(zipOutFile);
        const zipMD5 = MD5Utils.md5Dir(zipOutFile)[0].md5;
        packages.push({ version: 0, size: zipStat.size, files: fileCount, md5: zipMD5 });
        writePackageRecord(newVersion, { md5 }, versionCacheDir);



        const mainConfig = mainConfigs.find(value => value.key === key);
        if (mainConfig) {
            mainConfig.version = newVersion;
            mainConfig.supports = existVersions;
        } else {
            mainConfigs.push({ key, version: newVersion, supports: existVersions })
        }
        writeMainConfig(url, mainConfigs);
        console.log(`save package [${key}] config`);
        const configFileName = `subgame_config_${key}_${frameworkVersion}.json`;
        const configFilePath = path.join(outputDir, configFileName);
        const config: Config = { version: newVersion, packages };
        fs.writeFileSync(configFilePath, JSON.stringify(config));

        packageResult.packageVersion = newVersion;
        packageResult.size = zipStat.size;

        console.log(`Package [${key}] build finished`);


    } else if (md5Compare.deleted.length) {
        console.log(`Package [${key}] no changes (but delete)`);
    } else {
        console.log(`Package [${key}] no changes`);
    }
    fs.writeFileSync(path.join(outputDir, `subgame_config_${frameworkVersion}.json`), JSON.stringify(mainConfigs));

    FileUtils.rm(bundleDir);
    console.log(`Package [${key}] build result: \n ${JSON.stringify(packageResult)}`);
    console.log(`-----------------------------------------------------Handle package [${key}] end-------------------------------------------------------\n\n`);
    return packageResult;
}
