# -*- mode: python -*-
# -*- coding: utf-8 -*-

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2023 Emmanuel Viennet.  All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#   Emmanuel Viennet      emmanuel.viennet@viennet.net
#
##############################################################################

"""Comparaison de deux fichiers Apogée (maquettes)

1) Vérifier:
etape_apogee, vdi_apogee, cod_dip_apogee, annee_scolaire
structure: col_ids (la comparaison portera sur les colonnes communes)


2) Comparer listes d'étudiants
 Présents dans A mais pas dans B
 Présents dans B mais pas dans A
 nombre communs

3) Comparer résultats
Pour chaque étudiant commun:
 Pour chaque colonne commune:
    comparer les résultats

"""
from flask import g, url_for

from app import log
from app.scodoc import sco_apogee_csv, sco_apogee_reader
from app.scodoc.sco_apogee_csv import ApoData
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc import html_sco_header
from app.scodoc import sco_preferences

_HELP_TXT = """
<div class="help">
<p>Outil de comparaison de fichiers (maquettes CSV) Apogée.
</p>
<p>Cet outil compare deux fichiers fournis. Aucune donnée stockée dans ScoDoc n'est utilisée.
</p>
</div>
"""


def apo_compare_csv_form():
    """Form: submit 2 CSV files to compare them."""
    H = [
        html_sco_header.sco_header(page_title="Comparaison de fichiers Apogée"),
        """<h2>Comparaison de fichiers Apogée</h2>
        <form id="apo_csv_add" action="apo_compare_csv" method="post" enctype="multipart/form-data">
        """,
        _HELP_TXT,
        """
        <div class="apo_compare_csv_form_but">
        Fichier Apogée A: 
        <input type="file" size="30" name="file_a"/>
        </div>
        <div class="apo_compare_csv_form_but">
        Fichier Apogée B: 
        <input type="file" size="30" name="file_b"/>
        </div>
        <input type="checkbox" name="autodetect" checked/>autodétecter encodage</input>
        <div class="apo_compare_csv_form_submit">
        <input type="submit" value="Comparer ces fichiers"/>
        </div>
        </form>""",
        html_sco_header.sco_footer(),
    ]
    return "\n".join(H)


def apo_compare_csv(file_a, file_b, autodetect=True):
    """Page comparing 2 Apogee CSV files"""
    try:
        apo_data_a = _load_apo_data(file_a, autodetect=autodetect)
        apo_data_b = _load_apo_data(file_b, autodetect=autodetect)
    except (UnicodeDecodeError, UnicodeEncodeError) as exc:
        dest_url = url_for("notes.semset_page", scodoc_dept=g.scodoc_dept)
        if autodetect:
            raise ScoValueError(
                """
            Erreur: l'encodage de l'un des fichiers est mal détecté.
            Essayez sans auto-détection, ou vérifiez le codage et le contenu 
            des fichiers.
            """,
                dest_url=dest_url,
            ) from exc
        else:
            raise ScoValueError(
                f"""
            Erreur: l'encodage de l'un des fichiers est incorrect.
            Vérifiez qu'il est bien en {sco_apogee_reader.APO_INPUT_ENCODING}
            """,
                dest_url=dest_url,
            ) from exc
    H = [
        html_sco_header.sco_header(page_title="Comparaison de fichiers Apogée"),
        "<h2>Comparaison de fichiers Apogée</h2>",
        _HELP_TXT,
        '<div class="apo_compare_csv">',
        _apo_compare_csv(apo_data_a, apo_data_b),
        "</div>",
        """<p><a href="apo_compare_csv_form" class="stdlink">Autre comparaison</a></p>""",
        html_sco_header.sco_footer(),
    ]
    return "\n".join(H)


def _load_apo_data(csvfile, autodetect=True):
    "Read data from request variable and build ApoData"
    data_b = csvfile.read()
    if autodetect:
        data_b, message = sco_apogee_reader.fix_data_encoding(data_b)
        if message:
            log(f"apo_compare_csv: {message}")
        if not data_b:
            raise ScoValueError("fichier vide ? (apo_compare_csv: no data)")
    data = data_b.decode(sco_apogee_reader.APO_INPUT_ENCODING)
    apo_data = sco_apogee_csv.ApoData(data, orig_filename=csvfile.filename)
    return apo_data


def _apo_compare_csv(apo_a: ApoData, apo_b: ApoData):
    """Generate html report comparing A and B, two instances of ApoData
    representing Apogee CSV maquettes.
    """
    L = []
    # 1-- Check etape and codes
    L.append('<div class="section"><div class="tit">En-tête</div>')
    L.append('<div><span class="key">Nom fichier A:</span><span class="val_ok">')
    L.append(apo_a.orig_filename)
    L.append("</span></div>")
    L.append('<div><span class="key">Nom fichier B:</span><span class="val_ok">')
    L.append(apo_b.orig_filename)
    L.append("</span></div>")
    L.append('<div><span class="key">Étape Apogée:</span>')
    if apo_a.etape_apogee != apo_b.etape_apogee:
        L.append(
            f"""<span class="val_dif">{apo_a.etape_apogee} != {apo_b.etape_apogee}</span>"""
        )
    else:
        L.append(f"""<span class="val_ok">{apo_a.etape_apogee}</span>""")
    L.append("</div>")

    L.append('<div><span class="key">VDI Apogée:</span>')
    if apo_a.vdi_apogee != apo_b.vdi_apogee:
        L.append(
            f"""<span class="val_dif">{apo_a.vdi_apogee} != {apo_b.vdi_apogee}</span>"""
        )
    else:
        L.append(f"""<span class="val_ok">{apo_a.vdi_apogee}</span>""")
    L.append("</div>")

    L.append('<div><span class="key">Code diplôme :</span>')
    if apo_a.cod_dip_apogee != apo_b.cod_dip_apogee:
        L.append(
            f"""<span class="val_dif">{apo_a.cod_dip_apogee} != {apo_b.cod_dip_apogee}</span>"""
        )
    else:
        L.append(f"""<span class="val_ok">{apo_a.cod_dip_apogee}</span>""")
    L.append("</div>")

    L.append('<div><span class="key">Année scolaire :</span>')
    if apo_a.annee_scolaire != apo_b.annee_scolaire:
        L.append(
            '<span class="val_dif">%s != %s</span>'
            % (apo_a.annee_scolaire, apo_b.annee_scolaire)
        )
    else:
        L.append('<span class="val_ok">%s</span>' % (apo_a.annee_scolaire,))
    L.append("</div>")

    # Colonnes:
    a_elts = set(apo_a.apo_csv.apo_elts.keys())
    b_elts = set(apo_b.apo_csv.apo_elts.keys())
    L.append('<div><span class="key">Éléments Apogée :</span>')
    if a_elts == b_elts:
        L.append(f"""<span class="val_ok">{len(a_elts)}</span>""")
    else:
        elts_communs = a_elts.intersection(b_elts)
        elts_only_a = a_elts - a_elts.intersection(b_elts)
        elts_only_b = b_elts - a_elts.intersection(b_elts)
        L.append(
            '<span class="val_dif">différents (%d en commun, %d seulement dans A, %d seulement dans B)</span>'
            % (
                len(elts_communs),
                len(elts_only_a),
                len(elts_only_b),
            )
        )
        if elts_only_a:
            L.append(
                '<div span class="key">Éléments seulement dans A : </span><span class="val_dif">%s</span></div>'
                % ", ".join(sorted(elts_only_a))
            )
        if elts_only_b:
            L.append(
                '<div span class="key">Éléments seulement dans B : </span><span class="val_dif">%s</span></div>'
                % ", ".join(sorted(elts_only_b))
            )
    L.append("</div>")
    L.append("</div>")  # /section

    # 2--
    L.append('<div class="section"><div class="tit">Étudiants</div>')

    a_nips = set(apo_a.etud_by_nip)
    b_nips = set(apo_b.etud_by_nip)
    nb_etuds_communs = len(a_nips.intersection(b_nips))
    nb_etuds_dif = len(a_nips.union(b_nips) - a_nips.intersection(b_nips))
    L.append("""<div><span class="key">Liste d'étudiants :</span>""")
    if a_nips == b_nips:
        L.append(
            f"""<span class="s_ok">
        {len(a_nips)} étudiants (tous présents dans chaque fichier)</span>
        """
        )
    else:
        L.append(
            f"""<span class="val_dif">différents ({nb_etuds_communs} en commun, {
                    nb_etuds_dif} différents)</span>"""
        )
    L.append("</div>")
    L.append("</div>")  # /section

    # 3-- Résultats de chaque étudiant:
    if nb_etuds_communs > 0:
        L.append(
            """<div class="section sec_table">
        <div class="tit">Différences de résultats des étudiants présents dans les deux fichiers
        </div>
        <p>
        """
        )
        T = apo_table_compare_etud_results(apo_a, apo_b)
        if T.get_nb_rows() > 0:
            L.append(T.html())
        else:
            L.append(
                f"""<p class="p_ok">aucune différence de résultats 
            sur les {nb_etuds_communs} étudiants communs 
            (<em>les éléments Apogée n'apparaissant pas dans les deux 
            fichiers sont omis</em>)
            </p>
            """
            )
        L.append("</div>")  # /section

    return "\n".join(L)


def apo_table_compare_etud_results(A, B):
    """"""
    D = compare_etuds_res(A, B)
    T = GenTable(
        rows=D,
        titles={
            "nip": "NIP",
            "nom": "Nom",
            "prenom": "Prénom",
            "elt_code": "Element",
            "type_res": "Type",
            "val_A": "A: %s" % (A.orig_filename or ""),
            "val_B": "B: %s" % (B.orig_filename or ""),
        },
        columns_ids=("nip", "nom", "prenom", "elt_code", "type_res", "val_A", "val_B"),
        html_class="table_leftalign",
        html_with_td_classes=True,
        preferences=sco_preferences.SemPreferences(),
    )
    return T


def _build_etud_res(e, apo_data):
    r = {}
    for elt_code in apo_data.apo_csv.apo_elts:
        elt = apo_data.apo_csv.apo_elts[elt_code]
        try:
            # les colonnes de cet élément
            col_ids_type = [(ec["apoL_a01_code"], ec["Type Rés."]) for ec in elt.cols]
        except KeyError as exc:
            raise ScoValueError(
                "Erreur: un élément sans 'Type Rés.'. Vérifiez l'encodage de vos fichiers."
            ) from exc
        r[elt_code] = {}
        for col_id, type_res in col_ids_type:
            r[elt_code][type_res] = e.cols[col_id]
    return r


def compare_etud_res(r_A, r_B, remove_missing=True):
    """Pour chaque valeur difference dans les resultats d'un etudiant
    elt_code   type_res   val_A     val_B
    """
    diffs = []
    elt_codes = set(r_A).union(set(r_B))
    for elt_code in elt_codes:
        for type_res in r_A.get(elt_code, r_B.get(elt_code)):
            if elt_code not in r_A:
                if remove_missing:
                    continue
                else:
                    val_A = None  # element absent
            else:
                val_A = r_A[elt_code][type_res]
            if elt_code not in r_B:
                if remove_missing:
                    continue
                else:
                    val_B = None  # element absent
            else:
                val_B = r_B[elt_code][type_res]
            if type_res == "N":
                # Cas particulier pour les notes: compare les nombres
                try:
                    val_A_num = float(val_A.replace(",", "."))
                    val_B_num = float(val_B.replace(",", "."))
                except ValueError:
                    val_A_num, val_B_num = val_A, val_B
                val_A, val_B = val_A_num, val_B_num
            if val_A != val_B:
                diffs.append(
                    {
                        "elt_code": elt_code,
                        "type_res": type_res,
                        "val_A": val_A,
                        "val_B": val_B,
                    }
                )
    return diffs


def compare_etuds_res(A, B):
    """
    nip, nom, prenom, elt_code, type_res, val_A, val_B
    """
    A_nips = set(A.etud_by_nip)
    B_nips = set(B.etud_by_nip)
    common_nips = A_nips.intersection(B_nips)
    # A_not_B_nips = A_nips - B_nips
    # B_not_A_nips = B_nips - A_nips
    D = []
    for nip in common_nips:
        etu_A = A.etud_by_nip[nip]
        etu_B = B.etud_by_nip[nip]
        r_A = _build_etud_res(etu_A, A)
        r_B = _build_etud_res(etu_B, B)
        diffs = compare_etud_res(r_A, r_B)
        for d in diffs:
            d.update(
                {"nip": etu_A["nip"], "nom": etu_A["nom"], "prenom": etu_A["prenom"]}
            )
            D.append(d)
    return D