# coding: utf-8

"""
Интерфейс к pycurl, приближенно похожий на requests.
100% реализации интерфейса requests нет, потому что здесь это не нужно.
Также IDM нужны некоторые свои дефолтные параметры.
Нужен для того, чтобы избежать проблем с http://en.wikipedia.org/wiki/Server_Name_Indication ,
также определены параметры запроса по умолчанию (таймаут, сертификаты, заголовки).
"""

import logging
import operator
import urllib.request, urllib.parse, urllib.error
from io import BytesIO

from django.conf import settings
from django.utils.encoding import force_text
from django.utils.http import urlencode
import pycurl

from idm.utils import json
from idm.utils.http import RequestException
from idm.utils.tvm import get_tvm_ticket

log = logging.getLogger(__name__)


class Response(object):
    def __init__(self, status_code, content, headers, url):
        self.status_code = status_code
        self.content = content
        self.headers = force_text(headers)
        self.url = url

    def __repr__(self):
        return '<Response [%s]>' % self.status_code

    @property
    def text(self):
        return force_text(self.content)

    def json(self, **kwargs):
        try:
            return json.loads(self.text)
        except json.JSONDecodeError:
            log.warning('Cannot parse json from "%s": %s', self.url, self.content[:100], exc_info=True)
            raise


def _perform(url, method, params=None, data=None, use_client_certificate=True, tvm_id=None,
             check_server_certificate=True, timeout=settings.DEFAULT_REQUEST_TIMEOUT, headers=None):
    """
    Выполнить http(s)-запрос.

    @param url: URL
    @param method: Метод
    @param use_client_certificate: Использовать клиентский сертификат
    @param check_server_certificate: Проверять серверный сертификат
    @param timeout: Таймаут
    @param data: POST-данные (только для method=POST): dict для x-www-form-urlencoded, str для запро
    @param headers: dict с заголовками. Будет дополнено дефолтными значениями `DEFAULT_HEADERS`.
        Если значение пустое, заголовок не будет использоваться, даже если есть такой дефолтный.
    @return: Response() с данными
    @raises: ResponseException
    """

    log.debug('Attempt to perform a request by curl: url=%s, method=%s, data=%s, use_client_certificate=%s, '
              'timeout=%s',
              repr(url), repr(method), repr(data), repr(use_client_certificate), repr(timeout),
    )

    if params:
        url = url + ('?' if '?' not in url else '&') + urlencode(params)

    headers_buffer = BytesIO()
    body_buffer = BytesIO()
    curl = pycurl.Curl()
    curl.setopt(pycurl.URL, url)
    curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)
    curl.setopt(pycurl.CONNECTTIMEOUT, timeout)
    curl.setopt(pycurl.TIMEOUT, timeout)
    curl.setopt(pycurl.WRITEDATA, body_buffer)
    curl.setopt(pycurl.WRITEFUNCTION, body_buffer.write)
    curl.setopt(pycurl.HEADERFUNCTION, headers_buffer.write)

    if method == 'POST':
        curl.setopt(pycurl.POST, 1)
        if data:
            if isinstance(data, dict):
                # для обеспечения стабильности ссылки
                data = sorted(data.items(), key=operator.itemgetter(0))
                data = urllib.parse.urlencode(data)
            curl.setopt(pycurl.POSTFIELDS, data)

    # Заголовки
    headers = headers or {}
    final_headers = settings.DEFAULT_REQUEST_HEADERS.copy()
    for key, value in headers.items():
        key = key.lower()
        if value:
            final_headers[key] = value
        else:
            # Оверрайд дефолтных заголовков пустым значением
            final_headers[key] = None  # Чтобы следующая строчка не выдавала ошибку
            final_headers.pop(key)

    headers_list = sorted(['%s: %s' % (k, v) for k, v in final_headers.items()])

    # Проверка серверного сертификата
    if check_server_certificate:
        curl.setopt(pycurl.SSL_VERIFYHOST, 2)
        curl.setopt(pycurl.SSL_VERIFYPEER, 1)
        curl.setopt(pycurl.CAINFO, settings.CA_BUNDLE)
    else:
        curl.setopt(pycurl.SSL_VERIFYHOST, 0)
        curl.setopt(pycurl.SSL_VERIFYPEER, 0)

    # Клиентский сертификат
    if tvm_id:
        headers_list.append('%s: %s' % ('X-Ya-Service-Ticket', get_tvm_ticket(tvm_id)))
    elif use_client_certificate:
        cert = settings.CLIENT_CERT
        curl.setopt(pycurl.SSLCERT, cert)
        # Если у сертификата есть отдельный файл с ключом, его нужно подключить так:
        # curl.setopt(curl.SSLKEY, settings.CLIENT_PRIVATE_KEY)

    curl.setopt(pycurl.HTTPHEADER, headers_list)

    try:
        curl.perform()
    except Exception as e:
        log.warning('Failed to perform a request to "%s"', url, exc_info=True)
        raise RequestException(str(e))
    else:
        status_code = curl.getinfo(pycurl.HTTP_CODE)
        body = body_buffer.getvalue()
        headers = headers_buffer.getvalue()
        curl.close()

        log.debug('Received response with code "%s"', status_code)

        return Response(status_code, body, headers, url)


def get(url, **kwargs):
    return _perform(url, 'GET', **kwargs)


def delete(url, **kwargs):
    return _perform(url, 'DELETE', **kwargs)


def post(url, data, **kwargs):
    return _perform(url, 'POST', data=data, **kwargs)
