# pylint: disable=unused-import
"""Démonstration d'authentification HTTP {Basic,Digest} via gestion explicite des requêtes"""

import base64
import hashlib
import logging
import secrets
from operator import itemgetter, attrgetter
from pprint import pprint, pformat
from urllib.parse import urlparse


# logging.basicConfig(level=logging.DEBUG)


# changement de lib
# import requests
# from requests.auth import HTTPDigestAuth as RequestsHTTPDigestAuth
import httpx


ENCODING = "utf-8"
USERNAME = "romu"
PASSWORD = "pwd"
QOP = "auth"
basic_uri = f"http://pie.dev/basic-auth/{USERNAME}/{PASSWORD}"
digest_uri = f"http://pie.dev/digest-auth/{QOP}/{USERNAME}/{PASSWORD}"


def hash_many_hex(algorithm, *args):
    """hash function as defined in RFC 7616"""
    hasher = httpx.DigestAuth._ALGORITHM_TO_HASH_FUNCTION[algorithm]
    s = ":".join(args)
    return hasher(s.encode(ENCODING)).hexdigest()


def assert_httpbin_response(response, username=USERNAME):
    """assert correctness of HTTP response to authentication"""
    response.raise_for_status()
    content = response.json()
    assert content["authenticated"]
    assert content["user"] == username
    return response


def log_request(request):
    """Hook to log requests before sending. SECURE FIELDS are blacked
    e.g., event_hooks={"request": [log_request]}
    """
    print(f"Request[hook]{request.method} {request.url}\n{pformat(request.headers)}")


def auth_basic_auto(uri, username, password):
    """Authenticate to URI using HTTP Basic fully via httpx"""
    with httpx.Client() as client:
        response = client.get(uri, auth=(username, password))
        assert_httpbin_response(response, username)
        return response.headers.multi_items()


def auth_basic_manual(uri, username, password):
    """Authenticate to URI using HTTP Basic manually"""
    string = ":".join([username, password])
    challenge = base64.b64encode(string.encode(ENCODING)).decode(ENCODING)
    with httpx.Client() as client:
        response = client.get(uri, headers={"Authorization": f"Basic {challenge}"})
        assert_httpbin_response(response, username)
        return response.headers.multi_items()


def auth_digest_auto(uri, username, password):
    """Authenticate to URI using HTTP Digest fully via httpx"""
    with httpx.Client() as client:
        auth = httpx.DigestAuth(username, password)
        response = client.get(uri, auth=auth)
        assert_httpbin_response(response, username)
        return response.headers.multi_items()


def build_digest(
    algorithm,
    username,
    realm,
    password,
    method,
    uri_path,
    nonce,
    qop,
    nc=1,
    cnonce=None,
):
    """Build digest according to RFC 7616

    HA1=H(user:realm:password)`, `HA2=H(method:uri_path)`, `H=(HA1, nonce, nc, cnonce, qop, HA2)
    """

    ha1 = hash_many_hex(algorithm, username, realm, password)
    ha2 = hash_many_hex(algorithm, method, uri_path)
    # cnonce = "f10f5ae83f8df92f"
    if cnonce is None:
        cnonce = secrets.token_hex(16)
    response = hash_many_hex(algorithm, ha1, nonce, f"{nc:08x}", cnonce, qop, ha2)
    return response, cnonce


def auth_digest_manual(uri, username, password):
    """Authenticate to URI using HTTP Digest manually"""
    with httpx.Client() as client:
        first_response = client.get(uri)
        authenticate_headers = first_response.headers["www-authenticate"].removeprefix(
            "Digest"
        )
        authenticate_dict = {
            (z := kv.strip().split("="))[0]: z[1].removeprefix('"').removesuffix('"')
            for kv in authenticate_headers.split(",")
        }

        # print(authenticate_dict)
        realm, nonce, qop, opaque, algorithm = itemgetter(
            "realm", "nonce", "qop", "opaque", "algorithm"
        )(authenticate_dict)

        uri_path = urlparse(uri).path
        digest, cnonce = build_digest(
            algorithm, username, realm, password, "GET", uri_path, nonce, qop, nc=1
        )

        challenge = f'username="{username}", realm="{realm}", nonce="{nonce}", uri="{uri_path}", response="{digest}", algorithm="{algorithm}", qop="{qop}", nc="{1:08x}", cnonce="{cnonce}", opaque="{opaque}"'
        # print(challenge)

        second_response = client.get(
            uri, headers={"Authorization": f"Digest {challenge}"}
        )
        assert_httpbin_response(second_response, username)
        return second_response.headers.multi_items()


# print(token)


if __name__ == "__main__":
    # print(f"HTTP Basic authentication to {basic_uri} [auto]")
    # pprint(auth_basic_auto(basic_uri, USERNAME, PASSWORD))
    # print(f"HTTP Basic authentication to {basic_uri} [manual]")
    # pprint(auth_basic_manual(basic_uri, USERNAME, PASSWORD))

    # print(f"HTTP Digest authentication to {digest_uri} [auto]")
    # pprint(auth_digest_auto(digest_uri, USERNAME, PASSWORD))
    print(f"HTTP Digest authentication to {digest_uri} [manual]")
    print(auth_digest_manual(digest_uri, USERNAME, PASSWORD))

    # BUG sur jigswa : ok via browser, httpie/requests mais KO via httpx
    # <https://jigsaw.w3.org/HTTP/Digest/>
