#!/usr/bin/env python3
import pytest

import logging
import os
import pathlib
import requests

from spindle import pytest_utils
from spindle.pytest_utils import (
    response_raise_for_status,
)

from core.utils import (
    cryptography_load_pem_public_key,
    cryptography_rsa_oaep_encrypt,
    csv_file_iter_namedtuple,
    urlsafe_b64encode,
)

SCRIPT_DIR, SCRIPT_FILE = os.path.split(os.path.abspath(__file__))
SPINDLE_DIR = pathlib.Path(SCRIPT_DIR, "..", "..").resolve()

logger = pytest_utils.logger = logging.getLogger(__name__)

TEST_USERS_CSV_PATH = SPINDLE_DIR / ".test" / "test-users.csv"
TEST_USERS = {r.Email: r for r in csv_file_iter_namedtuple(TEST_USERS_CSV_PATH)}

FAKE_2FA = "locals"

BASE_URL = "http://localhost:38001/api_auth/1"

TEST_API_AUTH_USERS = [
    TEST_USERS[f"{user}@example.com.invalid"] for user in (
        "superuser-spindle-test",
        "distributor-admin-test-distributor-delta",
        "distributor-user-test-distributor-delta",
        "seller-admin-test-reseller-bar",
        "seller-user-test-reseller-bar",
        "customer-admin-test-customer-one",
        "customer-user-test-customer-one",
    )
]


def requests_get_raise(*args, **kwargs):
    r = requests.get(*args, **kwargs)
    return response_raise_for_status("requests.get", r, *args, **kwargs)


def requests_post_raise(*args, **kwargs):
    r = requests.post(*args, **kwargs)
    return response_raise_for_status("requests.post", r, *args, **kwargs)


# noinspection PyTypeChecker
TEST_API_RANDOM_LENGTHS = [
                              (None, 16),
                          ] + [
                              (l_e, "422 Client Error: Unprocessable Entity for url") for l_e in range(1, 8)
                          ] + [
                              (l_ok, l_ok) for l_ok in range(8, 257, 16)
                          ] + [
                              (l_e, "422 Client Error: Unprocessable Entity for url") for l_e in range(257, 512, 64)
                          ]


@pytest.mark.parametrize("length,exp", TEST_API_RANDOM_LENGTHS)
def test_api_random(length, exp):
    # Note: We are calling the real API here
    random_url = f"{BASE_URL}/random"
    kwargs = {}
    if length:
        kwargs["params"] = {"length": length}

    if isinstance(exp, str):
        with pytest.raises(requests.HTTPError, match=exp):
            requests_get_raise(random_url, **kwargs)
        return

    r = requests_get_raise(random_url, **kwargs)
    json = r.json()
    assert exp == json["length"], (length, json)
    assert json["length"] == len(json["text"]), json


@pytest.mark.parametrize("user", TEST_API_AUTH_USERS)
def test_api_auth(user):
    # Note: We are calling the real API here

    # And this tests the authentication end-to-end sequence

    headers = {}

    # Look in metadata to verify user status
    metadata_url = f"{BASE_URL}/metadata"
    r = requests_get_raise(metadata_url, headers=headers)
    json = r.json()
    assert not json["user_is_authenticated"], json
    assert not json["user_is_verified"], json

    # get public key and encode from GET login
    login_url = f"{BASE_URL}/login"
    r = requests_get_raise(login_url)
    json = r.json()
    rsa_public_key = cryptography_load_pem_public_key(json["public_rsa_key"])
    raw_password = user.Password
    enc_password = urlsafe_b64encode(cryptography_rsa_oaep_encrypt(rsa_public_key, raw_password)).decode("ASCII")

    # terms_checkbox set to False, rejected
    data = {
        "terms_checkbox": False,
        "trust": True,
        "email": user.Email,
        "enc_password": enc_password,
    }
    with pytest.raises(requests.HTTPError, match="406 Client Error: Not Acceptable for url"):
        requests_post_raise(login_url, json=data, headers=headers)

    # terms_checkbox set to True, accepted, `Authorization: Bearer token` supplied
    data["terms_checkbox"] = True
    r = requests_post_raise(login_url, json=data, headers=headers)
    json = r.json()
    token = json["token"]
    assert token, (data, json)

    headers["Authorization"] = f"Bearer {token}"

    # Look in metadata to verify user status with the token
    r = requests_get_raise(metadata_url, headers=headers)
    json = r.json()
    assert json["user_is_authenticated"], json
    assert not json["user_is_verified"], json

    # Use the token to verify
    verify_url = f"{BASE_URL}/verify"
    data = {"email": user.Email}
    with pytest.raises(requests.HTTPError, match="403 Client Error: Forbidden for url"):
        # Email would be sent to user by this
        requests_post_raise(verify_url, json=data, headers=headers)

    # Post again with the code (using fake code) to get JWT
    data["code"] = FAKE_2FA
    r = requests_post_raise(verify_url, json=data, headers=headers)
    json = r.json()
    x_verified_jwt = json["x_verified_jwt"]
    assert x_verified_jwt

    # Look in metadata to verify user status with the token and verified jwt
    headers["X-Verified-JWT"] = x_verified_jwt
    r = requests_get_raise(metadata_url, headers=headers)
    json = r.json()
    assert json["user_is_authenticated"], json
    assert json["user_is_verified"], json

    # Log in again but supply X-Verified-JWT this time
    del headers["Authorization"]
    data = {
        "terms_checkbox": True,
        "trust": True,
        "email": user.Email,
        "enc_password": enc_password,
    }
    r = requests_post_raise(login_url, json=data, headers=headers)
    json = r.json()
    token = json["token"]
    assert token, (data, json)
    assert json["user_is_verified"], json

    # Look in metadata to verify user status with the old token and verified jwt, should fail
    r = requests_get_raise(metadata_url, headers=headers)
    json = r.json()
    assert not json["user_is_authenticated"], json
    assert not json["user_is_verified"], json

    # Look in metadata to verify user status with the new token and verified jwt
    headers["Authorization"] = f"Bearer {token}"
    r = requests_get_raise(metadata_url, headers=headers)
    json = r.json()
    assert json["user_is_authenticated"], json
    assert json["user_is_verified"], json
