// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { __assign, __awaiter, __extends, __generator } from "tslib";
import { delay, RestError, stripRequest, WebResource } from "@azure/ms-rest-js";
/**
 * A long-running operation polling strategy base class that other polling strategies should extend.
 */
var LROPollStrategy = /** @class */ (function () {
    function LROPollStrategy(_azureServiceClient, _pollState) {
        this._azureServiceClient = _azureServiceClient;
        this._pollState = _pollState;
    }
    LROPollStrategy.prototype.getOperationStatus = function () {
        return this._pollState.state;
    };
    /**
     * Get whether or not this poll strategy's LRO is finished.
     * @returns Whether or not this poll strategy's LRO is finished.
     */
    LROPollStrategy.prototype.isFinished = function () {
        return isFinished(this._pollState.state);
    };
    /**
     * Send poll requests that check the LRO's status until it is determined that the LRO is finished.
     * @returns Whether or not the LRO succeeded.
     */
    LROPollStrategy.prototype.pollUntilFinished = function () {
        return __awaiter(this, void 0, void 0, function () {
            var delayInSeconds;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!!this.isFinished()) return [3 /*break*/, 3];
                        delayInSeconds = getDelayInSeconds(this._azureServiceClient, this._pollState.mostRecentResponse);
                        return [4 /*yield*/, delay(delayInSeconds * 1000)];
                    case 1:
                        _a.sent();
                        return [4 /*yield*/, this.sendPollRequest()];
                    case 2:
                        _a.sent();
                        return [3 /*break*/, 0];
                    case 3: return [2 /*return*/, this.isFinalStatusAcceptable()];
                }
            });
        });
    };
    LROPollStrategy.prototype.shouldDoFinalGetResourceRequest = function () {
        var initialRequestMethod = this._pollState.initialResponse.request.method;
        return !this._pollState.resource && (initialRequestMethod === "PUT" || initialRequestMethod === "PATCH" || initialRequestMethod === "POST");
    };
    LROPollStrategy.prototype.getMostRecentResponse = function () {
        return this._pollState.mostRecentResponse;
    };
    LROPollStrategy.prototype.getOperationResponse = function () {
        return __awaiter(this, void 0, void 0, function () {
            var response, result, resource;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!this.shouldDoFinalGetResourceRequest()) return [3 /*break*/, 2];
                        return [4 /*yield*/, this.doFinalGetResourceRequest()];
                    case 1:
                        _a.sent();
                        _a.label = 2;
                    case 2:
                        response = this._pollState.mostRecentResponse;
                        result = __assign(__assign({}, response), { headers: response.headers.clone() });
                        resource = this._pollState.resource;
                        if (!resource) {
                            result.bodyAsText = response.bodyAsText;
                            result.parsedBody = response.parsedBody;
                        }
                        else if (typeof resource.valueOf() === "string") {
                            result.bodyAsText = resource;
                            try {
                                result.parsedBody = JSON.parse(resource);
                            }
                            catch (err) {
                                // There was an error parsing the JSON. Hence we set the resource as-is. Most likely, the
                                // resource is a string that was already parsed.
                                result.parsedBody = resource;
                            }
                        }
                        else {
                            result.bodyAsText = JSON.stringify(resource);
                            result.parsedBody = resource;
                        }
                        return [2 /*return*/, result];
                }
            });
        });
    };
    LROPollStrategy.prototype.getRestError = function () {
        var error = new RestError("");
        error.request = stripRequest(this._pollState.mostRecentRequest);
        error.response = this._pollState.mostRecentResponse;
        error.message = "Long running operation failed with status: \"" + this._pollState.state + "\".";
        error.body = this._pollState.resource;
        if (error.body) {
            var innerError = error.body.error;
            if (innerError) {
                if (innerError.message) {
                    error.message = "Long running operation failed with error: \"" + innerError.message + "\".";
                }
                if (innerError.code) {
                    error.code = innerError.code;
                }
            }
        }
        return error;
    };
    LROPollStrategy.prototype.updateState = function (url, shouldDeserialize) {
        var _this = this;
        return this.updateOperationStatus(url, shouldDeserialize).then(function (result) {
            _this._pollState.state = getProvisioningState(result.parsedBody) || "Succeeded";
            _this._pollState.mostRecentResponse = result;
            _this._pollState.mostRecentRequest = result.request;
            _this._pollState.resource = getResponseBody(result);
        }).catch(function (error) {
            var resultStatus;
            if (error.response && error.response.status) {
                resultStatus = error.response.status;
                if (_this._pollState.initialResponse.request.method !== "DELETE" || resultStatus < 400 || 499 < resultStatus) {
                    throw error;
                }
            }
            else {
                throw error;
            }
        });
    };
    /**
     * Retrieves operation status by querying the operation URL.
     * @param {string} statusUrl URL used to poll operation result.
     */
    LROPollStrategy.prototype.updateOperationStatus = function (statusUrl, shouldDeserialize) {
        var requestUrl = statusUrl.replace(" ", "%20");
        var httpRequest = new WebResource(requestUrl, "GET");
        var pollState = this._pollState;
        httpRequest.operationSpec = pollState.mostRecentRequest.operationSpec;
        httpRequest.shouldDeserialize = shouldDeserialize;
        httpRequest.operationResponseGetter = getOperationResponse;
        var options = pollState.options;
        if (options && options.customHeaders) {
            var customHeaders = options.customHeaders;
            for (var _i = 0, _a = Object.keys(customHeaders); _i < _a.length; _i++) {
                var headerName = _a[_i];
                httpRequest.headers.set(headerName, customHeaders[headerName]);
            }
        }
        return this._azureServiceClient.sendRequest(httpRequest);
    };
    LROPollStrategy.prototype.getPollState = function () {
        return this._pollState;
    };
    return LROPollStrategy;
}());
export { LROPollStrategy };
function getOperationResponse(operationSpec, response) {
    var statusCode = response.status;
    var operationResponses = operationSpec.responses;
    var result = operationResponses[statusCode];
    if (!result) {
        if (statusCode === 200) {
            result = operationResponses[201] || operationResponses[202];
        }
        else if (201 <= statusCode && statusCode <= 299) {
            result = {};
        }
    }
    return result;
}
export function getDelayInSeconds(azureServiceClient, previousResponse) {
    var delayInSeconds = 30;
    if (azureServiceClient.longRunningOperationRetryTimeout != undefined) {
        delayInSeconds = azureServiceClient.longRunningOperationRetryTimeout;
    }
    else {
        var retryAfterHeaderValue = previousResponse.headers.get("retry-after");
        if (retryAfterHeaderValue) {
            var retryAfterDelayInSeconds = parseInt(retryAfterHeaderValue);
            if (!Number.isNaN(retryAfterDelayInSeconds)) {
                delayInSeconds = retryAfterDelayInSeconds;
            }
        }
    }
    return delayInSeconds;
}
function getProvisioningState(responseBody) {
    var result;
    if (responseBody) {
        if (responseBody.provisioningState) {
            result = responseBody.provisioningState;
        }
        else if (responseBody.properties) {
            result = responseBody.properties.provisioningState;
        }
    }
    return result;
}
function getResponseBody(response) {
    var result;
    try {
        if (response.parsedBody) {
            result = response.parsedBody;
        }
        else if (response.bodyAsText && response.bodyAsText.length > 0) {
            result = JSON.parse(response.bodyAsText);
        }
    }
    catch (error) {
        var deserializationError = new RestError("Error \"" + error + "\" occurred in parsing the responseBody \" +\n      \"while creating the PollingState for Long Running Operation- \"" + response.bodyAsText + "\"");
        deserializationError.request = response.request;
        deserializationError.response = response;
        throw deserializationError;
    }
    return result;
}
function getStatusFromResponse(response, responseBody) {
    if (responseBody == undefined) {
        responseBody = getResponseBody(response);
    }
    var result;
    switch (response.status) {
        case 202:
            result = "InProgress";
            break;
        case 204:
            result = "Succeeded";
            break;
        case 201:
            result = getProvisioningState(responseBody) || "InProgress";
            break;
        case 200:
            var provisioningState = getProvisioningState(responseBody);
            if (provisioningState) {
                result = provisioningState;
            }
            else if (getAzureAsyncOperationHeaderValue(response) || getLocationHeaderValue(response)) {
                result = "InProgress";
            }
            else {
                result = "Succeeded";
            }
            break;
        default:
            result = "Failed";
            break;
    }
    return result;
}
var terminalStates = ["Succeeded", "Failed", "Canceled", "Cancelled"];
/**
 * Get whether or not a long-running operation with the provided status is finished.
 * @param status The current status of a long-running operation.
 * @returns Whether or not a long-running operation with the provided status is finished.
 */
export function isFinished(status) {
    var result = false;
    for (var _i = 0, terminalStates_1 = terminalStates; _i < terminalStates_1.length; _i++) {
        var terminalState = terminalStates_1[_i];
        if (longRunningOperationStatesEqual(status, terminalState)) {
            result = true;
            break;
        }
    }
    return result;
}
export function longRunningOperationStatesEqual(lhs, rhs) {
    var lhsLowerCased = lhs && lhs.toLowerCase();
    var rhsLowerCased = rhs && rhs.toLowerCase();
    return lhsLowerCased === rhsLowerCased;
}
/**
 * Create a new long-running operation polling strategy based on the provided initial response.
 * @param initialResponse The initial response to the long-running operation's initial request.
 * @param azureServiceClient The AzureServiceClient that was used to send the initial request.
 * @param options Any options that were provided to the initial request.
 */
export function createLROPollStrategyFromInitialResponse(initialResponse, azureServiceClient, options) {
    var initialRequestMethod = initialResponse.request.method;
    var initialResponseStatus = initialResponse.status;
    var lroPollStrategyType;
    if (getAzureAsyncOperationHeaderValue(initialResponse)) {
        lroPollStrategyType = "AzureAsyncOperation";
    }
    else if (getLocationHeaderValue(initialResponse)) {
        lroPollStrategyType = "Location";
    }
    else if (initialRequestMethod === "PUT" || initialRequestMethod === "PATCH") {
        lroPollStrategyType = "GetResource";
    }
    else if (initialResponseStatus !== 201 && initialResponseStatus !== 202 && !isFinished(getStatusFromResponse(initialResponse))) {
        throw new Error("Can't determine long running operation polling strategy.");
    }
    var result;
    if (lroPollStrategyType) {
        var resource = getResponseBody(initialResponse);
        var lroPollState = {
            pollStrategyType: lroPollStrategyType,
            options: options,
            initialResponse: initialResponse,
            mostRecentResponse: initialResponse,
            mostRecentRequest: initialResponse.request,
            azureAsyncOperationHeaderValue: getAzureAsyncOperationHeaderValue(initialResponse),
            locationHeaderValue: getLocationHeaderValue(initialResponse),
            resource: resource,
            state: getStatusFromResponse(initialResponse, resource)
        };
        result = createLROPollStrategyFromPollState(azureServiceClient, lroPollState);
    }
    else {
        result = undefined;
    }
    return result;
}
export function createLROPollStrategyFromPollState(azureServiceClient, lroPollState) {
    var result;
    switch (lroPollState.pollStrategyType) {
        case "AzureAsyncOperation":
            result = new AzureAsyncOperationLROPollStrategy(azureServiceClient, lroPollState);
            break;
        case "Location":
            result = new LocationLROPollStrategy(azureServiceClient, lroPollState);
            break;
        case "GetResource":
            result = new GetResourceLROPollStrategy(azureServiceClient, lroPollState);
            break;
        default:
            throw new Error("Unrecognized LRO poll strategy type: \"" + lroPollState.pollStrategyType + "\"");
            break;
    }
    return result;
}
function getLocationHeaderValue(response) {
    return response.headers.get("location");
}
/**
 * A long-running operation polling strategy that is based on the location header.
 */
var LocationLROPollStrategy = /** @class */ (function (_super) {
    __extends(LocationLROPollStrategy, _super);
    function LocationLROPollStrategy() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    LocationLROPollStrategy.prototype.locationStrategyShouldDeserialize = function (parsedResponse) {
        var shouldDeserialize = false;
        var initialResponse = this._pollState.initialResponse;
        var initialRequestMethod = initialResponse.request.method;
        var statusCode = parsedResponse.status;
        if (statusCode === 200 ||
            (statusCode === 201 && (initialRequestMethod === "PUT" || initialRequestMethod === "PATCH")) ||
            (statusCode === 204 && (initialRequestMethod === "DELETE" || initialRequestMethod === "POST"))) {
            shouldDeserialize = true;
        }
        return shouldDeserialize;
    };
    /**
     * Retrieve PUT operation status by polling from "location" header.
     * @param {string} method - The HTTP method.
     * @param {PollingState} pollingState - The object to persist current operation state.
     */
    LocationLROPollStrategy.prototype.sendPollRequest = function () {
        var lroPollState = this._pollState;
        return this.updateOperationStatus(lroPollState.locationHeaderValue, this.locationStrategyShouldDeserialize.bind(this)).then(function (result) {
            var locationHeaderValue = getLocationHeaderValue(result);
            if (locationHeaderValue) {
                lroPollState.locationHeaderValue = locationHeaderValue;
            }
            lroPollState.mostRecentResponse = result;
            lroPollState.mostRecentRequest = result.request;
            var initialResponse = lroPollState.initialResponse;
            var initialRequestMethod = initialResponse.request.method;
            var initialResponseStatusCode = initialResponse.status;
            var statusCode = result.status;
            if (statusCode === 202) {
                lroPollState.state = "InProgress";
            }
            else if (statusCode === 200 ||
                (statusCode === 201 && (initialRequestMethod === "PUT" || initialRequestMethod === "PATCH")) ||
                (statusCode === 204 && (initialRequestMethod === "DELETE" || initialRequestMethod === "POST"))) {
                lroPollState.state = "Succeeded";
                lroPollState.resource = getResponseBody(result);
            }
            else if (statusCode === 404 && initialRequestMethod === "POST" &&
                (initialResponseStatusCode === 200 || initialResponseStatusCode === 201 || initialResponseStatusCode === 202)) {
                lroPollState.state = "Failed";
                lroPollState.resource = getResponseBody(result);
            }
            else if (400 <= statusCode && statusCode <= 499) {
                var resultBody = result.bodyAsText;
                var errorMessage = resultBody;
                try {
                    var resultObject = JSON.parse(resultBody);
                    errorMessage = resultObject.message;
                }
                catch (parseError) {
                    // Ignore the exception, use resultBody as the error message
                }
                throw new RestError(errorMessage, undefined, statusCode, stripRequest(result.request), result, resultBody);
            }
            else {
                throw new Error("The response with status code " + statusCode + " from polling for long running operation url \"" + lroPollState.locationHeaderValue + "\" is not valid.");
            }
        });
    };
    LocationLROPollStrategy.prototype.isFinalStatusAcceptable = function () {
        var lroPollState = this._pollState;
        var initialResponse = lroPollState.initialResponse;
        var initialResponseStatusCode = initialResponse.status;
        return longRunningOperationStatesEqual(lroPollState.state, "Succeeded") ||
            (initialResponse.request.method === "POST" && lroPollState.mostRecentResponse.status === 404 &&
                (initialResponseStatusCode === 200 ||
                    initialResponseStatusCode === 201 ||
                    initialResponseStatusCode === 202));
    };
    LocationLROPollStrategy.prototype.shouldDoFinalGetResourceRequest = function () {
        var lroPollState = this._pollState;
        var initialResponse = lroPollState.initialResponse;
        var result;
        var initialRequestMethod = initialResponse.request.method;
        var initialResponseStatusCode = initialResponse.status;
        if (initialRequestMethod === "POST" && lroPollState.mostRecentResponse.status === 404 &&
            (initialResponseStatusCode === 200 ||
                initialResponseStatusCode === 201 ||
                initialResponseStatusCode === 202)) {
            result = false;
        }
        else {
            result = _super.prototype.shouldDoFinalGetResourceRequest.call(this) ||
                (initialRequestMethod === "POST" && initialResponseStatusCode === 201);
        }
        return result;
    };
    LocationLROPollStrategy.prototype.doFinalGetResourceRequest = function () {
        var lroPollState = this._pollState;
        var initialResponse = lroPollState.initialResponse;
        var getResourceRequestUrl;
        var initialResponseStatusCode = initialResponse.status;
        var initialRequest = initialResponse.request;
        if (initialRequest.method === "POST" &&
            (initialResponseStatusCode === 200 ||
                initialResponseStatusCode === 201 ||
                initialResponseStatusCode === 202)) {
            getResourceRequestUrl = lroPollState.locationHeaderValue;
        }
        else {
            getResourceRequestUrl = initialRequest.url;
        }
        return this.updateState(getResourceRequestUrl, true);
    };
    return LocationLROPollStrategy;
}(LROPollStrategy));
function getAzureAsyncOperationHeaderValue(response) {
    return response.headers.get("azure-asyncoperation");
}
/**
 * A long-running operation polling strategy that is based on the azure-asyncoperation header.
 */
var AzureAsyncOperationLROPollStrategy = /** @class */ (function (_super) {
    __extends(AzureAsyncOperationLROPollStrategy, _super);
    function AzureAsyncOperationLROPollStrategy() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    /**
     * Retrieve operation status by polling from "azure-asyncoperation" header.
     * @param {PollingState} pollingState - The object to persist current operation state.
     * @param {boolean} inPostOrDelete - Invoked by Post Or Delete operation.
     */
    AzureAsyncOperationLROPollStrategy.prototype.sendPollRequest = function () {
        var lroPollState = this._pollState;
        return this.updateOperationStatus(lroPollState.azureAsyncOperationHeaderValue, false).then(function (response) {
            var statusCode = response.status;
            var parsedResponse = response.parsedBody;
            if (statusCode !== 200 && statusCode !== 201 && statusCode !== 202 && statusCode !== 204) {
                var error = new RestError("Invalid status code (" + statusCode + ") with response body \"" + response.bodyAsText + "\" occurred when polling for operation status.");
                error.statusCode = statusCode;
                error.request = stripRequest(response.request);
                error.response = response;
                error.body = parsedResponse;
                throw error;
            }
            if (!parsedResponse) {
                throw new Error("The response from long running operation does not contain a body.");
            }
            else if (!parsedResponse.status) {
                throw new Error("The response \"" + response.bodyAsText + "\" from long running operation does not contain the status property.");
            }
            var azureAsyncOperationHeaderValue = getAzureAsyncOperationHeaderValue(response);
            if (azureAsyncOperationHeaderValue) {
                lroPollState.azureAsyncOperationHeaderValue = azureAsyncOperationHeaderValue;
            }
            lroPollState.state = parsedResponse.status;
            lroPollState.mostRecentResponse = response;
            lroPollState.mostRecentRequest = response.request;
            lroPollState.resource = getResponseBody(response);
        });
    };
    AzureAsyncOperationLROPollStrategy.prototype.shouldDoFinalGetResourceRequest = function () {
        var lroPollState = this._pollState;
        var initialResponse = lroPollState.initialResponse;
        var initialRequestMethod = initialResponse.request.method;
        var result = false;
        if (initialRequestMethod === "PUT" || initialRequestMethod === "PATCH") {
            result = true;
        }
        else {
            if (lroPollState.locationHeaderValue) {
                var initialResponseStatusCode = initialResponse.status;
                if (initialRequestMethod === "POST") {
                    result = initialResponseStatusCode === 200 || initialResponseStatusCode === 201;
                }
                else if (initialRequestMethod === "DELETE") {
                    result = initialResponseStatusCode === 200 || initialResponseStatusCode === 202;
                }
            }
        }
        return result;
    };
    AzureAsyncOperationLROPollStrategy.prototype.doFinalGetResourceRequest = function () {
        var lroPollState = this._pollState;
        var locationHeaderValue = lroPollState.locationHeaderValue;
        var initialResponse = lroPollState.initialResponse;
        var initialRequest = initialResponse.request;
        var getResourceRequestUrl = initialRequest.url;
        if (locationHeaderValue) {
            var initialRequestMethod = initialRequest.method;
            var initialResponseStatusCode = initialResponse.status;
            if (initialRequestMethod === "POST" && (initialResponseStatusCode === 200 || initialResponseStatusCode === 201 || initialResponseStatusCode === 202)) {
                getResourceRequestUrl = locationHeaderValue;
            }
            else if (initialRequestMethod === "DELETE" && (initialResponseStatusCode === 200 || initialResponseStatusCode === 202)) {
                getResourceRequestUrl = locationHeaderValue;
            }
        }
        return this.updateState(getResourceRequestUrl, true);
    };
    AzureAsyncOperationLROPollStrategy.prototype.isFinalStatusAcceptable = function () {
        var lroPollState = this._pollState;
        var initialResponse = lroPollState.initialResponse;
        var initialResponseStatusCode = initialResponse.status;
        return longRunningOperationStatesEqual(lroPollState.state, "Succeeded") ||
            (initialResponse.request.method === "POST" && (initialResponseStatusCode === 200 || initialResponseStatusCode === 201));
    };
    return AzureAsyncOperationLROPollStrategy;
}(LROPollStrategy));
/**
 * A long-running operation polling strategy that is based on the resource's provisioning state.
 */
var GetResourceLROPollStrategy = /** @class */ (function (_super) {
    __extends(GetResourceLROPollStrategy, _super);
    function GetResourceLROPollStrategy() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    GetResourceLROPollStrategy.prototype.sendPollRequest = function () {
        var lroPollState = this._pollState;
        return this.updateOperationStatus(lroPollState.initialResponse.request.url, false).then(function (result) {
            var statusCode = result.status;
            var responseBody = result.parsedBody;
            if (statusCode !== 200 && statusCode !== 201 && statusCode !== 202 && statusCode !== 204) {
                var error = new RestError("Invalid status code with response body \"" + result.bodyAsText + "\" occurred when polling for operation status.");
                error.statusCode = statusCode;
                error.request = stripRequest(result.request);
                error.response = result;
                error.body = responseBody;
                throw error;
            }
            if (!result.parsedBody) {
                throw new Error("The response from long running operation does not contain a body.");
            }
            lroPollState.state = getProvisioningState(result.parsedBody) || "Succeeded";
            lroPollState.mostRecentResponse = result;
            lroPollState.mostRecentRequest = result.request;
            lroPollState.resource = getResponseBody(result);
        });
    };
    GetResourceLROPollStrategy.prototype.isFinalStatusAcceptable = function () {
        return longRunningOperationStatesEqual(this._pollState.state, "Succeeded");
    };
    GetResourceLROPollStrategy.prototype.doFinalGetResourceRequest = function () {
        return this.sendPollRequest();
    };
    return GetResourceLROPollStrategy;
}(LROPollStrategy));
//# sourceMappingURL=lroPollStrategy.js.map