import copy
import json
import logging
import operator
import time
from datetime import timedelta, datetime
from functools import reduce

from common.status import HTTP_200_OK
from flask import Blueprint, request
from flask_jwt_extended import jwt_required
from median.constant import EtatListe, PatientGlobal, TypeServiListe, TypeListe
from median.models import ListeItemModel, Service, ListeModel, Patient, Sejour, Prescription, Product
from peewee import fn, DoesNotExist, JOIN

acced_saved_blueprint = Blueprint('acced_saved', __name__)

logger = logging.getLogger('median')


@acced_saved_blueprint.route('', methods=['GET'])
@jwt_required()
def get_all():
    args = request.args
    _ipp = args['ipp']
    logger.info("Récupérer les listes de sorties...")
    acced_list = _getPatient(_ipp, equipment='ACCED')
    return acced_list


@acced_saved_blueprint.route('liste_sorties_patients/<string:pk_liste>', methods=['POST'])
@jwt_required()
def get_detail_patient(pk_liste: str):
    logger.info("Récupérer les items de listes de sorties de la liste : '%s'" % pk_liste)
    try:

        _items = (
            ListeModel.select(ListeModel, ListeItemModel, Product).join(
                Patient, on=(ListeModel.num_ipp == Patient.ipp)
            ).switch(ListeModel).join(
                ListeItemModel, on=(ListeModel.liste == ListeItemModel.liste).alias('itemList')
            ).switch(ListeModel).join(
                Product, on=(ListeItemModel.reference == Product.reference).alias('reference')
            ).distinct().where(
                (ListeModel.mode == 'Z') & (Patient.pk == pk_liste)
                & (ListeModel.type_servi == TypeServiListe.Nominatif.value)
            )
        )

        if len(_items) == 0:
            logger.error('No list for id %s' % pk_liste)
            return {'data': []}, HTTP_200_OK

        output_list = []
        for item in _items:

            if item.ddeb is None:
                dateElt = None
            else:
                dateElt = item.ddeb.isoformat()

            output_list.append({
                'list_pk': item.pk,
                'list_name': item.liste,
                'pk': item.itemList.pk,
                'item': item.itemList.item,
                'etat': item.itemList.etat,
                'reference': item.reference.reference,
                'fraction': item.itemList.fraction,
                'designation': item.reference.designation,
                'qte_demandee': item.itemList.qte_prescrite * item.itemList.fraction / 100,
                'qte_servie': item.itemList.qte_serv * item.itemList.fraction / 100,
                'moment': item.itemList.moment,
                'heure': item.itemList.heure,
                'risky': item.reference.risque,
                'ifNeeded': item.itemList.readonly,
                'date_deb': dateElt,
                'reference_pk': item.reference.pk
            })

        return {'data': output_list}, HTTP_200_OK
    except Exception as error:
        logger.error(('Get reappro items Datatables raised an exception: ', error.args))
        return {'message': error.args}


@acced_saved_blueprint.route('liste_sorties_items/<string:pk_liste>', methods=['POST'])
@jwt_required()
def get_detail_global(pk_liste):
    logger.info("Récupérer les items de listes de sorties de la liste : '%s'" % pk_liste)

    try:
        _items = (
            ListeModel.select(
                ListeModel.pk,
                ListeModel.liste,
                ListeModel.ddeb,
                ListeItemModel.pk,
                ListeItemModel.item,
                ListeItemModel.etat,
                ListeItemModel.fraction,
                ListeItemModel.qte_dem,
                ListeItemModel.qte_serv,
                ListeItemModel.moment,
                ListeItemModel.heure,
                Product.reference,
                Product.designation,
                Product.pk,
            )
            .join(Service, on=(ListeModel.service == Service.code))
            .alias("service")
            .switch(ListeModel)
            .join(ListeItemModel, on=(ListeModel.liste == ListeItemModel.liste).alias("itemList"))
            .switch(ListeModel)
            .join(Product, on=(ListeItemModel.reference == Product.reference).alias("reference"))
            .where(
                (ListeModel.mode == "S")
                & (Service.pk == pk_liste)
                & (ListeModel.num_ipp == PatientGlobal.Ipp.value)
                & (ListeModel.type_servi != "EXOTIQUE")
            )
        )

        if len(_items) == 0:
            logger.error('No list for id %s' % pk_liste)
            return {'data': []}, HTTP_200_OK

        output_list = []
        for item in _items:

            if item.ddeb is None:
                date_elt = None
            else:
                date_elt = item.ddeb.isoformat()

            output_list.append({
                'list_pk': item.pk,
                'list_name': item.liste,
                'reference': item.reference.reference,
                'designation': item.reference.designation,
                'pk': item.itemList.pk,
                'item': item.itemList.item,
                'etat': item.itemList.etat,
                'fraction': item.itemList.fraction,
                'qte_demandee': item.itemList.qte_dem,
                'qte_servie': item.itemList.qte_serv,
                'moment': item.itemList.moment,
                'heure': item.itemList.heure,
                'date_deb': date_elt if date_elt is not None else time.strftime('%Y-%m-%d'),
                'reference_pk': item.reference.pk
            })

        return {'data': output_list}, HTTP_200_OK
    except Exception as error:
        logger.error(('Get archived items raised an exception: ', error.args))
        return {'message': error.args}


@acced_saved_blueprint.route('replay', methods=['POST'])
@jwt_required()
def replay():
    data = json.loads(request.data)

    __replay(data=data, analyze=False)

    return {}, HTTP_200_OK


@acced_saved_blueprint.route('replay/analyze', methods=['POST'])
@jwt_required()
def replay_analyze():
    data = json.loads(request.data)
    errors = __replay(data=data, analyze=True)

    return {'report': errors}, HTTP_200_OK


def __replay(data, analyze):
    nb_day = int(data['nb_day'])
    list_pks = data['listes']
    start = data['start']

    f_liste_max = ListeModel.alias('ListeModelMax')
    max_ddeb = (f_liste_max.select(fn.MAX(f_liste_max.ddeb))
                .where((f_liste_max.num_ipp == ListeModel.num_ipp) &
                       (f_liste_max.mode == 'Z') &
                       (f_liste_max.type_servi == 'NOMINATIF')))

    where_expr = ((Patient.pk << list_pks) &
                  (ListeModel.mode == 'Z') &
                  (ListeModel.type_servi == 'NOMINATIF') &
                  (ListeModel.ddeb == max_ddeb))

    last_lsts = (ListeModel.select(ListeModel, Patient)
                 .join(Patient, on=(ListeModel.num_ipp == Patient.ipp))
                 .where(where_expr)
                 .order_by(ListeModel.ddeb.desc()))
    errors = []

    for last_lst in last_lsts:
        for d in range(nb_day):
            __duplicate_list(start=datetime.strptime(start, '%Y-%m-%d'),
                             current_list=last_lst,
                             num_day=d, report=errors, analyze=analyze)
    return errors


def __duplicate_list(start, current_list, num_day, report, analyze):
    # Note : Analyze mode is used for the summary before actual duplication
    dt = start + timedelta(days=num_day)

    name = f"{dt.strftime('%Y-%m-%d')} {current_list.num_sej}-R"

    r = next(filter(lambda e: e['ipp'] == current_list.num_ipp and e['num_sej'] == current_list.num_sej, report), None)

    if r is None and analyze:
        r = {
            'ipp': current_list.num_ipp,
            'num_sej': current_list.num_sej,
            'dates': [],
            'patient': {
                'firstname': current_list.patient.prenom,
                'lastname': current_list.patient.nom,
            }
        }
        report.append(r)

    try:

        (ListeModel.select(ListeModel.num_ipp, ListeModel.num_sej, Patient.nom, Patient.prenom)
         .join(Patient, on=(ListeModel.num_ipp == Patient.ipp))
         .where((ListeModel.mode == 'S') & (ListeModel.liste == name)).get())

        if analyze:
            r['dates'].append({
                'date': dt.strftime('%Y-%m-%d'),
                'isError': True
            })

    except DoesNotExist:
        if not analyze:
            list_new: ListeModel = copy.deepcopy(current_list)
            list_new.mode = TypeListe.Output.value
            list_new.etat = EtatListe.Vierge.value
            list_new.selectionne = 0
            list_new.ddeb = dt
            list_new.pk = None
            list_new.sequence = 'R'
            list_new.liste = name
            list_new.save()

            items_list = ListeItemModel.select().where((ListeItemModel.liste == current_list.liste))

            for item_list in items_list:
                new_item: ListeItemModel = copy.deepcopy(item_list)
                new_item.pk = None
                new_item.liste = list_new.liste
                new_item.qte_serv = 0
                new_item.dtprise = dt
                new_item.item_wms = 0
                new_item.mode = TypeListe.Output.value
                new_item.etat = EtatListe.Vierge.value
                new_item.save()
        else:
            r['dates'].append({
                'date': dt.strftime('%Y-%m-%d'),
                'isError': False
            })


def _getPatient(_ipp, equipment, criterias=None):
    _out = []

    if criterias is None:
        criterias = []

    expr = (ListeModel.mode == 'Z')
    if equipment == 'ACCED':
        expr = expr & (ListeModel.type_servi << ['NOMINATIF']) & (ListeModel.zone_deb == '')

    if len(criterias) > 0:
        lst = list(map(lambda s: (
            (Patient.nom.contains(s.strip())) |
            (Patient.prenom.contains(s.strip())) |
            (Patient.ipp.contains(s.strip())) |
            (Service.code.contains(s.strip())) |
            (Service.libelle.contains(s.strip()))
        ), criterias))
        expr = expr & reduce(operator.and_, lst)

    list_ids = Patient.select(
        Patient.pk,
        Patient.ipp, Patient.nom, Patient.prenom, Patient.nom_jeune_fille, Patient.sexe, Patient.date_naissance,
        Sejour.sejour, Sejour.chambre, Sejour.lit, ListeModel.num_sej,
        Service.pk, Service.code, Service.libelle,
        Prescription.ordre,
        ListeModel.ddeb, ListeModel.liste,
        ListeModel.pk.alias('liste_pk'),
    ).distinct().join(
        ListeModel, on=(Patient.ipp == ListeModel.num_ipp).alias('list_sortie')
    ).join(
        Service, on=(ListeModel.service == Service.code).alias('service')
    ).switch(Patient).join(
        Sejour, JOIN.LEFT_OUTER, on=(Patient.ipp == Sejour.ipp & Sejour.sejour == ListeModel.num_sej)
    ).switch(Patient).join(
        Prescription, JOIN.LEFT_OUTER, on=(
            (Patient.ipp == Prescription.ipp) &
            (Sejour.sejour == Prescription.sejour) &
            (ListeModel.id_prescription == Prescription.pk)
        )
    ).where(expr).order_by(Service.libelle, Patient.prenom, Patient.nom)

    for item in list_ids:

        list_item = list(filter(lambda f: f['ipp'] == item.ipp, _out))
        if not any(list_item):
            obj = {
                'type': 'patient',
                'service_pk': item.list_sortie.service.pk,
                'service_label': item.list_sortie.service.libelle,
                'service_code': item.list_sortie.service.code,
                'stay_num': item.list_sortie.num_sej,
                'stay_bed': item.sejour.lit if hasattr(item, 'sejour') else None,
                'stay_room': item.sejour.chambre if hasattr(item, 'sejour') else None,
                'title': item.list_sortie.service.libelle,
                'ipp': item.ipp,
                'nom': item.nom,
                'dates': [],
                'prenom': item.prenom,
                'patient_gender': item.sexe,
                'maiden_name': item.nom_jeune_fille,
                'birth_date': item.date_naissance.strftime('%Y-%m-%d'),
                'prescription_order': item.prescription.ordre if hasattr(item, 'prescription') else None,
                'pk': item.pk,
            }
            _out.append(obj)
        else:
            obj = list_item[0]

        obj['dates'].append({
            'date': item.list_sortie.ddeb.strftime('%Y-%m-%d'),
            'pk': item.list_sortie.liste_pk,
            'liste': item.list_sortie.liste
        })

    return _out
