// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { CosmosDiagnosticContext } from "./CosmosDiagnosticsContext.js";
import { ErrorResponse } from "../request/index.js";
import { CosmosDiagnostics, getRootNode } from "../CosmosDiagnostics.js";
import { getCurrentTimestampInMs } from "../utils/time.js";
import { CosmosDbDiagnosticLevel } from "./CosmosDbDiagnosticLevel.js";
import { Constants, prepareURL } from "../common/index.js";
import { allowTracing } from "./diagnosticLevelComparator.js";
import { randomUUID } from "@azure/core-util";
/**
 * @hidden
 * This is Internal Representation for DiagnosticNode. It contains useful helper functions to collect
 * diagnostic information throughout the lifetime of Diagnostic session.
 * The functions toDiagnosticNode() & toDiagnostic() are given to convert it to public facing counterpart.
 */
export class DiagnosticNodeInternal {
    id;
    nodeType;
    parent;
    children;
    data;
    startTimeUTCInMs;
    durationInMs;
    diagnosticLevel;
    diagnosticCtx;
    encryptionDiagnostics;
    /**
     * @internal
     */
    constructor(diagnosticLevel, type, parent, data = {}, startTimeUTCInMs = getCurrentTimestampInMs(), ctx = new CosmosDiagnosticContext()) {
        this.id = randomUUID();
        this.nodeType = type;
        this.startTimeUTCInMs = startTimeUTCInMs;
        this.data = data;
        this.children = [];
        this.durationInMs = 0;
        this.parent = parent;
        this.diagnosticCtx = ctx;
        this.diagnosticLevel = diagnosticLevel;
        // Initialize EncryptionDiagnostics
        this.encryptionDiagnostics = {
            encryptContent: {},
            decryptContent: {},
            processingDurationInMs: 0,
        };
    }
    /**
     * @internal
     */
    addLog(msg) {
        if (!this.data.log) {
            this.data.log = [];
        }
        this.data.log.push(msg);
    }
    /**
     * @internal
     */
    sanitizeHeaders(headers) {
        return headers;
    }
    /**
     * Updated durationInMs for node, based on endTimeUTCInMs provided.
     * @internal
     */
    updateTimestamp(endTimeUTCInMs = getCurrentTimestampInMs()) {
        this.durationInMs = endTimeUTCInMs - this.startTimeUTCInMs;
    }
    /**
     * @internal
     */
    recordSuccessfulNetworkCall(startTimeUTCInMs, requestContext, pipelineResponse, substatus, url) {
        const responseHeaders = pipelineResponse.headers.toJSON();
        const gatewayRequest = {
            activityId: responseHeaders[Constants.HttpHeaders.ActivityId],
            correlateActivityId: requestContext.headers[Constants.HttpHeaders.CorrelatedActivityId],
            startTimeUTCInMs,
            durationInMs: getCurrentTimestampInMs() - startTimeUTCInMs,
            statusCode: pipelineResponse.status,
            subStatusCode: substatus,
            requestPayloadLengthInBytes: calculateRequestPayloadLength(requestContext),
            responsePayloadLengthInBytes: calculateResponsePayloadLength(pipelineResponse),
            operationType: requestContext.operationType,
            resourceType: requestContext.resourceType,
            partitionKeyRangeId: requestContext.partitionKeyRangeId,
        };
        let requestData = {
            OperationType: gatewayRequest.operationType,
            resourceType: gatewayRequest.resourceType,
            requestPayloadLengthInBytes: gatewayRequest.requestPayloadLengthInBytes,
        };
        if (allowTracing(CosmosDbDiagnosticLevel.debugUnsafe, this.diagnosticLevel)) {
            requestData = {
                ...requestData,
                headers: this.sanitizeHeaders(requestContext.headers),
                requestBody: requestContext.body,
                responseBody: pipelineResponse.bodyAsText,
                url: url,
            };
        }
        this.addData({
            requestPayloadLengthInBytes: gatewayRequest.requestPayloadLengthInBytes,
            responsePayloadLengthInBytes: gatewayRequest.responsePayloadLengthInBytes,
            startTimeUTCInMs: gatewayRequest.startTimeUTCInMs,
            durationInMs: gatewayRequest.durationInMs,
            requestData,
        });
        this.diagnosticCtx.recordNetworkCall(gatewayRequest);
    }
    /**
     * @internal
     */
    recordFailedNetworkCall(startTimeUTCInMs, requestContext, retryAttemptNumber, statusCode, substatusCode, responseHeaders) {
        this.addData({ failedAttempty: true });
        const requestPayloadLengthInBytes = calculateRequestPayloadLength(requestContext);
        this.diagnosticCtx.recordFailedAttempt({
            activityId: responseHeaders[Constants.HttpHeaders.ActivityId],
            correlatedActivityId: requestContext.headers[Constants.HttpHeaders.CorrelatedActivityId],
            startTimeUTCInMs,
            durationInMs: getCurrentTimestampInMs() - startTimeUTCInMs,
            statusCode,
            subStatusCode: substatusCode,
            requestPayloadLengthInBytes,
            responsePayloadLengthInBytes: 0,
            operationType: requestContext.operationType,
            resourceType: requestContext.resourceType,
        }, retryAttemptNumber);
        let requestData = {
            OperationType: requestContext.operationType,
            resourceType: requestContext.resourceType,
            requestPayloadLengthInBytes,
        };
        if (allowTracing(CosmosDbDiagnosticLevel.debugUnsafe, this.diagnosticLevel)) {
            requestData = {
                ...requestData,
                headers: this.sanitizeHeaders(requestContext.headers),
                requestBody: requestContext.body,
                url: prepareURL(requestContext.endpoint, requestContext.path),
            };
        }
        this.addData({
            failedAttempty: true,
            requestData,
        });
    }
    /**
     * @internal
     */
    recordEndpointResolution(location) {
        this.addData({ selectedLocation: location });
        this.diagnosticCtx.recordEndpointResolution(location);
    }
    /**
     * @internal
     */
    addData(data, msg, level = this.diagnosticLevel) {
        if (level !== CosmosDbDiagnosticLevel.info) {
            this.data = { ...this.data, ...data };
            if (msg) {
                this.addLog(msg);
            }
        }
    }
    /**
     * Merge given DiagnosticNodeInternal's context to current node's DiagnosticContext, Treating GatewayRequests of
     * given DiagnosticContext, as metadata requests. Given DiagnosticNodeInternal becomes a child of this node.
     * @internal
     */
    addChildNode(child, level, metadataType) {
        this.diagnosticCtx.mergeDiagnostics(child.diagnosticCtx, metadataType);
        if (allowTracing(level, this.diagnosticLevel)) {
            child.parent = this;
            this.children.push(child);
        }
        return child;
    }
    /**
     * Merge given DiagnosticNodeInternal's context to current node's DiagnosticContext for bulk.
     * Given DiagnosticNodeInternal becomes a child of this node.
     * @internal
     */
    addBulkChildNode(child, level) {
        this.diagnosticCtx.mergeBulkDiagnostics(child.diagnosticCtx);
        if (allowTracing(level, this.diagnosticLevel)) {
            child.parent = this;
            this.children.push(child);
        }
        return child;
    }
    /**
     * @internal
     */
    initializeChildNode(type, level, data = {}) {
        if (allowTracing(level, this.diagnosticLevel)) {
            const child = new DiagnosticNodeInternal(this.diagnosticLevel, type, this, data, getCurrentTimestampInMs(), this.diagnosticCtx);
            this.children.push(child);
            return child;
        }
        else {
            return this;
        }
    }
    /**
     * @internal
     */
    recordQueryResult(resources, level) {
        if (allowTracing(level, this.diagnosticLevel)) {
            const previousCount = this.data.queryRecordsRead ?? 0;
            if (Array.isArray(resources)) {
                this.data.queryRecordsRead = previousCount + resources.length;
            }
        }
    }
    /**
     * @internal
     * record startTime for encryption in an operation
     */
    beginEncryptionDiagnostics(operation) {
        const startTime = getCurrentTimestampInMs();
        switch (operation) {
            case Constants.Encryption.DiagnosticsEncryptOperation:
                this.encryptionDiagnostics.encryptContent[Constants.Encryption.DiagnosticsStartTime] =
                    startTime;
                break;
            case Constants.Encryption.DiagnosticsDecryptOperation:
                this.encryptionDiagnostics.decryptContent[Constants.Encryption.DiagnosticsStartTime] =
                    startTime;
                break;
            default:
                throw new ErrorResponse("Invalid operation type for encryption diagnostics");
        }
    }
    /**
     * @internal
     * record duration from startTime and properties count for encryption in an operation
     */
    endEncryptionDiagnostics(operation, propertiesCount) {
        const endTime = getCurrentTimestampInMs();
        let processingDuration = 0;
        switch (operation) {
            case Constants.Encryption.DiagnosticsEncryptOperation:
                processingDuration =
                    endTime -
                        this.encryptionDiagnostics.encryptContent[Constants.Encryption.DiagnosticsStartTime];
                this.encryptionDiagnostics.encryptContent[Constants.Encryption.DiagnosticsDuration] =
                    processingDuration;
                // will be undefined in case of bulk/batch
                if (propertiesCount !== undefined) {
                    this.encryptionDiagnostics.encryptContent[Constants.Encryption.DiagnosticsPropertiesEncryptedCount] = propertiesCount;
                }
                break;
            case Constants.Encryption.DiagnosticsDecryptOperation:
                processingDuration =
                    endTime -
                        this.encryptionDiagnostics.decryptContent[Constants.Encryption.DiagnosticsStartTime];
                this.encryptionDiagnostics.decryptContent[Constants.Encryption.DiagnosticsDuration] =
                    processingDuration;
                if (propertiesCount !== undefined) {
                    this.encryptionDiagnostics.decryptContent[Constants.Encryption.DiagnosticsPropertiesDecryptedCount] = propertiesCount;
                }
                break;
            default:
                throw new ErrorResponse("Invalid operation type for encryption diagnostics");
        }
        this.diagnosticCtx.recordEncryptionDiagnostics(this.encryptionDiagnostics);
    }
    /**
     * Convert DiagnosticNodeInternal (internal representation) to DiagnosticNode (public, sanitized representation)
     * @internal
     */
    toDiagnosticNode() {
        return {
            id: this.id,
            nodeType: this.nodeType,
            children: this.children.map((child) => child.toDiagnosticNode()),
            data: this.data,
            startTimeUTCInMs: this.startTimeUTCInMs,
            durationInMs: this.durationInMs,
        };
    }
    /**
     * Convert to CosmosDiagnostics
     * @internal
     */
    toDiagnostic(clientConfigDiagnostic) {
        const rootNode = getRootNode(this);
        const diagnostiNode = allowTracing(CosmosDbDiagnosticLevel.debug, this.diagnosticLevel)
            ? rootNode.toDiagnosticNode()
            : undefined;
        const clientConfig = allowTracing(CosmosDbDiagnosticLevel.debug, this.diagnosticLevel)
            ? clientConfigDiagnostic
            : undefined;
        const cosmosDiagnostic = new CosmosDiagnostics(this.diagnosticCtx.getClientSideStats(), diagnostiNode, clientConfig);
        return cosmosDiagnostic;
    }
}
/**
 * @hidden
 */
export var DiagnosticNodeType;
(function (DiagnosticNodeType) {
    DiagnosticNodeType["CLIENT_REQUEST_NODE"] = "CLIENT_REQUEST_NODE";
    DiagnosticNodeType["METADATA_REQUEST_NODE"] = "METADATA_REQUEST_NODE";
    DiagnosticNodeType["HTTP_REQUEST"] = "HTTP_REQUEST";
    DiagnosticNodeType["BATCH_REQUEST"] = "BATCH_REQUEST";
    DiagnosticNodeType["PARALLEL_QUERY_NODE"] = "PARALLEL_QUERY_NODE";
    DiagnosticNodeType["DEFAULT_QUERY_NODE"] = "DEFAULT_QUERY_NODE";
    DiagnosticNodeType["QUERY_REPAIR_NODE"] = "QUERY_REPAIR_NODE";
    DiagnosticNodeType["BACKGROUND_REFRESH_THREAD"] = "BACKGROUND_REFRESH_THREAD";
    DiagnosticNodeType["REQUEST_ATTEMPTS"] = "REQUEST_ATTEMPTS";
})(DiagnosticNodeType || (DiagnosticNodeType = {}));
function calculateResponsePayloadLength(response) {
    return response?.bodyAsText?.length || 0;
}
function calculateRequestPayloadLength(requestContext) {
    return requestContext.body ? requestContext.body.length : 0;
}
//# sourceMappingURL=DiagnosticNodeInternal.js.map