import datetime

from django.core.cache import cache
from django.db.models import Sum
from django.utils import timezone
from typing import Union
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned

from rest_framework.exceptions import ParseError

from apps.system.models import User
from apps.inm.models import MIO, MIOItem, MIOItemA
from apps.pm.models import Mtask
from apps.mtm.models import Mgroup, Shift, Material, Route

from .models import SfLog, SfLogExp, WMaterial, Mlog, Mlogb, Handover


def get_sflog(mgroup: Mgroup, happen_time: datetime):
    sflog = SfLog.objects.filter(
        start_time__lt=happen_time, end_time__gte=happen_time, mgroup=mgroup).first()
    if sflog is None:  # 需要创建值班记录
        make_sflogs(mgroup=mgroup, start_date=(
            happen_time-datetime.timedelta(days=1)).date(), end_date=happen_time.date())
        sflog = SfLog.objects.filter(
            start_time__lt=happen_time, end_time__gte=happen_time, mgroup=mgroup).first()
    return sflog


def make_sflogs(mgroup: Mgroup, start_date: datetime.date, end_date: datetime.date, create_by=None):
    shift_rule = mgroup.shift_rule
    shifts = Shift.objects.filter(rule=shift_rule)  # 根据排班规则制定排班记录
    for shift in shifts:
        start_time_o = shift.start_time_o
        end_time_o = shift.end_time_o
        current_date = start_date
        while current_date <= end_date:
            start_time = datetime.datetime.combine(current_date, start_time_o)
            end_time = datetime.datetime.combine(current_date, end_time_o)
            if start_time > end_time:
                start_time -= datetime.timedelta(days=1)
            duration = end_time - start_time
            total_hour = duration.total_seconds() / 3600
            SfLog.objects.get_or_create(mgroup=mgroup, shift=shift, start_time=start_time, defaults={
                "mgroup": mgroup,
                "shift": shift,
                "work_date": current_date,
                "start_time": start_time,
                "end_time": end_time,
                "total_hour_now": total_hour,
                "total_hour": total_hour,
                "create_by": create_by
            })
            current_date = current_date + datetime.timedelta(days=1)


def get_pcoal_heat(year_s: int, month_s: int, day_s: int):
    """
    获取煤粉热值
    只有回转窑需要录入煤粉热值
    """
    key = f'pgoal_val_{year_s}_{month_s}_{day_s}'
    pcoal_heat = cache.get(key, None)
    if pcoal_heat is not None:
        return pcoal_heat
    else:
        try:
            qs = SfLog.objects.get(end_time__year=year_s, end_time__month=month_s, end_time__day=day_s,
                                   mgroup__name='回转窑', shift__name='白班')
            if qs.pcoal_heat is None:
                qs.pcoal_heat = 0
                qs.save()
            cache.set(f'pgoal_val_{year_s}_{month_s}_{day_s}', qs.pcoal_heat)
            return qs.pcoal_heat
        except Exception:
            return 0


def do_out(mio: MIO):
    """
    生产领料到车间
    """
    belong_dept = mio.belong_dept
    do_user = mio.do_user
    mioitems = MIOItem.objects.filter(mio=mio)
    for item in mioitems:
        wm, new_create = WMaterial.objects.get_or_create(batch=item.batch, material=item.material,
                                                         belong_dept=belong_dept, defaults={
                                                             "batch": item.batch,
                                                             "material": item.material,
                                                             "count": item.count,
                                                             "create_by": do_user,
                                                             "belong_dept": belong_dept
                                                         })
        if not new_create:
            wm.count = wm.count + item.count
            wm.update_by = do_user
            wm.save()


def do_in(mio: MIO):
    """
    生产入库后更新车间物料
    """
    belong_dept = mio.belong_dept
    do_user = mio.do_user
    mioitems = MIOItem.objects.filter(mio=mio)
    for item in mioitems:
        action_list = []
        mias = MIOItemA.objects.filter(mioitem=item)
        if mias.exists():  # 组合件入库
            mias_list = list(mias.values_list('material', 'batch', 'rate'))
            for i in range(len(mias_list)):
                material, batch, rate = mias_list[i]
                new_count = rate * item.count  # 假设 item.count 存在
                action_list.append([material, batch, new_count])
        else:
            action_list = [[item.material, item.batch, item.count]]
        for al in action_list:
            xmaterial, xbatch, xcount = al
            try:
                wm = WMaterial.objects.get(
                    batch=xbatch, material=xmaterial, belong_dept=belong_dept)
            except ObjectDoesNotExist:
                raise ParseError('车间物料不存在!')
            except MultipleObjectsReturned:
                raise ParseError('存在多行车间物料!')
            new_count = wm.count - xcount
            if new_count > 0:
                wm.count = new_count
                wm.update_by = do_user
                wm.save()
            elif new_count == 0:
                wm.delete()
            else:
                raise ParseError('车间物料不足')


def mlog_submit(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
    """
    生产日志提交后需要执行的操作
    """
    if mlog.submit_time is not None:
        return
    if now is None:
        now = timezone.now()
    if now.date() < mlog.handle_date:
        raise ParseError('不可提交未来的日志')
    belong_dept = mlog.mgroup.belong_dept
    material_out = mlog.material_out
    material_in = mlog.material_in
    if material_in:  # 需要进行车间库存管理
        # 需要判断领用数是否合理
        material_has_qs = WMaterial.objects.filter(
            batch=mlog.batch, material=material_in, belong_dept=belong_dept)
        count_x = material_has_qs.count()
        if count_x == 1:
            material_has = material_has_qs.first()
        elif count_x == 0:
            raise ParseError(
                f'{str(material_in)}-{mlog.batch}-批次库存不存在!')
        else:
            raise ParseError(
                f'{str(material_in)}-{mlog.batch}-存在多个相同批次!')
        if mlog.count_use > material_has.count:
            raise ParseError(
                f'{str(material_in)}-{mlog.batch}-该批次车间库存不足!')
        else:
            material_has.count = material_has.count - mlog.count_use
            if material_has.count == 0:
                material_has.delete()
            else:
                material_has.save()
    if material_out:  # 需要入车间库存
        # 有多个产物的情况
        if material_out.brothers and Mlogb.objects.filter(mlog=mlog).exists():
            for item in Mlogb.objects.filter(mlog=mlog):
                wmaterial, _ = WMaterial.objects.get_or_create(batch=mlog.batch, material=item.material_out, belong_dept=belong_dept, defaults={
                    'batch': mlog.batch, 'material': item.material_out, 'belong_dept': belong_dept
                })
                wmaterial.count = wmaterial.count + item.count_ok
                wmaterial.save()
        else:
            wmaterial, _ = WMaterial.objects.get_or_create(batch=mlog.batch, material=material_out, belong_dept=belong_dept, defaults={
                'batch': mlog.batch, 'material': material_out, 'belong_dept': belong_dept
            })
            wmaterial.count = wmaterial.count + mlog.count_ok
            wmaterial.save()
    mlog.submit_time = now
    mlog.submit_user = user
    mlog.save()


def mlog_revert(mlog: Mlog, user: User, now: Union[datetime.datetime, None]):
    """日志撤回
    """
    if mlog.submit_time is None:
        return
    if now is None:
        now = timezone.now()
    belong_dept = mlog.mgroup.belong_dept
    material_out = mlog.material_out
    material_in = mlog.material_in
    if material_in:
        # 领用数退回
        wmaterial, _ = WMaterial.objects.get_or_create(batch=mlog.batch, material=material_in, belong_dept=belong_dept, defaults={
            'batch': mlog.batch, 'material': material_in, 'belong_dept': belong_dept})
        wmaterial.count = wmaterial.count + mlog.count_use
        wmaterial.save()
    if material_out:  # 产物退回
        # 有多个产物的情况
        if material_out.brothers and Mlogb.objects.filter(mlog=mlog).exists():
            for item in Mlogb.objects.filter(mlog=mlog):
                wmaterial, _ = WMaterial.objects.get_or_create(batch=mlog.batch, material=item.material_out, belong_dept=belong_dept, defaults={
                    'batch': mlog.batch, 'material': item.material_out, 'belong_dept': belong_dept
                })
                wmaterial.count = wmaterial.count - item.count_ok
                if wmaterial.count < 0:
                    raise ParseError('车间库存不足, 产物无法回退')
                elif wmaterial.count == 0:
                    wmaterial.delete()
                else:
                    wmaterial.save()
        else:
            wmaterial, _ = WMaterial.objects.get_or_create(batch=mlog.batch, material=material_out, belong_dept=belong_dept, defaults={
                'batch': mlog.batch, 'material': material_out, 'belong_dept': belong_dept
            })
            wmaterial.count = wmaterial.count - mlog.count_ok
            if wmaterial.count < 0:
                raise ParseError('车间库存不足, 产物无法回退')
            elif wmaterial.count == 0:
                wmaterial.delete()
            else:
                wmaterial.save()
    mlog.submit_time = None
    mlog.submit_user = None
    mlog.save()


def update_mtask(mtask: Mtask):
    from apps.pm.models import Utask
    res = Mlog.objects.filter(mtask=mtask).exclude(submit_time=None).aggregate(sum_count_real=Sum(
        'count_real'), sum_count_ok=Sum('count_ok'), sum_count_notok=Sum('count_notok'))
    mtask.count_real = res['sum_count_real'] if res['sum_count_real'] else 0
    mtask.count_ok = res['sum_count_ok'] if res['sum_count_ok'] else 0
    mtask.count_notok = res['sum_count_notok'] if res['sum_count_notok'] else 0
    mtask.save()
    utask = mtask.utask
    if utask and mtask.is_count_utask:
        res2 = Mtask.objects.filter(utask=utask, mgroup=mtask.mgroup).aggregate(sum_count_real=Sum(
            'count_real'), sum_count_ok=Sum('count_ok'), sum_count_notok=Sum('count_notok'))
        utask.count_real = res2['sum_count_real'] if res2['sum_count_real'] else 0
        utask.count_ok = res2['sum_count_ok'] if res2['sum_count_ok'] else 0
        utask.count_notok = res2['sum_count_notok'] if res2['sum_count_notok'] else 0
        if utask.count_ok > 0 and utask.state == Utask.UTASK_ASSGINED:
            utask.state = Utask.UTASK_WORKING
        if Mtask.objects.filter(utask=utask).exclude(state=Mtask.MTASK_SUBMIT).count() == 0:
            utask.state = Utask.UTASK_SUBMIT
        utask.save()


def handover_submit(handover: Handover, user: User, now: Union[datetime.datetime, None]):
    """
    交接提交后需要执行的操作
    """
    if handover.submit_time is not None:
        return
    now = timezone.now()
    if handover.wm:
        wm = handover.wm
        count_x = wm.count - handover.count
        if count_x < 0:
            raise ParseError('车间库存不足!')
        elif count_x == 0:
            wm.delete()
        else:
            wm.count = count_x
            wm.save()
        wm_to, _ = WMaterial.objects.get_or_create(batch=handover.batch, material=handover.material, belong_dept=handover.send_dept, defaults={
            'batch': handover.batch, 'material': handover.material, 'belong_dept': handover.send_dept
        })
        wm_to.count = wm_to.count + handover.count
        wm_to.save()
    handover.submit_user = user
    handover.submit_time = now
    handover.save()
