"use strict";
/*
 * Copyright © 2020 Atomist, Inc.
 *
 * 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.
 */
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 __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.reportEndAndClose = exports.reportStart = exports.FulfillGoalOnRequested = void 0;
const decorators_1 = require("@atomist/automation-client/lib/decorators");
const globals_1 = require("@atomist/automation-client/lib/globals");
const graphQL_1 = require("@atomist/automation-client/lib/graph/graphQL");
const HandlerResult_1 = require("@atomist/automation-client/lib/HandlerResult");
const logger_1 = require("@atomist/automation-client/lib/util/logger");
const os = require("os");
const executeGoal_1 = require("../../../../../api-helper/goal/executeGoal");
const storeGoals_1 = require("../../../../../api-helper/goal/storeGoals");
const cancelGoals_1 = require("../../../../../api-helper/listener/cancelGoals");
const LoggingProgressLog_1 = require("../../../../../api-helper/log/LoggingProgressLog");
const WriteToAllProgressLog_1 = require("../../../../../api-helper/log/WriteToAllProgressLog");
const handlerRegistrations_1 = require("../../../../../api-helper/machine/handlerRegistrations");
const dateFormat_1 = require("../../../../../api-helper/misc/dateFormat");
const result_1 = require("../../../../../api-helper/misc/result");
const addressChannels_1 = require("../../../../../api/context/addressChannels");
const skillContext_1 = require("../../../../../api/context/skillContext");
const SdmGoalMessage_1 = require("../../../../../api/goal/SdmGoalMessage");
const types_1 = require("../../../../../typings/types");
const validateGoal_1 = require("../../../../delivery/goals/support/validateGoal");
const goalCaching_1 = require("../../../../goal/cache/goalCaching");
const goalSigning_1 = require("../../../../signing/goalSigning");
const array_1 = require("../../../../util/misc/array");
const time_1 = require("../../../../util/misc/time");
/**
 * Handle an SDM request goal. Used for many implementation types.
 */
let FulfillGoalOnRequested = class FulfillGoalOnRequested {
    constructor(implementationMapper, goalExecutionListeners) {
        this.implementationMapper = implementationMapper;
        this.goalExecutionListeners = goalExecutionListeners;
    }
    /* tslint:disable:cyclomatic-complexity */
    async handle(event, ctx) {
        const sdmGoal = event.data.SdmGoal[0];
        if (!validateGoal_1.shouldFulfill(sdmGoal)) {
            logger_1.logger.debug(`Goal ${sdmGoal.uniqueName} skipped because not fulfilled by this SDM`);
            return HandlerResult_1.Success;
        }
        await goalSigning_1.verifyGoal(sdmGoal, this.configuration.sdm.goalSigning, ctx);
        if ((await cancelGoals_1.cancelableGoal(sdmGoal, this.configuration)) && (await cancelGoals_1.isGoalCanceled(sdmGoal, ctx))) {
            logger_1.logger.debug(`Goal ${sdmGoal.uniqueName} has been canceled. Not fulfilling`);
            return HandlerResult_1.Success;
        }
        if (sdmGoal.fulfillment.method === SdmGoalMessage_1.SdmGoalFulfillmentMethod.SideEffect &&
            sdmGoal.fulfillment.registration !== this.configuration.name) {
            logger_1.logger.debug("Not fulfilling side-effected goal '%s' with method '%s/%s'", sdmGoal.uniqueName, sdmGoal.fulfillment.method, sdmGoal.fulfillment.name);
            return HandlerResult_1.Success;
        }
        else if (sdmGoal.fulfillment.method === SdmGoalMessage_1.SdmGoalFulfillmentMethod.Other) {
            // fail goal with neither Sdm nor SideEffect fulfillment
            await storeGoals_1.updateGoal(ctx, sdmGoal, {
                state: types_1.SdmGoalState.failure,
                description: `No fulfillment for ${sdmGoal.uniqueName}`,
            });
            return HandlerResult_1.Success;
        }
        const id = this.configuration.sdm.repoRefResolver.repoRefFromSdmGoal(sdmGoal);
        const credentials = await handlerRegistrations_1.resolveCredentialsPromise(this.configuration.sdm.credentialsResolver.eventHandlerCredentials(ctx, id));
        const addressChannels = addressChannels_1.addressChannelsFor(sdmGoal.push.repo, ctx);
        const preferences = this.configuration.sdm.preferenceStoreFactory(ctx);
        const implementation = this.implementationMapper.findImplementationBySdmGoal(sdmGoal);
        const { goal } = implementation;
        const progressLog = new WriteToAllProgressLog_1.WriteToAllProgressLog(sdmGoal.name, new LoggingProgressLog_1.LoggingProgressLog(sdmGoal.name, "debug"), await this.configuration.sdm.logFactory(ctx, sdmGoal));
        const goalInvocation = {
            configuration: this.configuration,
            goalEvent: sdmGoal,
            goal,
            progressLog,
            context: ctx,
            addressChannels,
            preferences,
            id,
            credentials,
            skill: skillContext_1.createSkillContext(ctx),
            parameters: !!event.data.SdmGoal[0].parameters ? JSON.parse(event.data.SdmGoal[0].parameters) : {},
        };
        const goalScheduler = await findGoalScheduler(goalInvocation, this.configuration);
        if (!!goalScheduler) {
            const start = Date.now();
            const result = await goalScheduler.schedule(goalInvocation);
            if (!!result && result.code !== undefined && result.code !== 0) {
                await storeGoals_1.updateGoal(ctx, sdmGoal, {
                    state: types_1.SdmGoalState.failure,
                    description: `Failed to schedule goal`,
                    url: progressLog.url,
                });
                await reportEndAndClose(result, start, progressLog);
            }
            else {
                await storeGoals_1.updateGoal(ctx, sdmGoal, {
                    state: !!result && !!result.state ? result.state : types_1.SdmGoalState.in_process,
                    phase: !!result && !!result.phase ? result.phase : "scheduled",
                    description: !!result && !!result.description
                        ? result.description
                        : storeGoals_1.descriptionFromState(goal, types_1.SdmGoalState.in_process, sdmGoal),
                    url: progressLog.url,
                    externalUrls: !!result ? result.externalUrls : undefined,
                });
            }
            return Object.assign(Object.assign({}, result), { 
                // successfully handled event even if goal failed
                code: 0 });
        }
        else {
            delete sdmGoal.id;
            const listeners = [];
            // Prepare cache project listeners for parameters
            if (!!goalInvocation.parameters) {
                if (!!goalInvocation.parameters[goalCaching_1.CacheInputGoalDataKey]) {
                    const input = goalInvocation.parameters[goalCaching_1.CacheInputGoalDataKey];
                    if (!!input && input.length > 0) {
                        listeners.push(goalCaching_1.cacheRestore({ entries: input }));
                    }
                }
                if (!!goalInvocation.parameters[goalCaching_1.CacheOutputGoalDataKey]) {
                    const output = goalInvocation.parameters[goalCaching_1.CacheOutputGoalDataKey];
                    if (!!output && output.length > 0) {
                        listeners.push(goalCaching_1.cachePut({ entries: output }));
                    }
                }
            }
            await reportStart(sdmGoal, progressLog);
            const start = Date.now();
            try {
                const result = await executeGoal_1.executeGoal({
                    projectLoader: this.configuration.sdm.projectLoader,
                    goalExecutionListeners: this.goalExecutionListeners,
                }, Object.assign(Object.assign({}, implementation), { projectListeners: [...array_1.toArray(implementation.projectListeners || []), ...listeners] }), goalInvocation);
                const terminatingStates = [
                    types_1.SdmGoalState.canceled,
                    types_1.SdmGoalState.failure,
                    types_1.SdmGoalState.skipped,
                    types_1.SdmGoalState.stopped,
                    types_1.SdmGoalState.success,
                    types_1.SdmGoalState.waiting_for_approval,
                ];
                if (!result || !result.state || terminatingStates.includes(result.state)) {
                    await reportEndAndClose(result, start, progressLog);
                }
                return Object.assign(Object.assign({}, result), { 
                    // successfully handled event even if goal failed
                    code: 0 });
            }
            catch (e) {
                e.message = `Goal executor threw exception: ${e.message}`;
                const egr = {
                    code: 1,
                    message: e.message,
                    state: types_1.SdmGoalState.failure,
                };
                await reportEndAndClose(egr, start, progressLog);
                throw e;
            }
        }
    }
};
__decorate([
    decorators_1.Value("") // empty path returns the entire configuration
    ,
    __metadata("design:type", Object)
], FulfillGoalOnRequested.prototype, "configuration", void 0);
FulfillGoalOnRequested = __decorate([
    decorators_1.EventHandler("Fulfill a goal when it reaches 'requested' state", graphQL_1.subscription("OnAnyRequestedSdmGoal")),
    __metadata("design:paramtypes", [Object, Array])
], FulfillGoalOnRequested);
exports.FulfillGoalOnRequested = FulfillGoalOnRequested;
async function findGoalScheduler(gi, configuration) {
    let goalSchedulers;
    if (!configuration.sdm.goalScheduler) {
        return undefined;
    }
    else if (!Array.isArray(configuration.sdm.goalScheduler)) {
        goalSchedulers = [configuration.sdm.goalScheduler];
    }
    else {
        goalSchedulers = configuration.sdm.goalScheduler;
    }
    for (const gl of goalSchedulers) {
        if (await gl.supports(gi)) {
            return gl;
        }
    }
    return undefined;
}
async function reportStart(sdmGoal, progressLog) {
    progressLog.write(`/--`);
    progressLog.write(`Start: ${dateFormat_1.formatDate(new Date(), "yyyy-mm-dd HH:MM:ss.l")}`);
    progressLog.write(`Repository: ${sdmGoal.push.repo.owner}/${sdmGoal.push.repo.name}/${sdmGoal.branch}`);
    progressLog.write(`Sha: ${sdmGoal.sha}`);
    progressLog.write(`Goal: ${sdmGoal.name} (${sdmGoal.uniqueName})`);
    progressLog.write(`Environment: ${sdmGoal.environment.slice(2)}`);
    progressLog.write(`GoalSet: ${sdmGoal.goalSet} - ${sdmGoal.goalSetId}`);
    progressLog.write(`Host: ${os.hostname()}`);
    progressLog.write(`SDM: ${globals_1.automationClientInstance().configuration.name}:${globals_1.automationClientInstance().configuration.version}`);
    progressLog.write("\\--");
    await progressLog.flush();
}
exports.reportStart = reportStart;
async function reportEndAndClose(result, start, progressLog) {
    progressLog.write(`/--`);
    progressLog.write(`Result: ${result_1.serializeResult(result)}`);
    progressLog.write(`Duration: ${time_1.formatDuration(Date.now() - start)}`);
    progressLog.write(`Finish: ${dateFormat_1.formatDate(new Date(), "yyyy-mm-dd HH:MM:ss.l")}`);
    progressLog.write("\\--");
    await progressLog.close();
}
exports.reportEndAndClose = reportEndAndClose;
//# sourceMappingURL=FulfillGoalOnRequested.js.map