import re

from urllib.parse import unquote, quote
from tvmauth.utils import remove_ticket_signature

COOKIE = 'COOKIE'

AUTHORIZATION_HEADERS = ('AUTHORIZATION', 'YANDEX-AUTHORIZATION', 'X-FORWARDED-USER')
TVM_2_USER_TICKET_HEADER = 'X-YA-USER-TICKET'
HASH_THROUGH_HEADERS = {'TICKET', 'X-YA-SERVICE-TICKET'}

HTTP_SESSION_COOKIE_NAME = 'Session_id'
HTTPS_SESSION_COOKIE_NAME = 'sessionid2'

AUTHORIZATION_PATTERN = re.compile('(?P<name>oauth|xiva|token) (?P<credential>[^; $]*)', re.IGNORECASE)
OAUTH_QS_PATTERN = re.compile('([&?]oauth_token=[^&$]*)')
SESSION_ID_QS_PATTERN = re.compile('([&?]sessionid=[^&$]*)')
SSL_SESSION_ID_QS_PATTERN = re.compile('([&?]sslsessionid=[^&$]*)')


class CredentialSanitizer:
    """
    Класс, предназначенный для хеширования критичных для пользователей данных.

    Подобные данные, например, OAuth токены и куки, необходимо хэшировать, прежде чем они попадут в лог.
    """
    templates = {
        'oauth_token': (OAUTH_QS_PATTERN, 'oauth_token=%s'),
        'sessionid': (SESSION_ID_QS_PATTERN, 'sessionid=%s'),
        'sslsessionid': (SSL_SESSION_ID_QS_PATTERN, 'sslsessionid=%s'),
    }

    @classmethod
    def get_headers_list_with_sanitized_credentials(cls, raw_headers):
        if raw_headers is None:
            return None
        """
        Хэширование креденшилов в хедерах

        :return [(<имя_заголовка>, <обработанное_значение>), ...]
        """
        hashed_headers_list = []
        for k, v in raw_headers.items():
            new_v = v
            key_upper = k.upper()
            if key_upper in AUTHORIZATION_HEADERS:
                new_v = cls._hash_authorization_header_values(v)
            elif key_upper == COOKIE:
                new_v = cls._truncate_cookie_header_values(v)
            elif key_upper == TVM_2_USER_TICKET_HEADER:
                new_v = remove_ticket_signature(v)
            elif key_upper in HASH_THROUGH_HEADERS:
                new_v = cls.hash_credential(v)
            hashed_headers_list.append((k, new_v))
        return hashed_headers_list

    @classmethod
    def sanitize_qs_parameter_in_url(self, qs_name, url):
        if qs_name not in self.templates:
            raise ValueError('Unsupported qs parameter to hash')
        pattern, replace_str = self.templates[qs_name]

        found_substrings = pattern.findall(url)
        for i in found_substrings:
            credential_value_to_sanitize = i.split('=', 1)[1]
            prefix = i[0]  # необходимо искать с префиксом из-за коллизий в именах sessionid и sslsessionid
            if qs_name in {'sessionid', 'sslsessionid'}:
                sanitized_value = CredentialSanitizer._sanitize_session_id_value(unquote(credential_value_to_sanitize))
            else:
                sanitized_value = CredentialSanitizer.hash_credential(unquote(credential_value_to_sanitize))
            url = url.replace(i, (prefix + replace_str) % quote(sanitized_value))
        return url

    @classmethod
    def sanitize_all_parameter_in_url(self, url):
        new_url = url
        for qs_name in self.templates.keys():
            new_url = self.sanitize_qs_parameter_in_url(qs_name, new_url)

        return new_url

    @staticmethod
    def hash_credential(value_to_hash):
        """Метод, предназначенный для хэширование всех попадающих в лог значений, требующих хэширования"""
        return ""  # TODO: добавить нормальное хеширование через секрет

    @classmethod
    def _hash_authorization_header_values(cls, authorization):
        m = AUTHORIZATION_PATTERN.search(authorization)
        if not m:
            return authorization
        replace_values = {
            'name': m.groupdict()['name'],
            'credential': cls.hash_credential(m.groupdict()['credential']),
        }
        return AUTHORIZATION_PATTERN.sub('%(name)s %(credential)s' % replace_values, authorization)

    @classmethod
    def _truncate_cookie_header_values(cls, cookies):
        parsed_cookie = cls.parse_cookie(cookies)
        for cookie_name in (HTTP_SESSION_COOKIE_NAME, HTTPS_SESSION_COOKIE_NAME):
            if cookie_name not in parsed_cookie:
                continue
            parsed_cookie[cookie_name] = cls._sanitize_session_id_value(parsed_cookie[cookie_name])

        return '; '.join('%s=%s' % (k, v) for k, v in parsed_cookie.items())

    @staticmethod
    def _sanitize_session_id_value(session_id):
        return session_id.rsplit('.', 1)[0]

    @staticmethod
    def parse_cookie(cookie):
        parts = cookie.split(';')
        result = {}
        for part in parts:
            key_value = part.split('=', 1)
            if len(key_value) == 2:
                result[key_value[0].strip()] = key_value[1]
        return result
