"""
Test de l'api justificatif

Ecrit par HARTMANN Matthias

"""

from random import randint

import requests
from tests.api.setup_test_api import (
    API_URL,
    CHECK_CERTIFICATE,
    GET,
    POST_JSON,
    APIError,
    api_headers,
    api_admin_headers,
)

ETUDID = 1
FAUX = 42069


JUSTIFICATIFS_FIELDS = {
    "justif_id": int,
    "etudid": int,
    "code_nip": str,
    "date_debut": str,
    "date_fin": str,
    "etat": str,
    "raison": str,
    "entry_date": str,
    "fichier": str,
    "user_id": int | None,
    "user_name": str | None,
    "user_nom_complet": str | None,
    "external_data": dict,
}

CREATE_FIELD = {"justif_id": int, "couverture": list}
BATCH_FIELD = {"errors": list, "success": list}

TO_REMOVE = []


def check_fields(data, fields: dict = None):
    """
    Cette fonction permet de vérifier que le dictionnaire data
    contient les bonnes clés et les bons types de valeurs.

    Args:
        data (dict): un dictionnaire (json de retour de l'api)
        fields (dict, optional): Un dictionnaire représentant les clés et les types d'une réponse.
    """
    if fields is None:
        fields = JUSTIFICATIFS_FIELDS
    assert set(data.keys()) == set(fields.keys())
    for key in data:
        if key in ("raison", "fichier", "user_id", "external_data"):
            assert (
                isinstance(data[key], fields[key]) or data[key] is None
            ), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"
        else:
            assert isinstance(
                data[key], fields[key]
            ), f"error [{key}:{type(data[key])}, {data[key]}, {fields[key]}]"


def check_failure_get(path, headers, err=None):
    """
    Cette fonction vérifiée que la requête GET renvoie bien un 404

    Args:
        path (str): la route de l'api
        headers (dict): le token d'auth de l'api
        err (str, optional): L'erreur qui est sensée être fournie par l'api.

    Raises:
        APIError: Une erreur car la requête a fonctionné (mauvais comportement)
    """
    try:
        GET(path=path, headers=headers)
        # ^ Renvoi un 404
    except APIError as api_err:
        if err is not None:
            assert api_err.payload["message"] == err
    else:
        raise APIError("Le GET n'aurait pas du fonctionner")


def check_failure_post(path, headers, data, err=None):
    """
    Cette fonction vérifiée que la requête POST renvoie bien un 404

    Args:
        path (str): la route de l'api
        headers (dict): le token d'auth
        data (dict): un dictionnaire (json) à envoyer
        err (str, optional): L'erreur qui est sensée être fournie par l'api.

    Raises:
        APIError: Une erreur car la requête a fonctionné (mauvais comportement)
    """
    try:
        data = POST_JSON(path=path, headers=headers, data=data)
        # ^ Renvoi un 404
    except APIError as api_err:
        if err is not None:
            assert api_err.payload["message"] == err
    else:
        raise APIError("Le POST n'aurait pas du fonctionner")


def create_data(etat: str, day: str, raison: str = None):
    """
    Permet de créer un dictionnaire assiduité

    Args:
        etat (str): l'état du justificatif (VALIDE,NON_VALIDE,MODIFIE, ATTENTE)
        day (str): Le jour du justificatif
        raison (str, optional): Une description du justificatif (eg: motif retard )

    Returns:
        dict: la représentation d'une assiduité
    """
    data = {
        "date_debut": f"2022-01-{day}T08:00",
        "date_fin": f"2022-01-{day}T10:00",
        "etat": etat,
    }
    if raison is not None:
        data["desc"] = raison

    return data


def test_route_justificatif(api_headers):
    """test de la route /justificatif/<justif_id:int>"""

    # Bon fonctionnement == id connu
    data = GET(path="/justificatif/1", headers=api_headers)
    check_fields(data)

    # Mauvais Fonctionnement == id inconnu

    check_failure_get(
        f"/justificatif/{FAUX}",
        api_headers,
    )


def test_route_justificatifs(api_headers):
    """test de la route /justificatifs/<etudid:int>"""
    # Bon fonctionnement

    data = GET(path=f"/justificatifs/{ETUDID}", headers=api_headers)
    assert isinstance(data, list)
    for just in data:
        check_fields(just, JUSTIFICATIFS_FIELDS)

    data = GET(path=f"/justificatifs/{ETUDID}/query?", headers=api_headers)
    assert isinstance(data, list)
    for just in data:
        check_fields(just, JUSTIFICATIFS_FIELDS)

    # Mauvais fonctionnement
    check_failure_get(f"/justificatifs/{FAUX}", api_headers)
    check_failure_get(f"/justificatifs/{FAUX}/query?", api_headers)


def test_route_create(api_admin_headers):
    """test de la route /justificatif/<justif_id:int>/create"""
    # -== Unique ==-

    # Bon fonctionnement
    data = create_data("valide", "01")

    res = POST_JSON(f"/justificatif/{ETUDID}/create", [data], api_admin_headers)
    check_fields(res, BATCH_FIELD)
    assert len(res["success"]) == 1

    TO_REMOVE.append(res["success"][0]["message"]["justif_id"])

    data2 = create_data("modifie", "02", "raison")
    res = POST_JSON(f"/justificatif/{ETUDID}/create", [data2], api_admin_headers)
    check_fields(res, BATCH_FIELD)
    assert len(res["success"]) == 1

    TO_REMOVE.append(res["success"][0]["message"]["justif_id"])

    # Mauvais fonctionnement
    check_failure_post(f"/justificatif/{FAUX}/create", api_admin_headers, [data])

    res = POST_JSON(
        f"/justificatif/{ETUDID}/create",
        [create_data("absent", "03")],
        api_admin_headers,
    )
    check_fields(res, BATCH_FIELD)
    assert len(res["errors"]) == 1
    assert res["errors"][0]["message"] == "param 'etat': invalide"

    # -== Multiple ==-

    # Bon Fonctionnement

    etats = ["valide", "modifie", "non_valide", "attente"]
    data = [
        create_data(etats[d % 4], 10 + d, "raison" if d % 2 else None)
        for d in range(randint(3, 5))
    ]

    res = POST_JSON(f"/justificatif/{ETUDID}/create", data, api_admin_headers)
    check_fields(res, BATCH_FIELD)
    for dat in res["success"]:
        check_fields(dat["message"], CREATE_FIELD)
        TO_REMOVE.append(dat["message"]["justif_id"])

    # Mauvais Fonctionnement

    data2 = [
        create_data(None, "25"),
        create_data("blabla", 26),
        create_data("valide", 32),
    ]

    res = POST_JSON(f"/justificatif/{ETUDID}/create", data2, api_admin_headers)
    check_fields(res, BATCH_FIELD)
    assert len(res["errors"]) == 3

    assert res["errors"][0]["message"] == "param 'etat': manquant"
    assert res["errors"][1]["message"] == "param 'etat': invalide"
    assert (
        res["errors"][2]["message"]
        == "param 'date_debut': format invalide, param 'date_fin': format invalide"
    )


def test_route_edit(api_admin_headers):
    """test de la route /justificatif/<justif_id:int>/edit"""
    # Bon fonctionnement

    data = {"etat": "modifie", "raison": "test"}
    res = POST_JSON(f"/justificatif/{TO_REMOVE[0]}/edit", data, api_admin_headers)
    assert isinstance(res, dict) and "couverture" in res.keys()

    data["raison"] = None
    res = POST_JSON(f"/justificatif/{TO_REMOVE[1]}/edit", data, api_admin_headers)
    assert isinstance(res, dict) and "couverture" in res.keys()

    # Mauvais fonctionnement

    check_failure_post(f"/justificatif/{FAUX}/edit", api_admin_headers, data)
    data["etat"] = "blabla"
    check_failure_post(
        f"/justificatif/{TO_REMOVE[2]}/edit",
        api_admin_headers,
        data,
        err="param 'etat': invalide",
    )


def test_route_delete(api_admin_headers):
    """test de la route /justificatif/delete"""
    # -== Unique ==-

    # Bon fonctionnement
    data = TO_REMOVE[0]

    res = POST_JSON("/justificatif/delete", [data], api_admin_headers)
    check_fields(res, BATCH_FIELD)
    for dat in res["success"]:
        assert dat["message"] == "OK"

    # Mauvais fonctionnement
    res = POST_JSON("/justificatif/delete", [data], api_admin_headers)
    check_fields(res, BATCH_FIELD)
    assert len(res["errors"]) == 1

    # -== Multiple ==-

    # Bon Fonctionnement

    data = TO_REMOVE[1:]

    res = POST_JSON("/justificatif/delete", data, api_admin_headers)
    check_fields(res, BATCH_FIELD)
    for dat in res["success"]:
        assert dat["message"] == "OK"

    # Mauvais Fonctionnement

    data2 = [
        FAUX,
        FAUX + 1,
        FAUX + 2,
    ]

    res = POST_JSON("/justificatif/delete", data2, api_admin_headers)
    check_fields(res, BATCH_FIELD)
    assert len(res["errors"]) == 3

    assert all(i["message"] == "Justificatif non existant" for i in res["errors"])


# Gestion de l'archivage


def _send_file(justif_id: int, filename: str, headers):
    """
    Envoi un fichier vers la route d'importation
    """
    with open(filename, "rb") as file:
        url: str = API_URL + f"/justificatif/{justif_id}/import"
        req = requests.post(
            url,
            files={filename: file},
            headers=headers,
            verify=CHECK_CERTIFICATE,
            timeout=30,
        )

        if req.status_code != 200:
            raise APIError(f"erreur status={req.status_code} !", req.json())

        return req.json()


def _check_failure_send(
    justif_id: int,
    headers,
    filename: str = "tests/api/test_api_justificatif.txt",
    err: str = None,
):
    """
    Vérifie si l'envoie d'un fichier renvoie bien un 404

    Args:
        justif_id (int): l'id du justificatif
        headers (dict): token d'auth de l'api
        filename (str, optional): le chemin vers le fichier.
            Defaults to "tests/api/test_api_justificatif.txt".
        err (str, optional): l'erreur attendue.

    Raises:
        APIError: Si l'envoie fonction (mauvais comportement)
    """
    try:
        _send_file(justif_id, filename, headers)
        # ^ Renvoi un 404
    except APIError as api_err:
        if err is not None:
            assert api_err.payload["message"] == err
    else:
        raise APIError("Le POST n'aurait pas du fonctionner")


def test_import_justificatif(api_admin_headers):
    """test de la route /justificatif/<justif_id:int>/import"""

    # Bon fonctionnement

    filename: str = "tests/api/test_api_justificatif.txt"

    resp: dict = _send_file(1, filename, api_admin_headers)
    assert "filename" in resp
    assert resp["filename"] == "test_api_justificatif.txt"

    filename: str = "tests/api/test_api_justificatif2.txt"
    resp: dict = _send_file(1, filename, api_admin_headers)
    assert "filename" in resp
    assert resp["filename"] == "test_api_justificatif2.txt"

    # Mauvais fonctionnement

    _check_failure_send(FAUX, api_admin_headers)


def test_list_justificatifs(api_admin_headers):
    """test de la route /justificatif/<justif_id:int>/list"""

    # Bon fonctionnement

    res: list = GET("/justificatif/1/list", api_admin_headers)

    assert isinstance(res, dict)
    assert len(res["filenames"]) == 2
    assert res["total"] == 2

    res: list = GET("/justificatif/2/list", api_admin_headers)

    assert isinstance(res, dict)
    assert len(res["filenames"]) == 0
    assert res["total"] == 0

    # Mauvais fonctionnement

    check_failure_get(f"/justificatif/{FAUX}/list", api_admin_headers)


def _post_export(justif_id: int, fname: str, api_headers):
    """
    Envoie une requête poste sans data et la retourne

    Args:
        id (int): justif_id
        fname (str): nom du fichier (coté serv)
        api_headers (dict): token auth de l'api

    Returns:
        request: la réponse de l'api
    """
    url: str = API_URL + f"/justificatif/{justif_id}/export/{fname}"
    res = requests.post(url, headers=api_headers)
    return res


def test_export(api_admin_headers):
    """test de la route /justificatif/<justif_id:int>/export/<filename:str>"""

    # Bon fonctionnement

    assert (
        _post_export(1, "test_api_justificatif.txt", api_admin_headers).status_code
        == 200
    )

    # Mauvais fonctionnement
    assert (
        _post_export(FAUX, "test_api_justificatif.txt", api_admin_headers).status_code
        == 404
    )
    assert _post_export(1, "blabla.txt", api_admin_headers).status_code == 404
    assert _post_export(2, "blabla.txt", api_admin_headers).status_code == 404


def test_remove_justificatif(api_admin_headers):
    """test de la route /justificatif/<justif_id:int>/remove"""

    # Bon fonctionnement

    filename: str = "tests/api/test_api_justificatif.txt"
    _send_file(2, filename, api_admin_headers)
    filename: str = "tests/api/test_api_justificatif2.txt"
    _send_file(2, filename, api_admin_headers)

    res: dict = POST_JSON(
        "/justificatif/1/remove", {"remove": "all"}, api_admin_headers
    )
    assert res == {"response": "removed"}
    l = GET("/justificatif/1/list", api_admin_headers)
    assert isinstance(l, dict)
    assert l["total"] == 0

    res: dict = POST_JSON(
        "/justificatif/2/remove",
        {"remove": "list", "filenames": ["test_api_justificatif2.txt"]},
        api_admin_headers,
    )
    assert res == {"response": "removed"}
    l = GET("/justificatif/2/list", api_admin_headers)
    assert isinstance(l, dict)
    assert l["total"] == 1

    res: dict = POST_JSON(
        "/justificatif/2/remove",
        {"remove": "list", "filenames": ["test_api_justificatif.txt"]},
        api_admin_headers,
    )
    assert res == {"response": "removed"}
    l = GET("/justificatif/2/list", api_admin_headers)
    assert isinstance(l, dict)
    assert l["total"] == 0

    # Mauvais fonctionnement

    check_failure_post("/justificatif/2/remove", api_admin_headers, {})
    check_failure_post(
        f"/justificatif/{FAUX}/remove", api_admin_headers, {"remove": "all"}
    )
    check_failure_post("/justificatif/1/remove", api_admin_headers, {"remove": "all"})


def test_justifies(api_admin_headers):
    """test la route /justificatif/<justif_id:int>/justifies"""

    # Bon fonctionnement

    res: list = GET("/justificatif/1/justifies", api_admin_headers)
    assert isinstance(res, list)

    # Mauvais fonctionnement

    check_failure_get(f"/justificatif/{FAUX}/justifies", api_admin_headers)
