# Copyright 2015 MongoDB, 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.
from __future__ import annotations

from collections import namedtuple

try:
    from mockupdb import OpMsgReply, OpReply

    _HAVE_MOCKUPDB = True
except ImportError:
    _HAVE_MOCKUPDB = False

from pymongo import ReadPreference

__all__ = ["operations", "upgrades"]


Operation = namedtuple("Operation", ["name", "function", "reply", "op_type", "not_master"])
"""Client operations on MongoDB.

Each has a human-readable name, a function that actually executes a test, and
a type that maps to one of the types in the Server Selection Spec:
'may-use-secondary', 'must-use-primary', etc.

The special type 'always-use-secondary' applies to an operation with an explicit
read mode, like the operation "command('c', read_preference=SECONDARY)".

The not-master response is how a secondary responds to a must-use-primary op,
or how a recovering member responds to a may-use-secondary op.

Example uses:

We can use "find_one" to validate that the SlaveOk bit is set when querying a
standalone, even with mode PRIMARY, but that it isn't set when sent to a mongos
with mode PRIMARY. Or it can validate that "$readPreference" is included in
mongos queries except with mode PRIMARY or SECONDARY_PREFERRED (PYTHON-865).

We can use "options_old" and "options_new" to test that the driver queries an
old server's system.namespaces collection, but uses the listCollections command
on a new server (PYTHON-857).

"secondary command" is good to test that the client can direct reads to
secondaries in a replica set, or select a mongos for secondary reads in a
sharded cluster (PYTHON-868).
"""

if _HAVE_MOCKUPDB:
    not_master_reply = OpMsgReply(ok=0, errmsg="not master")

    operations = [
        Operation(
            "find_one",
            lambda client: client.db.collection.find_one(),
            reply={"cursor": {"id": 0, "firstBatch": []}},
            op_type="may-use-secondary",
            not_master=not_master_reply,
        ),
        Operation(
            "count_documents",
            lambda client: client.db.collection.count_documents({}),
            reply={"n": 1},
            op_type="may-use-secondary",
            not_master=not_master_reply,
        ),
        Operation(
            "estimated_document_count",
            lambda client: client.db.collection.estimated_document_count(),
            reply={"n": 1},
            op_type="may-use-secondary",
            not_master=not_master_reply,
        ),
        Operation(
            "aggregate",
            lambda client: client.db.collection.aggregate([]),
            reply={"cursor": {"id": 0, "firstBatch": []}},
            op_type="may-use-secondary",
            not_master=not_master_reply,
        ),
        Operation(
            "options",
            lambda client: client.db.collection.options(),
            reply={"cursor": {"id": 0, "firstBatch": []}},
            op_type="must-use-primary",
            not_master=not_master_reply,
        ),
        Operation(
            "command",
            lambda client: client.db.command("foo"),
            reply={"ok": 1},
            op_type="must-use-primary",  # Ignores client's read preference.
            not_master=not_master_reply,
        ),
        Operation(
            "secondary command",
            lambda client: client.db.command("foo", read_preference=ReadPreference.SECONDARY),
            reply={"ok": 1},
            op_type="always-use-secondary",
            not_master=OpReply(ok=0, errmsg="node is recovering"),
        ),
        Operation(
            "listIndexes",
            lambda client: client.db.collection.index_information(),
            reply={"cursor": {"id": 0, "firstBatch": []}},
            op_type="must-use-primary",
            not_master=not_master_reply,
        ),
    ]
else:
    operations = []

_ops_by_name = {op.name: op for op in operations}

Upgrade = namedtuple("Upgrade", ["name", "function", "old", "new", "wire_version"])

upgrades = []
