# -*- coding: UTF-8 -*
##############################################################################
# ScoDoc
# Copyright (c) 1999 - 2023 Emmanuel Viennet.  All rights reserved.
# See LICENSE
##############################################################################

"""ScoDoc models: Groups & partitions
"""
from operator import attrgetter

from app import db
from app.models import SHORT_STR_LEN
from app.models import GROUPNAME_STR_LEN
from app.scodoc import sco_utils as scu


class Partition(db.Model):
    """Partition: découpage d'une promotion en groupes"""

    __table_args__ = (db.UniqueConstraint("formsemestre_id", "partition_name"),)

    id = db.Column(db.Integer, primary_key=True)
    partition_id = db.synonym("id")
    formsemestre_id = db.Column(
        db.Integer,
        db.ForeignKey("notes_formsemestre.id"),
        index=True,
    )
    # "TD", "TP", ... (NULL for 'all')
    partition_name = db.Column(db.String(SHORT_STR_LEN))
    # Numero = ordre de presentation)
    numero = db.Column(db.Integer, nullable=False, default=0)
    # Calculer le rang ?
    bul_show_rank = db.Column(
        db.Boolean(), nullable=False, default=False, server_default="false"
    )
    # Montrer quand on indique les groupes de l'étudiant ?
    show_in_lists = db.Column(
        db.Boolean(), nullable=False, default=True, server_default="true"
    )
    # Editable (créer/renommer groupes) ? (faux pour les groupes de parcours)
    groups_editable = db.Column(
        db.Boolean(), nullable=False, default=True, server_default="true"
    )
    groups = db.relationship(
        "GroupDescr",
        backref=db.backref("partition", lazy=True),
        lazy="dynamic",
        cascade="all, delete-orphan",
        order_by="GroupDescr.numero",
    )

    def __init__(self, **kwargs):
        super(Partition, self).__init__(**kwargs)
        if self.numero is None:
            # génère numero à la création
            last_partition = Partition.query.order_by(Partition.numero.desc()).first()
            if last_partition:
                self.numero = last_partition.numero + 1
            else:
                self.numero = 1

    def __repr__(self):
        return f"""<{self.__class__.__name__} {self.id} "{self.partition_name or '(default)'}">"""

    @classmethod
    def check_name(
        cls, formsemestre: "FormSemestre", partition_name: str, existing=False
    ) -> bool:
        """check if a partition named 'partition_name' can be created in the given formsemestre.
        If existing is True, allow a partition_name already existing in the formsemestre.
        """
        if not isinstance(partition_name, str):
            return False
        if not (0 < len(partition_name.strip()) < SHORT_STR_LEN):
            return False
        if (not existing) and (
            partition_name in [p.partition_name for p in formsemestre.partitions]
        ):
            return False
        return True

    def is_parcours(self) -> bool:
        "Vrai s'il s'agit de la partition de parcours"
        return self.partition_name == scu.PARTITION_PARCOURS

    def to_dict(self, with_groups=False, str_keys: bool = False) -> dict:
        """as a dict, with or without groups.
        If str_keys, convert integer dict keys to strings (useful for JSON)
        """
        d = dict(self.__dict__)
        d["partition_id"] = self.id
        d.pop("_sa_instance_state", None)
        d.pop("formsemestre", None)

        if with_groups:
            groups = sorted(self.groups, key=attrgetter("numero", "group_name"))
            # un dict et non plus une liste, pour JSON
            if str_keys:
                d["groups"] = {
                    str(group.id): group.to_dict(with_partition=False)
                    for group in groups
                }
            else:
                d["groups"] = {
                    group.id: group.to_dict(with_partition=False) for group in groups
                }
        return d

    def get_etud_group(self, etudid: int) -> "GroupDescr":
        "Le groupe de l'étudiant dans cette partition, ou None si pas présent"
        return (
            GroupDescr.query.filter_by(partition_id=self.id)
            .join(group_membership)
            .filter_by(etudid=etudid)
            .first()
        )


class GroupDescr(db.Model):
    """Description d'un groupe d'une partition"""

    __tablename__ = "group_descr"
    __table_args__ = (db.UniqueConstraint("partition_id", "group_name"),)

    id = db.Column(db.Integer, primary_key=True)
    group_id = db.synonym("id")
    partition_id = db.Column(db.Integer, db.ForeignKey("partition.id"))
    #  "A", "C2", ...  (NULL for 'all'):
    group_name = db.Column(db.String(GROUPNAME_STR_LEN))
    # Numero = ordre de presentation
    numero = db.Column(db.Integer, nullable=False, default=0)

    etuds = db.relationship(
        "Identite",
        secondary="group_membership",
        lazy="dynamic",
    )

    def __repr__(self):
        return (
            f"""<{self.__class__.__name__} {self.id} "{self.group_name or '(tous)'}">"""
        )

    def get_nom_with_part(self) -> str:
        "Nom avec partition: 'TD A'"
        return f"{self.partition.partition_name or ''} {self.group_name or '-'}"

    def to_dict(self, with_partition=True) -> dict:
        """as a dict, with or without partition"""
        d = dict(self.__dict__)
        d.pop("_sa_instance_state", None)
        if with_partition:
            d["partition"] = self.partition.to_dict(with_groups=False)
        return d

    @classmethod
    def check_name(
        cls, partition: "Partition", group_name: str, existing=False, default=False
    ) -> bool:
        """check if a group named 'group_name' can be created in the given partition.
        If existing is True, allow a group_name already existing in the partition.
        If default, name must be empty and default group must not yet exists.
        """
        if not isinstance(group_name, str):
            return False
        if not default and not (0 < len(group_name.strip()) < GROUPNAME_STR_LEN):
            return False
        if (not existing) and (group_name in [g.group_name for g in partition.groups]):
            return False
        return True


group_membership = db.Table(
    "group_membership",
    db.Column("etudid", db.Integer, db.ForeignKey("identite.id", ondelete="CASCADE")),
    db.Column("group_id", db.Integer, db.ForeignKey("group_descr.id")),
    db.UniqueConstraint("etudid", "group_id"),
)
# class GroupMembership(db.Model):
#     """Association groupe / étudiant"""

#     __tablename__ = "group_membership"
#     __table_args__ = (db.UniqueConstraint("etudid", "group_id"),)
#     id = db.Column(db.Integer, primary_key=True)
#     etudid = db.Column(db.Integer, db.ForeignKey("identite.id", ondelete="CASCADE"))
#     group_id = db.Column(db.Integer, db.ForeignKey("group_descr.id"))