/**
 *    Copyright (C) 2019-present MongoDB, Inc.
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the Server Side Public License, version 1,
 *    as published by MongoDB, Inc.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    Server Side Public License for more details.
 *
 *    You should have received a copy of the Server Side Public License
 *    along with this program. If not, see
 *    <http://www.mongodb.com/licensing/server-side-public-license>.
 *
 *    As a special exception, the copyright holders give permission to link the
 *    code of portions of this program with the OpenSSL library under certain
 *    conditions as described in each individual source file and distribute
 *    linked combinations including the program with the OpenSSL library. You
 *    must comply with the Server Side Public License in all respects for
 *    all of the code used other than as permitted herein. If you modify file(s)
 *    with this exception, you may extend this exception to your version of the
 *    file(s), but you are not obligated to do so. If you do not wish to do so,
 *    delete this exception statement from your version. If you delete this
 *    exception statement from all source files in the program, then also delete
 *    it in the license file.
 */

#pragma once

#include "mongo/db/client.h"
#include "mongo/rpc/metadata/client_metadata.h"
#include "mongo/util/tick_source.h"
#include "mongo/util/time_support.h"

namespace mongo {

/**
 * Tracks metrics for a single transaction coordinator.
 *
 * For all timing related stats, a TickSource with at least microsecond resolution must be used.
 */
class SingleTransactionCoordinatorStats {
public:
    SingleTransactionCoordinatorStats() = default;

    struct LastClientInfo {
        std::string clientHostAndPort;
        long long connectionId;
        BSONObj clientMetadata;
        std::string appName;

        void update(Client* client) {
            if (client->hasRemote()) {
                clientHostAndPort = client->getRemote().toString();
            }
            connectionId = client->getConnectionId();
            if (auto metadata = ClientMetadata::get(client)) {
                clientMetadata = metadata->getDocument();
                appName = metadata->getApplicationName().toString();
            }
        }
    };

    //
    // Setters
    //

    /**
     * Sets the time the transaction coordinator was created.
     *
     * Can only be called once.
     */
    void setCreateTime(TickSource::Tick curTick, Date_t curWallClockTime);

    /**
     * Sets the time the transaction coordinator was destroyed.
     *
     * Can only be called once, and must be called after setCreateTime.
     */
    void setEndTime(TickSource::Tick curTick, Date_t curWallClockTime);

    /**
     * Sets the time the transaction coordinator wrote the participant list and started waiting for
     * the participant list to become majority-committed.
     *
     * Can only be called once, and must be called after setCreateTime.
     */
    void setWritingParticipantListStartTime(TickSource::Tick curTick, Date_t curWallClockTime);

    /**
     * Sets the time the transaction coordinator sent 'prepare' to the participants and started
     * waiting for votes (i.e., 'prepare' responses).
     *
     * Can only be called once, and must be called after setWritingParticipantListStartTime.
     */
    void setWaitingForVotesStartTime(TickSource::Tick curTick, Date_t curWallClockTime);

    /**
     * Sets the time the transaction coordinator wrote the decision and started waiting for the
     * decision to become majority-committed.
     *
     * Can only be called once, and must be called after setWaitingForVotesStartTime.
     */
    void setWritingDecisionStartTime(TickSource::Tick curTick, Date_t curWallClockTime);

    /**
     * Sets the time the transaction coordinator sent the decision to the participants and started
     * waiting for acknowledgments.
     *
     * Can only be called once, and must be called after setWritingDecisionStartTime.
     */
    void setWaitingForDecisionAcksStartTime(TickSource::Tick curTick, Date_t curWallClockTime);

    /**
     * Sets the time the transaction coordinator deleted its durable state.
     *
     * Can only be called once, and must be called after setWaitingForDecisionAcksStartTime.
     */
    void setDeletingCoordinatorDocStartTime(TickSource::Tick curTick, Date_t curWallClockTime);

    //
    // Getters
    //

    /**
     * Returns the time the coordinator was created.
     *
     * Must be called after setCreateTime.
     */
    Date_t getCreateTime() const {
        return _createWallClockTime;
    }

    /**
     * Returns the time the coordinator was destroyed.
     *
     * Must be called after setCreateTime.
     */
    Date_t getEndTime() const {
        return _endWallClockTime;
    }

    /**
     * Returns the time the coordinator started writing the participant list. Note, this is also the
     * two-phase commit start time.
     *
     * Must be called after setWritingParticipantListStartTime.
     */
    Date_t getWritingParticipantListStartTime() const {
        return _writingParticipantListStartWallClockTime;
    }

    /**
     * Returns the time the coordinator started sending 'prepare' and collecting votes.
     *
     * Must be called after setWaitingForVotesStartTime.
     */
    Date_t getWaitingForVotesStartTime() const {
        return _waitingForVotesStartWallClockTime;
    }

    /**
     * Returns the time the coordinator started making the decision durable.
     *
     * Must be called after setWritingDecisionStartTime.
     */
    Date_t getWritingDecisionStartTime() const {
        return _writingDecisionStartWallClockTime;
    }

    /**
     * Returns the time the coordinator started sending the decision and waiting for
     * acknowledgments.
     *
     * Must be called after setWaitingForDecisionAcksStartTime.
     */
    Date_t getWaitingForDecisionAcksStartTime() const {
        return _waitingForDecisionAcksStartWallClockTime;
    }

    /**
     * Returns the time the coordinator started deleting its durable state.
     *
     * Must be called after setDeletingCoordinatorDocStartTime.
     */
    Date_t getDeletingCoordinatorDocStartTime() const {
        return _deletingCoordinatorDocStartWallClockTime;
    }

    /**
     * If the end time has been set, returns the duration between the create time and end time, else
     * returns the duration between the create time and curTick.
     *
     * Must be called after setCreateTime, but can be called any number of times.
     */
    Microseconds getDurationSinceCreation(TickSource* tickSource, TickSource::Tick curTick) const;

    /**
     * If the end time has been set, returns the duration between the writing participant list start
     * time and end time, else returns the duration between the writing participant list start time
     * and curTick.
     *
     * Must be called after setWritingParticipantListStartTime, but can be called any number of
     * times.
     */
    Microseconds getTwoPhaseCommitDuration(TickSource* tickSource, TickSource::Tick curTick) const;

    /**
     * If the waiting for votes start time has been set, returns the duration between the writing
     * participant list start time and the waiting for votes start time, else returns the duration
     * between the writing for participant list start time and curTick.
     *
     * Must be called after setWritingParticipantListStartTime, but can be called any number of
     * times.
     */
    Microseconds getWritingParticipantListDuration(TickSource* tickSource,
                                                   TickSource::Tick curTick) const;

    /**
     * If the writing decision start time has been set, returns the duration between the waiting for
     * votes start time and the writing decision start time, else returns the duration between
     * the waiting for votes start time and curTick.
     *
     * Must be called after setWaitingForVotesStartTime, but can be called any number of times.
     */
    Microseconds getWaitingForVotesDuration(TickSource* tickSource, TickSource::Tick curTick) const;

    /**
     * If the waiting for decision acks start time has been set, returns the duration between the
     * writing decision start time and the waiting for decision acks start time, else returns the
     * duration between the writing decision start time and curTick.
     *
     * Must be called after setWritingDecisionStartTime, but can be called any number of times.
     */
    Microseconds getWritingDecisionDuration(TickSource* tickSource, TickSource::Tick curTick) const;

    /**
     * If the deleting coordinator doc start time has been set, returns the duration between the
     * waiting for decision acks start time and the deleting coordinator doc start time, else
     * returns the duration between the waiting for decision acks start time and curTick.
     *
     * Must be called after setWaitingForDecisionAcksStartTime, but can be called any number of
     * times.
     */
    Microseconds getWaitingForDecisionAcksDuration(TickSource* tickSource,
                                                   TickSource::Tick curTick) const;

    /**
     * If the end time has been set, returns the duration between the deleting coordinator doc start
     * and the end time, else returns the duration between the deleting coordinator doc start time
     * and curTick.
     *
     * Must be called after setDeletingCoordinatorDocStartTime, but can be called any number of
     * times.
     */
    Microseconds getDeletingCoordinatorDocDuration(TickSource* tickSource,
                                                   TickSource::Tick curTick) const;

    /**
     * Reports the time duration for each step in the two-phase commit and stores them as a
     * sub-document of the provided parent BSONObjBuilder. The metrics are stored under key
     * "stepDurations" in the parent document.
     */
    void reportMetrics(BSONObjBuilder& parent,
                       TickSource* tickSource,
                       TickSource::Tick curTick) const;

    /**
     * Reports information about the last client to interact with this transaction.
     */
    void reportLastClient(BSONObjBuilder& parent) const;

    /**
     * Updates the LastClientInfo object stored in this SingleTransactionStats instance with the
     * given Client's information.
     */
    void updateLastClientInfo(Client* client) {
        invariant(client);
        _lastClientInfo.update(client);
    }
    /*
     * Marks this transaction coordinator has having recovered from failure.
     */
    void setRecoveredFromFailover();

private:
    Date_t _createWallClockTime;
    TickSource::Tick _createTime{0};

    // The writing participant list start time doubles as the two-phase commit start time, since
    // writing the participant list is the first step of the two-phase commit.
    Date_t _writingParticipantListStartWallClockTime;
    TickSource::Tick _writingParticipantListStartTime{0};

    Date_t _waitingForVotesStartWallClockTime;
    TickSource::Tick _waitingForVotesStartTime{0};

    Date_t _writingDecisionStartWallClockTime;
    TickSource::Tick _writingDecisionStartTime{0};

    Date_t _waitingForDecisionAcksStartWallClockTime;
    TickSource::Tick _waitingForDecisionAcksStartTime{0};

    Date_t _deletingCoordinatorDocStartWallClockTime;
    TickSource::Tick _deletingCoordinatorDocStartTime{0};

    Date_t _endWallClockTime;
    TickSource::Tick _endTime{0};

    LastClientInfo _lastClientInfo;
    bool _hasRecoveredFromFailover = false;
};

}  // namespace mongo
