# -*- coding: utf-8 -*-

"""
certifi.py
~~~~~~~~~~

This module returns the installation location of cacert.pem.
"""
import os
import hashlib


__CERT_URL = "raw.githubusercontent.com"
__CERT_PATH = "certifi/python-certifi/master/certifi/cacert.pem"

_parent_dir = ""


### return values
RV_SUCCESS = 0
RV_NO_UPDATE = 1

RV_ERR_BAD_SSL = -1
RV_ERR_BAD_SSL_LIB = -2
RV_ERR_CERT_WRITE = -3
RV_ERR = -4


def where(prefix=""):
    """
    Returns the normcase'd path to the cert file

    IN:
        prefix - path prefix to use. If not passed in,
            we use the known parent dir. use set_parent_dir to adjust.
            (Default: "")

    RETURNS: cert file path (relative)
    """
    if len(prefix) < 1:
        prefix = _parent_dir

    f = os.path.dirname(__file__)

    return os.path.normcase(os.path.join(prefix, f, 'cacert.pem'))


def ssl_ctx():
    """
    Gets the SSLContext object using our certs.

    Raises SSLError if the cert chain is bad.

    RETURNS: tuple of the following format:
        [0] - RV_SUCCESS if success
              RV_ERR_BAD_SSL if cert error
              RV_ERR_BAD_SSL_LIB if no SSL lib
        [1] - SSLContext if success, None otherwise
    """
    try:
        return _ssl_ctx()
    except (ImportError, AttributeError):
        return RV_ERR_BAD_SSL_LIB, None


def _ssl_ctx():
    """
    This is so import errors can be caught.

    REUTRNS: see ssl_ctx
    """
    import ssl # must be here because may not hve ssl on start

    try:

        ctx = ssl.create_default_context()
        ctx.load_cert_chain(where())
        return RV_SUCCESS, ctx

    except ssl.SSLError:
        return RV_ERR_BAD_SSL, None


def set_parent_dir(parent_dir):
    """
    Sets the parent dir (aka prefix).
    Used with where, so use what you would use with where()

    IN:
        parent_dir - parent dir string
    """
    global _parent_dir
    _parent_dir = parent_dir


def has_cert():
    """
    Checks if we have a cert. does NOT check if cert is valid or anything.

    RETURNS: True if we have a cert, False if not
    """
    try:
        return os.access(where(), os.F_OK)
    except IOError:
        return False


def check_update(pkg_parent_path=""):
    """
    Checks for a cert update, dls it, and saves it if updated.

    ONLY CALL THIS IN RUNTIME

    IN:
        pkg_parent_path - path to the parent dir containing this package.
            This is important for writing the cert to the correct place.
            (Default: "")

    RETURNS: tuple:
        [0] - RV_SUCCESS if success
              RV_NO_UPDATE if no update
              RV_ERR if some error occured
              RV_ERR_BAD_SSL if an SSL error occured 
              RV_ERR_BAD_SSL_LIB if no SSL library
              RV_ERR_CERT_WRITE if downloaded cert could not be written to disk
        [1] - server response if [0] is RV_ERR, 
            the IOexception if [0] is RV_ERR_CERT_WRITE,
            otherwise None
    """
    try:
        return _check_update(pkg_parent_path)
    except (ImportError, AttributeError):
        # no ssl lib or ssl not loaded
        return RV_ERR_BAD_SSL_LIB, None


def _check_update(pkg_parent_path):
    """
    Internal check_update. This is import errors can be caught

    RETURNS: see check_update
    """
    # the imports are here since we may not have ssl on start
    import ssl
    import httplib

    h_conn = httplib.HTTPSConnection(__CERT_URL)

    try:
        # may fail if no SSL and site demands it
        h_conn.connect()

        h_conn.request("GET", "/" + __CERT_PATH)
        server_response = h_conn.getresponse()

        if server_response.status != 200: 
            # unexpected respponses
            return RV_ERR, server_response

        # read data
        cert_data = server_response.read()

        if has_cert():
            new_cert_hash = hashlib.sha256(cert_data)

            with open(where(pkg_parent_path), "r") as curr_cert:
                curr_cert_hash = hashlib.sha256(curr_cert.read())

                if new_cert_hash.hexdigest() == curr_cert_hash.hexdigest():
                    # current file is exact same so no need to save
                    return RV_NO_UPDATE, None

        # save the cert to our location
        with open(where(pkg_parent_path), "w") as new_cert:
            new_cert.write(cert_data)

        return RV_SUCCESS, None

    except IOError as e:
        # error writing.
        return RV_ERR_CERT_WRITE, e

    except ssl.SSLError:
        # site demanded SSL but no cert
        return RV_ERR_BAD_SSL, None

    except httplib.HTTPException:
        # unknown http exception
        return RV_ERR, server_response

    finally:
        h_conn.close()