#!/usr/bin/env python3
# -*- mode: python -*-
# -*- coding: utf-8 -*-

"""Construction des fichiers exemples pour la documentation.

    Usage:
        python tests/api/make_samples.py [entry_names]
        python tests/api/make_samples.py -i <filepath> [entrynames]

Si entry_names est spécifié, la génération est restreinte aux exemples cités.
Exemple:
    python make_samples departements departement-formsemestres

Doit être exécutée immédiatement apres une initialisation de la base pour test API! 
(car dépendant des identifiants générés lors de la création des objets)

Modifer le /opt/scodoc/.env pour pointer sur la base test
    SCODOC_DATABASE_URI="postgresql:///SCODOC_TEST_API"

puis re-créer cette base     
    tools/create_database.sh --drop SCODOC_TEST_API
    flask db upgrade
    flask sco-db-init --erase
    flask init-test-database

et lancer le serveur test:
    flask run --debug
```

Cet utilitaire prend en argument le fichier de nom `samples.csv` contenant la description 
des exemples (séparés par une tabulation (\t), une ligne par exemple)
* Le nom de l'exemple donne le nom du fichier généré (nom_exemple => nom_exemple.json.md). 
Plusieurs lignes peuvent partager le même nom. dans ce cas le fichier contiendra 
chacun des exemples
* l'url utilisée
* la permission nécessaire (par défaut ScoView)
* la méthode GET,POST à utiliser (si commence par #, la ligne est ignorée)
* les arguments éventuel (en cas de POST): une chaîne de caractère selon json

Implémentation:
Le code complète une structure de données (Samples) qui est un dictionnaire de set 
(indicé par le nom des exemples).
Chacun des éléments du set est un exemple (Sample)
Quand la structure est complète, on génére tous les fichiers textes
- nom de l'exemple
- un ou plusieurs exemples avec pour chacun
    - l'url utilisée
    - les arguments éventuels
    - le résultat
Le tout mis en forme au format markdown et rangé dans le répertoire DATA_DIR (/tmp/samples) 
qui est créé ou écrasé si déjà existant.
"""
import os
import shutil
import sys
import re
from collections import defaultdict
from pprint import pprint as pp

import urllib3
import json

import pandas as pd

from setup_test_api import (
    API_PASSWORD,
    API_URL,
    API_USER,
    APIError,
    CHECK_CERTIFICATE,
    get_auth_headers,
    GET,
    POST_JSON,
    SCODOC_URL,
)

DATA_DIR = "/tmp/samples/"
SAMPLES_FILENAME = "tests/ressources/samples/samples.csv"


class SampleException(Exception):
    pass


class Sample:
    def __init__(self, url, method="GET", permission="ScoView", content=None):
        self.content = content
        self.permission = permission
        self.url = url
        self.method = method
        self.result = None
        self.output = "json"
        if permission == "ScoView":
            HEADERS = get_auth_headers("test", "test")
        elif permission == "ScoSuperAdmin":
            HEADERS = get_auth_headers("admin_api", "admin_api")
        elif permission == "UsersAdmin":
            HEADERS = get_auth_headers("admin_api", "admin_api")
        else:
            raise SampleException(f"Bad permission : {permission}")
        if self.method == "GET":
            self.result = GET(self.url, HEADERS)
        elif self.method == "POST":
            if self.content == "":
                self.result = POST_JSON(self.url, headers=HEADERS)
            else:
                HEADERS["Content-Type"] = "application/json ; charset=utf-8"
                self.result = POST_JSON(self.url, json.loads(self.content), HEADERS)
        elif self.method[0] != "#":
            error = f'Bad method : "{self.method}"'
            raise SampleException(error)
        self.shorten()
        with open("sample_TEST.json.md", "tw", encoding="utf-8") as f:
            self.dump(f)

    def _shorten(self, item):
        "Abrège les longues listes: limite à 2 éléments et affiche '...' etc. à la place"
        if isinstance(item, list):
            return [self._shorten(child) for child in item[:2] + ["..."]]
        return item

    def shorten(self):
        "Abrège le résultat"
        self.result = self._shorten(self.result)

    def pp(self):
        print(f"------ url: {self.url}")
        print(f"method: {self.method}")
        print(f"content: {self.content}")
        print(f"permission: {self.permission}")
        pp(self.result, indent=4)

    def dump(self, file):
        self.url = self.url.replace("?date_courante=2022-07-20", "")

        file.write(f"#### {self.method} {self.url}\n")
        if len(self.content) > 0:
            file.write("> `Content-Type: application/json`\n")
            file.write("> \n")
            file.write(f"> `{self.content}`\n\n")

        file.write("```json\n")
        content = json.dumps(self.result, indent=4, sort_keys=True)
        content = content.replace("... etc.", "...")
        # regexp for date like: "2022-08-14T10:01:44.043869+02:00"
        regexp = re.compile(
            r'"(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?"'
        )
        content = regexp.sub('"2022-08-20T12:00:00.000000+02:00"', content)
        file.write(content)
        file.write("\n```\n\n")


class Samples:
    def __init__(self, entry_names):
        """Entry_names: la liste des entrées à reconstruire.
        si None, la totalité des lignes de samples.csv est prise en compte
        """
        self.entries = defaultdict(set)
        self.entry_names = entry_names

    def add_sample(self, line):
        entry = line["entry_name"]
        url = line["url"]
        method = line["method"]
        permission = line["permission"]
        content = line["content"]
        if self.entry_names is None or entry in self.entry_names:
            if method[0] == "#":
                detail = "**ignored**"
            elif content == "":
                detail = ""
            else:
                detail = f": {content}"
            print(f"{entry:50} {method:5} {url:50} {detail}")
            sample = Sample(url, method, permission, content)
            self.entries[entry].add(sample)

    def pp(self):
        for entry, samples in self.entries.items():
            print(f"=== {entry}")
            for sample in samples:
                sample.pp()

    def dump(self):
        for entry, samples in self.entries.items():
            with open(f"{DATA_DIR}sample_{entry}.json.md", "tw", encoding="utf-8") as f:
                f.write(f"### {entry}\n\n")
                # Trié de façon à rendre le fichier indépendant de l'ordre des résultats
                for sample in sorted(samples, key=lambda s: s.url):
                    sample.dump(f)


def make_samples(samples_filename):
    "Génère les samples"
    entry_names = None
    if len(sys.argv) >= 3 and sys.argv[1] == "-i":
        samples_filename = sys.argv[2]
        entry_names = sys.argv[3:] if len(sys.argv) > 3 else None
    else:
        entry_names = sys.argv[1:]

    if os.path.exists(DATA_DIR):
        if not os.path.isdir(DATA_DIR):
            raise SampleException(f"{DATA_DIR} existe déjà et n'est pas un répertoire")
        # DATA_DIR existe déjà - effacer et recréer
        shutil.rmtree(DATA_DIR)
        os.mkdir(DATA_DIR)
    else:
        os.mkdir(DATA_DIR)

    samples = Samples(entry_names)
    df = pd.read_csv(
        samples_filename,
        comment="#",
        sep=";",
        quotechar='"',
        dtype={
            "entry_name": str,
            "url": str,
            "permission": str,
            "method": str,
            "content": str,
        },
        keep_default_na=False,
    )
    df = df.reset_index()
    df.apply(samples.add_sample, axis=1)
    samples.dump()
    return samples


if not CHECK_CERTIFICATE:
    urllib3.disable_warnings()

make_samples(SAMPLES_FILENAME)