import math

import numpy as np
from median.constant import HistoryType, PickingMachineType
from median.models import HistoriqueCoupe, Magasin, Historique, CompteurJour
from peewee import fn
from ressources.acced.production.acced_production_service import CONTRAT_CUT_REJECT_PERCENTAGE, State, _get_config
from ressources.acced.production.acced_production_service import get_trends

from ressources.acced.production.acced_production_service import get_counters, get_counter_sum


def get_cut_counter(type_mags, start, end):
    equipments = Magasin.select(Magasin.type_machine, Magasin.type_mag).where(Magasin.type_mag << type_mags)
    acced_v2s = list(map(lambda r: r.type_mag,
                         filter(lambda e: e.type_machine != PickingMachineType.Acced_Dose_Unit.value, equipments)))
    acced_v3s = list(map(lambda r: r.type_mag,
                         filter(lambda e: e.type_machine == PickingMachineType.Acced_Dose_Unit.value, equipments)))

    if len(acced_v2s) == 1:
        return get_cut_counter_v2(acced_v2s, start, end)
    elif len(acced_v3s) == 1:
        return get_cut_counter_v3(acced_v3s, start, end)


def get_cut_counter_v3(type_mags, start, end):
    variables = ['API_Nbr_Coupe_Med', 'API_Nbr_BoitePass_CP_Med',
                 'API_Nbr_Rejet_Dlect_E1_Remp_Med', 'API_Nbr_Rejet_Dlect_E2_Remp_Med',  # read defect
                 'API_Nbr_Rejet_Dpd_E1_Remp_Med', 'API_Nbr_Rejet_Dpd_E2_Remp_Med',  # dose presence defect
                 'API_Nbr_Rejet_Dps_E1_Remp_Med', 'API_Nbr_Rejet_Dps_E2_Remp_Med']

    counter_values = get_counters(start=start, end=end, type_mags=type_mags, all_variables=variables)

    weeks = list(map(lambda r: r['date'], counter_values))

    return {
        'counters': [
            {
                'counter': 'bagger_e1',
                'value': get_counter_sum(counter_values=counter_values,
                                         variable='cut_e1_defect_total', max_threshold=0),
                'values': [
                    {
                        'counter': 'cut_reject_read',
                        'value': get_counter_sum(counter_values=counter_values,
                                                 variable='cut_e1_defect_read', max_threshold=0)
                    },
                    {
                        'counter': 'cut_reject_presence',
                        'value': get_counter_sum(counter_values=counter_values,
                                                 variable='cut_e1_defect_presence', max_threshold=0)
                    },
                    {
                        'counter': 'picking_reject_read',
                        'value': get_counter_sum(counter_values=counter_values,
                                                 variable='picking_e1_defect_read', max_threshold=0)
                    },
                    {
                        'counter': 'picking_reject_presence',
                        'value': get_counter_sum(counter_values=counter_values,
                                                 variable='picking_e1_defect_presence', max_threshold=0)
                    },

                ]
            },
            {
                'counter': 'bagger_e2',
                'value': get_counter_sum(counter_values=counter_values,
                                         variable='cut_e2_defect_total', max_threshold=0),
                'values': [
                    {
                        'counter': 'reject_read',
                        'value': get_counter_sum(counter_values=counter_values,
                                                 variable='cut_e2_defect_read', max_threshold=0)
                    },
                    {
                        'counter': 'reject_presence',
                        'value': get_counter_sum(counter_values=counter_values,
                                                 variable='cut_e2_defect_presence', max_threshold=0)
                    },
                    {
                        'counter': 'picking_reject_read',
                        'value': get_counter_sum(counter_values=counter_values,
                                                 variable='picking_e2_defect_read', max_threshold=0)
                    },
                    {
                        'counter': 'picking_reject_presence',
                        'value': get_counter_sum(counter_values=counter_values,
                                                 variable='picking_e2_defect_presence', max_threshold=0)
                    },
                ]
            },
        ],
        'data':  get_trends(val_counts=[], val_weeks=weeks)
    }


def get_cut_counter_v2(type_mags, start, end):
    req = (CompteurJour.select(fn.YEAR(CompteurJour.chrono).alias('year'),
                               fn.WEEKOFYEAR(CompteurJour.chrono).alias('week'),
                               fn.SUM(CompteurJour.coupe).alias('cut'),
                               fn.SUM(CompteurJour.boitePassCP).alias('cut_stock'),
                               fn.SUM(CompteurJour.boiteRejetDpdE1).alias('e1_defect_presence'),
                               fn.SUM(CompteurJour.boiteRejetDlectE1).alias('e1_defect_read'),
                               fn.SUM(CompteurJour.boiteRejetDlectE1 + CompteurJour.boiteRejetDpdE1)
                               .alias('e1_defect_total'),
                               fn.SUM(CompteurJour.boiteRejetDpdE2).alias('e2_defect_presence'),
                               fn.SUM(CompteurJour.boiteRejetDlectE2).alias('e2_defect_read'),
                               fn.SUM(CompteurJour.boiteRejetDlectE2 + CompteurJour.boiteRejetDpdE2)
                               .alias('e2_defect_total'),)
           .where((CompteurJour.poste << type_mags) &
                  (CompteurJour.chrono >= start) &
                  (CompteurJour.chrono < end))
           .group_by(fn.WEEKOFYEAR(CompteurJour.chrono),
                     fn.YEAR(CompteurJour.chrono).alias('year'))
           .order_by(CompteurJour.chrono.asc())
           )

    cut_counters = list(req.objects())

    if len(cut_counters) > 0:
        weeks = list(map(lambda r: {'num': r.week, 'year': r.year}, cut_counters))
        cuts = list(map(lambda r: int(r.cut), cut_counters))
        cut_stocks = list(map(lambda r: int(r.cut_stock), cut_counters))
        rejets = list(map(lambda r: int(r.cut) - int(r.cut_stock), cut_counters))
        ratio_rejects = list(
            map(lambda r: round(((int(r.cut) - int(r.cut_stock)) / int(r.cut)) * 100, 2) if int(r.cut) > 0 else 0,
                cut_counters))

        e1_defect_presence = list(map(lambda r: int(r.e1_defect_presence), cut_counters))
        e1_defect_read = list(map(lambda r: int(r.e1_defect_read), cut_counters))
        e1_defect_total = list(map(lambda r: int(r.e1_defect_total), cut_counters))

        e2_defect_presence = list(map(lambda r: int(r.e2_defect_presence), cut_counters))
        e2_defect_read = list(map(lambda r: int(r.e2_defect_read), cut_counters))
        e2_defect_total = list(map(lambda r: int(r.e2_defect_total), cut_counters))

        trends_cut_stock = get_trends(val_counts=cut_stocks, val_weeks=weeks)
        trends_cut_stock['sum'] = sum(cut_stocks)
        trends_cut_stock['mean'] = round(np.mean(np.array(cut_stocks)), 2) if len(cut_stocks) > 0 else 0

        trends_cut = get_trends(val_counts=cuts, val_weeks=weeks)
        trends_cut['sum'] = sum(cuts)
        trends_cut['mean'] = round(np.mean(np.array(cuts)), 2) if len(cuts) > 0 else 0

        trends_rejet = get_trends(val_counts=rejets, val_weeks=weeks)
        trends_rejet['sum'] = sum(rejets)
        trends_rejet['mean'] = round(np.mean(np.array(rejets)), 2) if len(rejets) > 0 else 0

        ####################################################
        trends_e1_defect_total = get_trends(val_counts=e1_defect_total, val_weeks=weeks)
        trends_e1_defect_total['sum'] = sum(e1_defect_total)
        trends_e1_defect_total['mean'] = round(np.mean(np.array(e1_defect_total)), 2) if len(e1_defect_total) > 0 else 0

        trends_e1_defect_presence_total = get_trends(val_counts=e1_defect_presence, val_weeks=weeks)
        trends_e1_defect_presence_total['sum'] = sum(e1_defect_presence)
        trends_e1_defect_presence_total['mean'] = round(np.mean(np.array(e1_defect_presence)), 2) \
            if len(e1_defect_presence) > 0 else 0

        trends_e1_defect_read_total = get_trends(val_counts=e1_defect_read, val_weeks=weeks)
        trends_e1_defect_read_total['sum'] = sum(e1_defect_read)
        trends_e1_defect_read_total['mean'] = round(np.mean(np.array(e1_defect_read)), 2) \
            if len(e1_defect_read) > 0 else 0

        ####################################################
        trends_e2_defect_total = get_trends(val_counts=e2_defect_total, val_weeks=weeks)
        trends_e2_defect_total['sum'] = sum(e2_defect_total)
        trends_e2_defect_total['mean'] = round(np.mean(np.array(e2_defect_total)), 2) \
            if len(e2_defect_total) > 0 else 0

        trends_e2_defect_presence_total = get_trends(val_counts=e2_defect_presence, val_weeks=weeks)
        trends_e2_defect_presence_total['sum'] = sum(e2_defect_presence)
        trends_e2_defect_presence_total['mean'] = round(np.mean(np.array(e2_defect_presence)), 2) \
            if len(e2_defect_presence) > 0 else 0

        trends_e2_defect_read_total = get_trends(val_counts=e2_defect_read, val_weeks=weeks)
        trends_e2_defect_read_total['sum'] = sum(e2_defect_read)
        trends_e2_defect_read_total['mean'] = round(np.mean(np.array(e2_defect_read)), 2) \
            if len(e2_defect_read) > 0 else 0

        return {
            'counters': [

                {
                    'counter': 'cut_number',
                    'value': trends_cut
                },
                {
                    'counter': 'cut_stock',
                    'value': trends_cut_stock
                },
                {
                    'counter': 'rejet_number',
                    'value': trends_rejet
                },
                {
                    'counter': 'bagger_e1',
                    'value': trends_e1_defect_total,
                    'values': [
                        {
                            'counter': 'reject_read',
                            'value': trends_e1_defect_read_total
                        },
                        {
                            'counter': 'reject_presence',
                            'value': trends_e1_defect_presence_total
                        }
                    ]
                },
                {
                    'counter': 'bagger_e2',
                    'value': trends_e2_defect_total,
                    'values': [
                        {
                            'counter': 'reject_read',
                            'value': trends_e2_defect_read_total
                        },
                        {
                            'counter': 'reject_presence',
                            'value': trends_e2_defect_presence_total
                        }
                    ]
                }
            ],
            'data': get_trends(val_counts=ratio_rejects, val_weeks=weeks)
        }

    else:
        return {}


def get_stock_direct(type_mag, start, end):
    req = (Historique.select(fn.WEEKOFYEAR(Historique.chrono).alias('week'),
                             fn.SUM(Historique.quantite_mouvement).alias('qty'))
           .join(Magasin, on=Historique.adresse.startswith(Magasin.mag))
           .where((Magasin.type_mag == type_mag) &
                  (Historique.type_mouvement == HistoryType.Directe.value) &
                  (Historique.chrono >= start) &
                  (Historique.chrono <= end))
           .group_by(fn.WEEKOFYEAR(Historique.chrono)))

    cuts = list(req.objects())
    if len(cuts) > 0:
        weeks = list(map(lambda c: c.week, cuts))
        values = list(map(lambda c: int(c.qty), cuts))
        trends = get_trends(val_counts=values, val_weeks=weeks)
        trends['mean'] = round(np.mean(np.array(values)), 2) if len(values) > 0 else 0
        trends['sum'] = sum(values)
        return trends
    else:
        return None


def get_stock_number(type_mag, start, end):
    req = (HistoriqueCoupe.select(fn.WEEKOFYEAR(HistoriqueCoupe.chrono).alias('week'),
                                  fn.SUM(HistoriqueCoupe.quantite_mouvement).alias('qty'))
           .join(Magasin, on=Magasin.mag == HistoriqueCoupe.magasin)
           .where((Magasin.type_mag == type_mag) &
                  (HistoriqueCoupe.chrono >= start) &
                  (HistoriqueCoupe.chrono <= end))
           .group_by(fn.WEEKOFYEAR(HistoriqueCoupe.chrono)))

    cuts = list(req.objects())
    if len(cuts) > 0:
        weeks = list(map(lambda c: c.week, cuts))
        values = list(map(lambda c: int(c.qty), cuts))
        trends = get_trends(val_counts=values, val_weeks=weeks)
        trends['mean'] = round(np.mean(np.array(values)), 2) if len(values) > 0 else 0
        trends['sum'] = sum(values)
        return trends
    else:
        return None


def get_cut_reject_mean(type_mag, start, end):
    equipments = Magasin.select(Magasin.type_machine, Magasin.type_mag).where(Magasin.type_mag == type_mag)

    # Cut rejet ratio can't be calculated with ACCED V3 equipement
    acced_v2s = list(map(lambda r: r.type_mag,
                         filter(lambda e: e.type_machine != PickingMachineType.Acced_Dose_Unit.value, equipments)))

    req = (CompteurJour.select(
        fn.IFNULL(fn.SUM(CompteurJour.coupe), 0).alias('cut'),
        fn.IFNULL(fn.SUM(CompteurJour.boitePassCP), 0).alias('cut_stock'))
           .where((CompteurJour.poste << acced_v2s) &
                  (CompteurJour.chrono >= start) &
                  (CompteurJour.chrono < end))).objects()

    cuts = list(map(lambda r: int(r.cut), req))
    rejets = list(map(lambda r: int(r.cut) - int(r.cut_stock), req))

    total_cuts = sum(cuts)
    total_rejets = sum(rejets)

    reject_sum = round((total_rejets / total_cuts) * 100, 2) if total_cuts > 0 else 0

    state = State.UNKNOW.value if total_cuts == 0 or math.isnan(reject_sum) else State.KO.value
    threshold = _get_config(property=CONTRAT_CUT_REJECT_PERCENTAGE)
    if total_cuts > 0 and reject_sum <= threshold:
        state = State.OK.value
    elif total_cuts > 0 and reject_sum <= (1.1 * threshold):
        state = State.WARNING.value
    return {
        'mean': reject_sum,
        'state': state,
        'threshold': {
            'max': threshold,
            'warning': round(1.1 * threshold, 2),
            'unit': 'unit.percentage'
        }
    } if state != State.UNKNOW.value else None
