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

"""Initialise une base pour les tests de l'API ScoDoc 9

 Création des départements, formations, semestres, étudiants, groupes...

 Utilisation: voir tests/api/README.md

"""
import datetime
import random
import time
import sys

from app import db
from app.auth.models import Role, User
from app.but.import_refcomp import orebut_import_refcomp
from app import models
from app.models import departements
from app.models import (
    Absence,
    Departement,
    Formation,
    FormSemestre,
    FormSemestreEtape,
    Identite,
    ModuleImpl,
    NotesNotes,
)
from app.scodoc import (
    sco_cache,
    sco_evaluation_db,
    sco_formations,
    sco_formsemestre_inscriptions,
    sco_groups,
)
from app.scodoc.sco_permissions import Permission
from tools.fakeportal.gen_nomprenoms import nomprenom

random.seed(12345678)  # tests reproductibles

# La formation à utiliser:
FORMATION_XML_FILENAME = "tests/ressources/formations/scodoc_formation_RT_BUT_RT_v1.xml"
REFCOMP_FILENAME = (
    "ressources/referentiels/but2022/competences/but-RT-05012022-081735.xml"
)


def create_departements(acronyms: list[str]) -> list[Departement]:
    "Create depts"
    return [departements.create_dept(acronym) for acronym in acronyms]


def import_formation(dept_id: int) -> Formation:
    """Import formation from XML.
    Associate ref. comp.
    Returns formation_id
    """
    with open(FORMATION_XML_FILENAME, encoding="utf-8") as f:
        doc = f.read()
    # --- Création de la formation (import programme)
    f = sco_formations.formation_import_xml(doc)
    formation = Formation.query.get(f[0])
    # --- Association ref. comp.
    with open(REFCOMP_FILENAME, encoding="utf-8") as f:
        xml_data = f.read()
        ref_comp = orebut_import_refcomp(
            xml_data, dept_id=dept_id, orig_filename=REFCOMP_FILENAME
        )
    formation.referentiel_competence_id = ref_comp.id
    db.session.add(formation)
    db.session.commit()
    return formation


def create_users(depts: list[Departement]) -> tuple:
    """Crée les roles et utilisateurs nécessaires aux tests"""
    dept = depts[0]
    # Le rôle  standard LecteurAPI existe déjà: lui donne les permissions
    #  ScoView, ScoAbsAddBillet, ScoEtudChangeGroups
    role_lecteur = Role.query.filter_by(name="LecteurAPI").first()
    if role_lecteur is None:
        print("Erreur: rôle LecteurAPI non existant")
        sys.exit(1)
    perm_sco_view = Permission.get_by_name("ScoView")
    role_lecteur.add_permission(perm_sco_view)
    perm_sco_users = Permission.get_by_name("ScoUsersView")
    role_lecteur.add_permission(perm_sco_users)
    # Edition billets
    perm_billets = Permission.get_by_name("ScoAbsAddBillet")
    role_lecteur.add_permission(perm_billets)
    perm_groups = Permission.get_by_name("ScoEtudChangeGroups")
    role_lecteur.add_permission(perm_groups)
    db.session.add(role_lecteur)

    # Un role pour juste voir les utilisateurs
    role_users_viewer = Role(
        name="UsersViewer", permissions=Permission.ScoUsersView | Permission.ScoView
    )
    db.session.add(role_users_viewer)
    # Role View sur l'API, pour demander un jeton
    role_view = Role(name="Viewer", permissions=Permission.ScoView)
    db.session.add(role_view)

    # Un utilisateur "test" (passwd test) pouvant lire l'API
    user_test = User(user_name="test", nom="Doe", prenom="John", dept=dept.acronym)
    user_test.set_password("test")
    db.session.add(user_test)
    user_test.add_role(role_lecteur, None)

    # Un utilisateur "other" n'ayant aucune permission sur l'API
    other = User(user_name="other", nom="Sans", prenom="Permission", dept=dept.acronym)
    other.set_password("other")
    db.session.add(other)

    # Un utilisateur "admin_api"
    admin_api = User(user_name="admin_api", nom="Admin", prenom="API")
    admin_api.set_password("admin_api")
    db.session.add(admin_api)
    role_super_admin = Role.query.filter_by(name="SuperAdmin").first()
    if role_super_admin is None:
        print("Erreur: rôle SuperAdmin non existant")
        sys.exit(1)
    admin_api.add_role(role_super_admin, None)

    # Des utilisateurs voyant certains utilisateurs...
    users = []
    for dept in depts:
        u = User(
            user_name=f"u_{dept.acronym}",
            nom=f"U {dept.acronym}",
            prenom="lambda",
            dept=dept.acronym,
        )
        u.set_password("test")
        users.append(u)
        db.session.add(u)
    # Roles pour tester les fonctions sur les utilisateurs
    for i, u in enumerate(users):
        for dept in depts[: i + 1]:
            u.add_role(role_users_viewer, dept.acronym)
            u.add_role(role_view, None)  # necessaire pour avoir le jeton

    db.session.commit()
    return user_test, other


def create_fake_etud(dept: Departement) -> Identite:
    """Créé un faux étudiant et l'insère dans la base."""
    civilite = random.choice(("M", "F", "X"))
    nom, prenom = nomprenom(civilite)
    etud: Identite = Identite(
        civilite=civilite, nom=nom, prenom=prenom, dept_id=dept.id
    )
    db.session.add(etud)
    db.session.commit()
    # créé un étudiant sur deux avec un NIP et INE alphanumérique
    etud.code_nip = f"{etud.id}" if (etud.id % 2) else f"NIP{etud.id}"
    etud.code_ine = f"INE{etud.id}" if (etud.id % 2) else f"{etud.id}"
    db.session.add(etud)
    db.session.commit()
    adresse = models.Adresse(
        etudid=etud.id, email=f"{etud.prenom}.{etud.nom}@example.com"
    )
    db.session.add(adresse)
    admission = models.Admission(etudid=etud.id)
    db.session.add(admission)
    db.session.commit()
    return etud


def create_etuds(dept: Departement, nb=16) -> list:
    "create nb etuds"
    return [create_fake_etud(dept) for _ in range(nb)]


def create_formsemestre(
    formation: Formation, responsable: User, semestre_idx=1
) -> FormSemestre:
    """Create formsemestre and moduleimpls
    responsable: resp. du formsemestre
    """
    formsemestre = FormSemestre(
        dept_id=formation.dept_id,
        semestre_id=semestre_idx,
        titre="Semestre test",
        date_debut=datetime.datetime(2021, 9, 1),
        date_fin=datetime.datetime(2022, 8, 31),
        modalite="FI",
        formation=formation,
    )
    db.session.add(formsemestre)
    db.session.commit()
    # Crée un modulimpl par module de ce semestre:
    for module in formation.modules.filter_by(semestre_id=semestre_idx):
        modimpl = models.ModuleImpl(
            module_id=module.id,
            formsemestre_id=formsemestre.id,
            responsable_id=responsable.id,
        )
        db.session.add(modimpl)
    db.session.commit()
    partition_id = sco_groups.partition_create(
        formsemestre.id, default=True, redirect=False
    )
    _group_id = sco_groups.create_group(partition_id, default=True)
    return formsemestre


def inscrit_etudiants(etuds: list, formsemestre: FormSemestre):
    """Inscrit les étudiants au semestre et à tous ses modules.
    1/5 DEF, 1/5 DEF
    """
    for i, etud in enumerate(etuds):
        if (i + 1) % 5 == 0:
            etat = "D"
        elif (i + 2) % 5 == 0:
            etat = "DEF"
        else:
            etat = "I"
        sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules(
            formsemestre.id,
            etud.id,
            group_ids=[],
            etat=etat,
            method="init db test",
        )


def create_evaluations(formsemestre: FormSemestre):
    "creation d'une evaluation dans cahque modimpl du semestre"
    for modimpl in formsemestre.modimpls:
        args = {
            "moduleimpl_id": modimpl.id,
            "jour": None,
            "heure_debut": "8h00",
            "heure_fin": "9h00",
            "description": None,
            "note_max": 20,
            "coefficient": 1.0,
            "visibulletin": True,
            "publish_incomplete": True,
            "evaluation_type": None,
            "numero": None,
        }
        evaluation_id = sco_evaluation_db.do_evaluation_create(**args)


def saisie_notes_evaluations(formsemestre: FormSemestre, user: User):
    """
    Saisie les notes des evaluations d'un semestre
    """
    etuds = formsemestre.etuds
    list_etuds = []
    for etu in etuds:
        list_etuds.append(etu)

    date_debut = formsemestre.date_debut
    date_fin = formsemestre.date_fin

    list_ues = formsemestre.query_ues()

    def saisir_notes(evaluation_id: int, condition: int):
        """
        Permet de saisir les notes de manière aléatoire suivant une condition
        Définition des valeurs de condition :
            0 : all_notes_saisies
            1 : all_notes_manquantes
            2 : some_notes_manquantes
        """
        if condition == 2:
            return  # nothing to do
        if condition == 0:
            list_a_saisir = list_etuds
        else:
            percent = 80 / 100
            len_etuds = len(list_etuds)
            list_a_saisir = random.sample(list_etuds, k=int(percent * len_etuds))

        for etud in list_a_saisir:
            note = NotesNotes(
                etudid=etud.id,
                evaluation_id=evaluation_id,
                value=random.uniform(0, 20),
                comment="",
                date=date_debut + random.random() * (date_fin - date_debut),
                uid=user.id,
            )
            db.session.add(note)
        db.session.commit()

    for ue in list_ues:
        mods = ue.modules
        for mod in mods:
            moduleimpl = ModuleImpl.query.get_or_404(mod.id)
            for evaluation in moduleimpl.evaluations:
                condition_saisie_notes = random.randint(0, 2)
                saisir_notes(evaluation.id, condition_saisie_notes)


def add_absences(formsemestre: FormSemestre):
    """
    Ajoute des absences en base
    """
    date_debut = formsemestre.date_debut
    date_fin = formsemestre.date_fin

    etuds = formsemestre.etuds
    for etu in etuds:
        aleatoire = random.randint(0, 1)
        if aleatoire == 1:
            nb_absences = random.randint(1, 5)
            for absence in range(0, nb_absences):
                etudid = etu.id
                jour = date_debut + random.random() * (date_fin - date_debut)
                estabs = True
                estjust = True if random.randint(0, 1) == 1 else False
                matin = True if random.randint(0, 1) == 1 else False
                description = ""
                absence = Absence(
                    etudid=etudid,
                    jour=jour,
                    estabs=estabs,
                    estjust=estjust,
                    matin=matin,
                    description=description,
                )
                db.session.add(absence)
    db.session.commit()


def create_etape_apo(formsemestre: FormSemestre):
    """
    Ajoute étapes Apogée au formsemestre
    """
    etape_apo1 = FormSemestreEtape(
        id=1, formsemestre_id=formsemestre.id, etape_apo="A1"
    )
    db.session.add(etape_apo1)

    etape_apo2 = FormSemestreEtape(
        id=2, formsemestre_id=formsemestre.id, etape_apo="A2"
    )
    db.session.add(etape_apo2)

    etape_apo3 = FormSemestreEtape(
        id=3, formsemestre_id=formsemestre.id, etape_apo="A3"
    )
    db.session.add(etape_apo3)

    list_etapes = [etape_apo1, etape_apo2, etape_apo3]
    formsemestre.etapes = list_etapes
    db.session.commit()


def init_test_database():
    """Appelé par la commande `flask init-test-database`

    Création d'un département et de son contenu pour les tests
    """
    import app as mapp

    depts = create_departements(["TAPI", "AA", "BB", "CC", "DD"])
    dept = depts[0]
    mapp.set_sco_dept(dept.acronym)
    user_lecteur, user_autre = create_users(depts)
    with sco_cache.DeferredSemCacheManager():
        etuds = create_etuds(dept, nb=20)
        formation = import_formation(dept.id)
        formsemestre = create_formsemestre(formation, user_lecteur)
        create_evaluations(formsemestre)
        inscrit_etudiants(etuds[:16], formsemestre)
        saisie_notes_evaluations(formsemestre, user_lecteur)
        add_absences(formsemestre)
        create_etape_apo(formsemestre)
    # à compléter
    # - groupes
    # - absences
    # - notes
    # - décisions de jury
    # ...