# Copyright 2014-present 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
#
# https://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.

"""Represent MongoClient's configuration."""
from __future__ import annotations

import threading
import traceback
from typing import Any, Collection, Optional, Type, Union

from bson.objectid import ObjectId
from pymongo import common
from pymongo.asynchronous import monitor, pool
from pymongo.asynchronous.pool import Pool
from pymongo.common import LOCAL_THRESHOLD_MS, SERVER_SELECTION_TIMEOUT
from pymongo.errors import ConfigurationError
from pymongo.pool_options import PoolOptions
from pymongo.server_description import ServerDescription
from pymongo.topology_description import TOPOLOGY_TYPE, _ServerSelector

_IS_SYNC = False


class TopologySettings:
    def __init__(
        self,
        seeds: Optional[Collection[tuple[str, int]]] = None,
        replica_set_name: Optional[str] = None,
        pool_class: Optional[Type[Pool]] = None,
        pool_options: Optional[PoolOptions] = None,
        monitor_class: Optional[Type[monitor.Monitor]] = None,
        condition_class: Optional[Type[threading.Condition]] = None,
        local_threshold_ms: int = LOCAL_THRESHOLD_MS,
        server_selection_timeout: int = SERVER_SELECTION_TIMEOUT,
        heartbeat_frequency: int = common.HEARTBEAT_FREQUENCY,
        server_selector: Optional[_ServerSelector] = None,
        fqdn: Optional[str] = None,
        direct_connection: Optional[bool] = False,
        load_balanced: Optional[bool] = None,
        srv_service_name: str = common.SRV_SERVICE_NAME,
        srv_max_hosts: int = 0,
        server_monitoring_mode: str = common.SERVER_MONITORING_MODE,
        topology_id: Optional[ObjectId] = None,
    ):
        """Represent MongoClient's configuration.

        Take a list of (host, port) pairs and optional replica set name.
        """
        if heartbeat_frequency < common.MIN_HEARTBEAT_INTERVAL:
            raise ConfigurationError(
                "heartbeatFrequencyMS cannot be less than %d"
                % (common.MIN_HEARTBEAT_INTERVAL * 1000,)
            )

        self._seeds: Collection[tuple[str, int]] = seeds or [("localhost", 27017)]
        self._replica_set_name = replica_set_name
        self._pool_class: Type[Pool] = pool_class or pool.Pool
        self._pool_options: PoolOptions = pool_options or PoolOptions()
        self._monitor_class: Type[monitor.Monitor] = monitor_class or monitor.Monitor
        self._condition_class: Type[threading.Condition] = condition_class or threading.Condition
        self._local_threshold_ms = local_threshold_ms
        self._server_selection_timeout = server_selection_timeout
        self._server_selector = server_selector
        self._fqdn = fqdn
        self._heartbeat_frequency = heartbeat_frequency
        self._direct = direct_connection
        self._load_balanced = load_balanced
        self._srv_service_name = srv_service_name
        self._srv_max_hosts = srv_max_hosts or 0
        self._server_monitoring_mode = server_monitoring_mode
        if topology_id is not None:
            self._topology_id = topology_id
        else:
            self._topology_id = ObjectId()
        # Store the allocation traceback to catch unclosed clients in the
        # test suite.
        self._stack = "".join(traceback.format_stack()[:-2])

    @property
    def seeds(self) -> Collection[tuple[str, int]]:
        """List of server addresses."""
        return self._seeds

    @property
    def replica_set_name(self) -> Optional[str]:
        return self._replica_set_name

    @property
    def pool_class(self) -> Type[Pool]:
        return self._pool_class

    @property
    def pool_options(self) -> PoolOptions:
        return self._pool_options

    @property
    def monitor_class(self) -> Type[monitor.Monitor]:
        return self._monitor_class

    @property
    def condition_class(self) -> Type[threading.Condition]:
        return self._condition_class

    @property
    def local_threshold_ms(self) -> int:
        return self._local_threshold_ms

    @property
    def server_selection_timeout(self) -> int:
        return self._server_selection_timeout

    @property
    def server_selector(self) -> Optional[_ServerSelector]:
        return self._server_selector

    @property
    def heartbeat_frequency(self) -> int:
        return self._heartbeat_frequency

    @property
    def fqdn(self) -> Optional[str]:
        return self._fqdn

    @property
    def direct(self) -> Optional[bool]:
        """Connect directly to a single server, or use a set of servers?

        True if there is one seed and no replica_set_name.
        """
        return self._direct

    @property
    def load_balanced(self) -> Optional[bool]:
        """True if the client was configured to connect to a load balancer."""
        return self._load_balanced

    @property
    def srv_service_name(self) -> str:
        """The srvServiceName."""
        return self._srv_service_name

    @property
    def srv_max_hosts(self) -> int:
        """The srvMaxHosts."""
        return self._srv_max_hosts

    @property
    def server_monitoring_mode(self) -> str:
        """The serverMonitoringMode."""
        return self._server_monitoring_mode

    def get_topology_type(self) -> int:
        if self.load_balanced:
            return TOPOLOGY_TYPE.LoadBalanced
        elif self.direct:
            return TOPOLOGY_TYPE.Single
        elif self.replica_set_name is not None:
            return TOPOLOGY_TYPE.ReplicaSetNoPrimary
        else:
            return TOPOLOGY_TYPE.Unknown

    def get_server_descriptions(self) -> dict[Union[tuple[str, int], Any], ServerDescription]:
        """Initial dict of (address, ServerDescription) for all seeds."""
        return {address: ServerDescription(address) for address in self.seeds}
