"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CloudRunService = void 0;
const archiver_1 = __importDefault(require("archiver"));
const fs_extra_1 = require("fs-extra");
const path_1 = __importDefault(require("path"));
const utils_1 = require("../utils");
/**
 * 云托管服务管理类
 * 提供云托管服务的初始化、下载、列表查询和删除等功能
 */
class CloudRunService {
    constructor(environment) {
        this.environment = environment;
        this.tcbrService = new utils_1.CloudService(environment.cloudBaseContext, 'tcbr', '2022-02-17');
        this.tcbService = new utils_1.CloudService(environment.cloudBaseContext, 'tcb', '2018-06-08');
    }
    /**
     * 初始化云托管代码项目
     * @param {Object} params 初始化参数
     * @param {string} params.serverName 服务名称，将作为项目目录名
     * @param {string} [params.template='helloworld'] 模板标识符，默认为'helloworld'
     * @param {string} [params.targetPath='.'] 目标路径，默认为当前目录
     * @returns {Promise<void>}
     * @throws 当模板不存在、下载失败或解压失败时抛出错误
     */
    async init(params) {
        const { serverName, template = 'helloworld', targetPath = '.' } = params || {};
        // 1. 获取模板列表
        const templates = await this.getTemplates();
        const templateData = templates.find(item => item.identifier === template);
        if (!templateData || !templateData.zipFileStore) {
            throw new Error(`未找到匹配的模板: ${template}`);
        }
        // 2. 下载并解压模板到目标目录
        const downloadUrl = templateData.zipFileStore;
        const projectDir = path_1.default.resolve(targetPath, serverName);
        await (0, utils_1.downloadAndExtractRemoteZip)(downloadUrl, projectDir);
        return { projectDir };
    }
    /**
     * 下载云托管服务代码到本地目录
     * @param {Object} params 下载参数
     * @param {string} params.serverName 要下载的服务名称
     * @param {string} params.targetPath 下载的目标路径(绝对路径或相对路径)
     * @returns {Promise<void>}
     * @throws 当以下情况发生时抛出错误:
     * - 未找到服务版本
     * - 获取下载地址失败
     * - 下载或解压过程中出错
     */
    async download(params) {
        var _a, _b;
        const envConfig = this.environment.lazyEnvironmentConfig;
        const { serverName, targetPath } = params;
        /**
         * 获取最新版本
         */
        const cloudRunServerDetailRes = await this.detail({ serverName });
        const version = (_b = (_a = cloudRunServerDetailRes.OnlineVersionInfos) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.VersionName;
        if (!version) {
            throw new Error('未找到云托管服务版本');
        }
        /**
         * 获取下载地址
         */
        const cloudBaseBuildServiceRes = await this.tcbService.request('DescribeCloudBaseBuildService', {
            EnvId: envConfig.EnvId,
            ServiceName: serverName,
            ServiceVersion: version
        });
        if (cloudBaseBuildServiceRes === null || cloudBaseBuildServiceRes === void 0 ? void 0 : cloudBaseBuildServiceRes.DownloadUrl) {
            await (0, utils_1.downloadAndExtractRemoteZip)(cloudBaseBuildServiceRes === null || cloudBaseBuildServiceRes === void 0 ? void 0 : cloudBaseBuildServiceRes.DownloadUrl, path_1.default.resolve(targetPath));
        }
        else {
            throw new Error(`云托管代码下载地址为空（请求ID: ${cloudBaseBuildServiceRes.RequestId}）`);
        }
    }
    /**
     * 获取云托管服务列表
     * @param {Object} [params] 查询参数
     * @param {number} [params.pageSize] 每页数量
     * @param {number} [params.pageNum] 页码
     * @param {string} [params.serverName] 服务名称筛选
     * @param {string} [params.serverType] 服务类型筛选
     * @returns {Promise<ICloudrunListResponse>} 包含服务列表和总数等信息的响应对象
     */
    async list(params) {
        const envConfig = this.environment.lazyEnvironmentConfig;
        return this.tcbrService.request('DescribeCloudRunServers', Object.assign({ EnvId: envConfig.EnvId }, {
            PageSize: (params === null || params === void 0 ? void 0 : params.pageSize) || 10,
            PageNum: (params === null || params === void 0 ? void 0 : params.pageNum) || 1,
            ServerName: params === null || params === void 0 ? void 0 : params.serverName,
            ServerType: params === null || params === void 0 ? void 0 : params.serverType
        }));
    }
    /**
     *查询云托管服务详情
     * @param {Object} params 查询参数
     * @param {string} params.serverName 要查询的服务名称
     * @returns {Promise<ICloudrunDetailResponse>} 返回服务详情响应对象
     */
    async detail(params) {
        const envConfig = this.environment.lazyEnvironmentConfig;
        return this.tcbrService.request('DescribeCloudRunServerDetail', {
            EnvId: envConfig.EnvId,
            ServerName: params.serverName
        });
    }
    /**
     * 删除指定的云托管服务
     * @param {string} serverName - 要删除的服务名称，必须是在当前环境中已存在的服务
     * @returns {Promise<IResponseInfo>} 返回删除操作的响应信息
     */
    async delete(params) {
        // 获取当前环境配置(包含EnvId)
        const envConfig = this.environment.lazyEnvironmentConfig;
        // 调用TCBR服务的DeleteCloudRunServer接口执行删除
        return this.tcbrService.request('DeleteCloudRunServer', {
            EnvId: envConfig.EnvId, // 环境ID
            ServerName: params.serverName // 要删除的服务名称
        });
    }
    /**
     * 本地代码部署云托管服务
     * @param {Object} params 部署参数
     * @param {string} params.serverName 要部署的服务名称
     * @param {string} params.targetPath 本地代码路径
     * @param {Object} [params.serverConfig] 服务配置项(可选)
     * @param {string[]} [params.serverConfig.OpenAccessTypes] 开放访问类型
     * @param {number} [params.serverConfig.Cpu] CPU规格
     * @param {number} [params.serverConfig.Mem] 内存规格
     * @param {number} [params.serverConfig.MinNum] 最小实例数
     * @param {number} [params.serverConfig.MaxNum] 最大实例数
     * @param {Object} [params.serverConfig.PolicyDetails] 策略详情
     * @param {Object} [params.serverConfig.CustomLogs] 自定义日志配置
     * @param {Object} [params.serverConfig.EnvParams] 环境变量参数
     * @param {number} [params.serverConfig.Port] 端口(函数型服务不允许设置)
     * @param {string} [params.serverConfig.Dockerfile] Dockerfile路径
     * @param {string} [params.serverConfig.BuildDir] 构建目录
     * @param {boolean} [params.serverConfig.InternalAccess] 是否开启内网访问
     * @param {string} [params.serverConfig.InternalDomain] 内网域名
     * @param {string} [params.serverConfig.EntryPoint] 入口文件
     * @param {string} [params.serverConfig.Cmd] 启动命令
     * @returns {Promise<IResponseInfo>} 返回部署操作的响应信息
     */
    async deploy(params) {
        const { serverName, targetPath = process.cwd(), serverConfig } = params;
        /**
         * 参数校验和默认值设置
         */
        if (!serverName) {
            throw new Error('Missing required parameters: serviceName');
        }
        // 获取当前环境配置(包含EnvId)
        const envConfig = this.environment.lazyEnvironmentConfig;
        /**
         * 获取部署包上传信息
         */
        const { UploadUrl: uploadUrl, UploadHeaders: uploadHeaders, PackageName: packageName, PackageVersion: packageVersion } = await this.tcbService.request('DescribeCloudBaseBuildService', {
            EnvId: envConfig.EnvId,
            ServiceName: serverName
        });
        const deployInfo = {
            DeployType: 'package',
            PackageName: packageName,
            PackageVersion: packageVersion
        };
        /**
         * 上传部署包
         */
        const zipFile = await codeToZip(targetPath, { installDependency: true });
        await upload({
            action: uploadUrl,
            file: zipFile,
            filename: 'file',
            headers: (uploadHeaders || []).reduce((map, item) => {
                map[item.Key] = item.Value;
                return map;
            }, {}) || {},
            method: 'PUT',
            send: (file) => file
        });
        /**
         * 执行部署
         */
        if (await this._checkFunctionExist(serverName)) {
            // 更新
            const serverDetail = await this.detail({ serverName });
            const _serverConfig = Object.assign(Object.assign(Object.assign({}, ((serverDetail === null || serverDetail === void 0 ? void 0 : serverDetail.ServerConfig) || {})), serverConfig), ((serverDetail === null || serverDetail === void 0 ? void 0 : serverDetail.ServerConfig.Tag) === 'function:' ? { Port: 3000 } : {}) // 函数型不能指定端口，需要固定为3000
            );
            deployInfo.ReleaseType = 'FULL';
            return this._upsertFunction(false, {
                name: serverName,
                deployInfo,
                serverConfig: _serverConfig
            });
        }
        else {
            // 创建
            /**
             * 判断是容器器还是函数型
             */
            let type = 'function';
            if (serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.Dockerfile) {
                type = 'container';
            }
            else {
                if (await (0, fs_extra_1.pathExists)(path_1.default.join(targetPath, 'Dockerfile'))) {
                    type = 'container';
                }
            }
            if (type === 'function') {
                deployInfo.BuildPacks = {
                    LanguageVersion: '20.18',
                    RepoLanguage: 'Node.js'
                };
            }
            const _serverConfig = Object.assign(Object.assign(Object.assign({ OpenAccessTypes: ['OA', 'PUBLIC'], Cpu: 0, Mem: 0, MinNum: 0, MaxNum: 0, PolicyDetails: [], EnvParams: JSON.stringify({}), InitialDelaySeconds: 0, CustomLogs: '', HasDockerfile: true, CreateTime: '', EnvId: envConfig.EnvId, ServerName: serverName, Port: type === 'container' ? 80 : 3000, Dockerfile: 'Dockerfile', BuildDir: '' }, serverConfig), (type === 'function' ? { Port: 3000 } : {})), { Tag: type === 'container' ? '' : 'function:' });
            return this._upsertFunction(true, {
                name: serverName,
                deployInfo,
                serverConfig: _serverConfig
            });
        }
    }
    /**
     * 获取云托管服务模板列表
     * @returns {Promise<ITemplate[]>} 返回模板数组
     */
    async getTemplates() {
        return (0, utils_1.fetchTemplates)(['tcbrFunc', 'tcbrContainer']);
    }
    async _checkFunctionExist(name) {
        try {
            await this.detail({
                serverName: name
            });
            return true;
        }
        catch (e) {
            if (e.code === 'ResourceNotFound' ||
                // 备注：以下条件当 NotFound 处理（已与 fisheryan 确认过）
                (e.code === 'InvalidParameter' && e.original.Message === 'service data illegal')) {
                return false;
            }
            throw e;
        }
    }
    _upsertFunction(isNew, data) {
        const { name, deployInfo, serverConfig } = data;
        const envConfig = this.environment.lazyEnvironmentConfig;
        return this.tcbrService.request(isNew ? 'CreateCloudRunServer' : 'UpdateCloudRunServer', {
            EnvId: envConfig.EnvId,
            ServerName: name,
            DeployInfo: deployInfo,
            ServerConfig: serverConfig
        });
    }
}
exports.CloudRunService = CloudRunService;
__decorate([
    (0, utils_1.preLazy)()
], CloudRunService.prototype, "init", null);
__decorate([
    (0, utils_1.preLazy)()
], CloudRunService.prototype, "download", null);
__decorate([
    (0, utils_1.preLazy)()
], CloudRunService.prototype, "list", null);
__decorate([
    (0, utils_1.preLazy)()
], CloudRunService.prototype, "detail", null);
__decorate([
    (0, utils_1.preLazy)()
], CloudRunService.prototype, "delete", null);
__decorate([
    (0, utils_1.preLazy)()
], CloudRunService.prototype, "deploy", null);
async function codeToZip(cwd, options) {
    const archive = (0, archiver_1.default)('zip', {
        zlib: { level: 1 } // 保持与之前相同的压缩级别
    });
    const chunks = [];
    const bufferPromise = new Promise((resolve, reject) => {
        archive.on('data', (chunk) => {
            chunks.push(chunk);
        });
        archive.on('end', () => {
            resolve(Buffer.concat(chunks));
        });
        archive.on('error', err => {
            reject(err);
        });
    });
    async function addFilesToArchive(dir, root = false, relativePath = '') {
        const entries = await (0, fs_extra_1.readdir)(dir, { withFileTypes: true });
        for (let entry of entries) {
            const fullPath = path_1.default.join(dir, entry.name);
            const entryRelativePath = path_1.default.join(relativePath, entry.name);
            if (entry.isDirectory()) {
                if (['logs', '.git'].includes(entry.name)) {
                    // 忽略 logs 等目录
                    continue;
                }
                if (options === null || options === void 0 ? void 0 : options.installDependency) {
                    // 忽略 node_modules 等目录
                    if (['node_modules'].includes(entry.name)) {
                        continue;
                    }
                }
                await addFilesToArchive(fullPath, false, entryRelativePath);
            }
            else {
                if (root) {
                    // 可以配置忽略指定文件名的文件
                    if ([''].includes(entry.name)) {
                        continue;
                    }
                }
                // 保持与之前相同的文件添加方式，包括相对路径处理
                archive.file(fullPath, { name: entryRelativePath });
            }
        }
    }
    await addFilesToArchive(path_1.default.resolve(cwd), true);
    await archive.finalize();
    return bufferPromise;
}
function upload({ action, file, method = 'POST', headers = {}, withCredentials = false }) {
    return (0, utils_1.fetchStream)(action, {
        method,
        headers,
        body: file,
        credentials: withCredentials ? 'include' : 'same-origin'
    })
        .then(async (response) => {
        const text = await response.text();
        const dataMeta = {
            data: null,
            context: {
                response,
                file
            }
        };
        try {
            dataMeta.data = JSON.parse(text);
        }
        catch (e) {
            // If parsing fails, keep data as null
        }
        if (!response.ok) {
            throw new Error(`${response.status} ${response.statusText} HTTP ERROR`);
        }
        return dataMeta;
    })
        .catch(error => {
        throw error; // 或者可以在这里进行错误转换
    });
}
