#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
:mod:`bankid.client` -- BankID Client
=====================================

Created on 2014-09-09 by hbldh

"""

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import

import os
import warnings
import six
import base64

import requests
from zeep import Client
from zeep.transports import Transport
from zeep.exceptions import Error
from pkg_resources import resource_filename

from bankid.exceptions import get_error_class, BankIDWarning

# Handling Python 2.7 verification of certificates with urllib3.
# See README.rst for details.
try:
    import requests.packages.urllib3.contrib.pyopenssl

    requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3()
except ImportError:
    if bool(os.environ.get("PYBANKID_DISABLE_WARNINGS", False)):
        requests.packages.urllib3.disable_warnings()


class BankIDClient(object):
    """The client to use for communicating with BankID servers.

    :param certificates: Tuple of string paths to the certificate to use and
        the key to sign with.
    :type certificates: tuple
    :param test_server: Use the test server for authenticating and signing.
    :type test_server: bool
    :param request_timeout: Timeout for BankID requests.
    :type request_timeout: int

    """

    def __init__(self, certificates, test_server=False, request_timeout=None, **kwargs):
        self.certs = certificates

        warnings.warn(
            "BankIDClient using the SOAP API will "
            "be deprecated in February 2020. Use "
            "bankid.BankIDJSONClient instead.",
            PendingDeprecationWarning,
        )

        if test_server:
            self.api_url = "https://appapi2.test.bankid.com/rp/v4"
            self.wsdl_url = "https://appapi2.test.bankid.com/rp/v4?wsdl"
            self.verify_cert = resource_filename(
                "bankid.certs", "appapi2.test.bankid.com.pem"
            )
        else:

            self.api_url = "https://appapi2.bankid.com/rp/v4"
            self.wsdl_url = "https://appapi2.bankid.com/rp/v4?wsdl"
            self.verify_cert = resource_filename(
                "bankid.certs", "appapi2.bankid.com.pem"
            )

        headers = {"Content-Type": "text/xml;charset=UTF-8"}

        session = requests.Session()
        session.verify = self.verify_cert
        session.cert = self.certs
        session.headers = headers
        timeout_kwarg = {} if request_timeout is None else {"timeout": request_timeout}
        transport = Transport(session=session, **timeout_kwarg)
        self.client = Client(self.wsdl_url, transport=transport)

    def authenticate(self, personal_number, **kwargs):
        """Request an authentication order. The :py:meth:`collect` method
        is used to query the status of the order.

        :param personal_number: The Swedish personal number
            in format YYYYMMDDXXXX.
        :type personal_number: str
        :return: The OrderResponse parsed to a dictionary.
        :rtype: dict
        :raises BankIDError: raises a subclass of this error
                             when error has been returned from server.

        """
        if "requirementAlternatives" in kwargs:
            warnings.warn(
                "Requirement Alternatives " "option is not tested.", BankIDWarning
            )

        try:
            out = self.client.service.Authenticate(
                personalNumber=personal_number, **kwargs
            )
        except Error as e:
            raise get_error_class(e, "Could not complete Authenticate order.")

        return self._dictify(out)

    def sign(self, user_visible_data, personal_number=None, **kwargs):
        """Request an signing order. The :py:meth:`collect` method
        is used to query the status of the order.

        :param user_visible_data: The information that the end user is
            requested to sign.
        :type user_visible_data: str
        :param personal_number: The Swedish personal number in
            format YYYYMMDDXXXX.
        :type personal_number: str
        :return: The OrderResponse parsed to a dictionary.
        :rtype: dict
        :raises BankIDError: raises a subclass of this error
                     when error has been returned from server.

        """
        if "requirementAlternatives" in kwargs:
            warnings.warn(
                "Requirement Alternatives option is not tested.", BankIDWarning
            )

        if isinstance(user_visible_data, six.text_type):
            data = base64.b64encode(user_visible_data.encode("utf-8")).decode("ascii")
        else:
            data = base64.b64encode(user_visible_data).decode("ascii")

        try:
            out = self.client.service.Sign(
                userVisibleData=data, personalNumber=personal_number, **kwargs
            )
        except Error as e:
            raise get_error_class(e, "Could not complete Sign order.")

        return self._dictify(out)

    def collect(self, order_ref):
        """Collect the progress status of the order with the specified
        order reference.

        :param order_ref: The UUID string specifying which order to
            collect status from.
        :type order_ref: str
        :return: The CollectResponse parsed to a dictionary.
        :rtype: dict
        :raises BankIDError: raises a subclass of this error
                             when error has been returned from server.

        """
        try:
            out = self.client.service.Collect(order_ref)
        except Error as e:
            raise get_error_class(e, "Could not complete Collect call.")

        return self._dictify(out)

    def file_sign(self, **kwargs):
        """Request a file signing order. The :py:meth:`collect` method
        is used to query the status of the order.

        .. note::

            Not implemented due to that the method is deprecated.

        :raises NotImplementedError: This method is not implemented.

        """
        raise NotImplementedError(
            "FileSign is deprecated and therefore not implemented."
        )

    def _dictify(self, doc):
        """Transforms the replies to a regular Python dict with
        strings and datetimes.

        Tested with BankID version 2.5 return data.

        :param doc: The response as interpreted by :py:mod:`zeep`.
        :returns: The response parsed to a dict.
        :rtype: dict

        """
        return {
            k: (self._dictify(doc[k]) if hasattr(doc[k], "_xsd_type") else doc[k])
            for k in doc
        }
