import datetime
import math

import numpy as np
from median.constant import TypeServiListe
from median.models import ListeValide, ItemValide, Magasin
from peewee import fn
from ressources.acced.production.acced_production_service import (CONTRAT_DUPLICATE_THOUSAND, _get_config, State,
                                                                  get_equipment_case, get_counters, get_trends)


# Duplicate ratio (for thousand) per week
def get_duplicate_ratio_mean(start, end, type_mag):
    equipment_case = get_equipment_case()
    end_date = datetime.datetime.strptime(end.replace('/', '-'), "%Y-%m-%d") + datetime.timedelta(days=1)
    req = (ListeValide
           .select(fn.YEAR(ListeValide.chrono).alias('year'),
                   fn.WEEKOFYEAR(ListeValide.chrono).alias('week_num'),
                   fn.COUNT(ItemValide.pk_doublon.distinct()).alias('duplicate_num'))
           .join(ItemValide, on=ItemValide.liste_pk == ListeValide.pk)
           .join(Magasin, on=Magasin.mag == equipment_case)
           .where((ListeValide.chrono >= start) & (ListeValide.chrono < end_date) &
                  (Magasin.type_mag == type_mag) & (ItemValide.doublon > 0) &
                  (ListeValide.type_servi == TypeServiListe.Nominatif.value))
           .order_by(ListeValide.chrono.asc())
           .group_by(fn.WEEKOFYEAR(ListeValide.chrono), fn.YEAR(ListeValide.chrono))
           ).objects() if equipment_case is not None else []

    variables = ['API_Nbr_Carnet_Pilulier_Med', 'API_Nbr_Sachet_Pose_Carnet_Med', 'API_Nbr_SachetP_Pose_Carnet_Med',
                 'API_Nbr_Du_Pose_Carnet_Med', 'API_Temps_Cueil_Carnet_Med', 'API_Nbr_Carnet_Pilulier_heure_Med']
    counter_values = get_counters(start=start, end=end, type_mags=[type_mag], all_variables=variables)

    duplicates = 0
    picking_bp_dose = 0

    for duplicate in req:
        counter = next(filter(lambda c: c['date']['num'] == duplicate.week_num and
                              c['date']['year'] == duplicate.year, counter_values), None)
        sum_picking_bp_doses = counter['pillbox_sachet'] + counter['stack_sachet'] if counter is not None else 0
        duplicates = duplicates + duplicate.duplicate_num
        picking_bp_dose = picking_bp_dose + sum_picking_bp_doses

    duplicate_mean = round(duplicates * 1000 / picking_bp_dose, 2) if picking_bp_dose > 0 else None

    state = State.UNKNOW.value if duplicate_mean is None or math.isnan(duplicate_mean) else State.KO.value

    threshold = _get_config(property=CONTRAT_DUPLICATE_THOUSAND)

    if state != State.UNKNOW.value:
        if duplicate_mean <= threshold:
            state = State.OK.value
        elif duplicate_mean <= (1.1*threshold):
            state = State.WARNING.value

    return {
        'unit': 'unit.thousand.ratio',
        'mean': duplicate_mean,
        'state': state,
        'threshold': {
            'max': threshold,
            'warning': round(1.1*threshold, 2),
            'unit': 'unit.thousand.ratio',
        }
    }


def get_duplicate_counter(start, end, type_mag):
    equipment_case = get_equipment_case()
    end_date = datetime.datetime.strptime(end.replace('/', '-'), "%Y-%m-%d") + datetime.timedelta(days=1)
    req = (ListeValide
           .select(fn.YEAR(ListeValide.chrono).alias('year'),
                   fn.WEEKOFYEAR(ListeValide.chrono).alias('week_num'),
                   fn.COUNT(ItemValide.pk_doublon.distinct()).alias('duplicate_num'))
           .join(ItemValide, on=ItemValide.liste_pk == ListeValide.pk)
           .join(Magasin, on=Magasin.mag == equipment_case)
           .where((ListeValide.chrono >= start) & (ListeValide.chrono < end_date) &
                  (Magasin.type_mag == type_mag) & (ItemValide.doublon > 0) &
                  (ListeValide.type_servi == TypeServiListe.Nominatif.value))
           .order_by(ListeValide.chrono.asc())
           .group_by(fn.WEEKOFYEAR(ListeValide.chrono), fn.YEAR(ListeValide.chrono))
           ).objects() if equipment_case is not None else []

    variables = ['API_Nbr_Carnet_Pilulier_Med', 'API_Nbr_Sachet_Pose_Carnet_Med', 'API_Nbr_SachetP_Pose_Carnet_Med',
                 'API_Nbr_Du_Pose_Carnet_Med', 'API_Temps_Cueil_Carnet_Med', 'API_Nbr_Carnet_Pilulier_heure_Med']
    counter_values = get_counters(start=start, end=end, type_mags=[type_mag], all_variables=variables)

    week_ratio = []
    duplicates = list(req)
    for counter in counter_values:
        duplicate = next(filter(lambda d: counter['date']['num'] == d.week_num and
                                counter['date']['year'] == d.year, duplicates), None)
        sum_picking_bp_doses = counter['pillbox_sachet'] + counter['stack_sachet'] if counter is not None else 0
        duplicate_ratio = (duplicate.duplicate_num * 1000) / sum_picking_bp_doses \
            if duplicate is not None and sum_picking_bp_doses > 0 else 0
        week_ratio.append({
            'date': {'num': counter['date']['num'], 'year': counter['date']['year']},
            'duplicate_ratio': duplicate_ratio,
            'duplicate_num': duplicate.duplicate_num if duplicate is not None else 0,
            'picking_bp': sum_picking_bp_doses
        })

    val_weeks = list(map(lambda r: r['date'], week_ratio))
    val_counts = list(map(lambda r: r['duplicate_ratio'], week_ratio))

    list_duplicates = list(map(lambda r: r['duplicate_num'], week_ratio))
    list_picking_bp = list(map(lambda r: r['picking_bp'], week_ratio))

    return {
        'counters': [
            {
                'counter': 'duplicate_num',
                'value': fill_trend_counter(counts=list_duplicates, weeks=val_weeks, threshold={'max': 0})
            },
            {
                'counter': 'picking_bp',
                'value': fill_trend_counter(counts=list_picking_bp, weeks=val_weeks, threshold={'min': 0})
            },
        ],
        'data': get_trends(val_counts=val_counts, val_weeks=val_weeks)
    }


def fill_trend_counter(counts, weeks, threshold):
    trend = get_trends(val_counts=counts, val_weeks=weeks)
    trend['sum'] = sum(counts)
    trend['mean'] = round(np.mean(np.array(counts)), 2) if len(counts) else 0
    trend['threshold'] = threshold
    return trend
