# The MIT License (MIT)
# Copyright (c) 2014 Microsoft Corporation

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Session Consistency Tracking in the Azure Cosmos database service.
"""

import sys
import traceback
import threading

from . import _base
from . import http_constants
from ._vector_session_token import VectorSessionToken
from .exceptions import CosmosHttpResponseError


class SessionContainer(object):
    def __init__(self):
        self.collection_name_to_rid = {}
        self.rid_to_session_token = {}
        self.session_lock = threading.RLock()

    def get_session_token(self, resource_path):
        """Get Session Token for collection_link.

        :param str resource_path: Self link / path to the resource
        :return: Session Token dictionary for the collection_id
        :rtype: dict
        """

        with self.session_lock:
            is_name_based = _base.IsNameBased(resource_path)
            collection_rid = ""
            session_token = ""

            try:
                if is_name_based:
                    # get the collection name
                    collection_name = _base.GetItemContainerLink(resource_path)
                    collection_rid = self.collection_name_to_rid[collection_name]
                else:
                    collection_rid = _base.GetItemContainerLink(resource_path)

                if collection_rid in self.rid_to_session_token:
                    token_dict = self.rid_to_session_token[collection_rid]
                    session_token_list = []
                    for key in token_dict.keys():
                        session_token_list.append("{0}:{1}".format(key, token_dict[key].convert_to_string()))
                    session_token = ",".join(session_token_list)
                    return session_token

                # return empty token if not found
                return ""
            except Exception:  # pylint: disable=broad-except
                return ""

    def set_session_token(self, response_result, response_headers):
        """Session token must only be updated from response of requests that
        successfully mutate resource on the server side (write, replace, delete etc).

        :param dict response_result:
        :param dict response_headers:
        :return: None
        """

        # there are two pieces of information that we need to update session token-
        # self link which has the rid representation of the resource, and
        # x-ms-alt-content-path which is the string representation of the resource

        with self.session_lock:
            collection_rid = ""
            collection_name = ""

            try:
                self_link = response_result["_self"]

                # extract alternate content path from the response_headers
                # (only document level resource updates will have this),
                # and if not present, then we can assume that we don't have to update
                # session token for this request
                alt_content_path = ""
                alt_content_path_key = http_constants.HttpHeaders.AlternateContentPath
                response_result_id_key = u"id"
                response_result_id = None
                if alt_content_path_key in response_headers:
                    alt_content_path = response_headers[http_constants.HttpHeaders.AlternateContentPath]
                    response_result_id = response_result[response_result_id_key]
                else:
                    return
                collection_rid, collection_name = _base.GetItemContainerInfo(
                    self_link, alt_content_path, response_result_id
                )

            except ValueError:
                return
            except Exception:  # pylint: disable=broad-except
                exc_type, exc_value, exc_traceback = sys.exc_info()
                traceback.print_exception(exc_type, exc_value, exc_traceback, limit=2, file=sys.stdout)
                return

            if collection_name in self.collection_name_to_rid:
                # check if the rid for the collection name has changed
                # this means that potentially, the collection was deleted
                # and recreated
                existing_rid = self.collection_name_to_rid[collection_name]
                if collection_rid != existing_rid:
                    # flush the session tokens for the old rid, and
                    # update the new rid into the collection name to rid map.
                    self.rid_to_session_token[existing_rid] = {}