from apps.system.models import Dept, User
from apps.sam.models import OrderItem
from apps.mtm.models import Route, Material, Mgroup
from rest_framework.exceptions import ParseError
from apps.pm.models import Mtask, Utask
from django.db.models.query import QuerySet
from django.utils import timezone
from datetime import date, timedelta
import math
from typing import List, Union


class PmService:

    @classmethod
    def make_utasks_from_orderitems(cls, user, orderitemIds: List[str], start_date: date, end_date: date, belong_dept: Union[Dept, None]):
        start_date_str = start_date.strftime('%Y%m%d')
        if start_date >= end_date:
            raise ParseError('开始时间不可大于结束时间')
        orderitems = OrderItem.objects.filter(pk__in=orderitemIds)
        if orderitems.exclude(utask=None).exists():
            raise ParseError('存在订单项已排任务!')
        rdict = {}
        for item in orderitems:
            productId = item.material.id
            if productId not in rdict:
                rdict[productId] = [item.material, item.count, [item.id]]
            else:
                rdict[productId][1] = rdict[productId][1] + item.count
                rdict[productId][2].append(item.id)
        # 生成大任务
        make_list = []
        for k, v in rdict.items():
            xproduct, xcount, orderitemsId = v
            if xproduct.is_assemb:
                for key in xproduct.components:
                    make_list.append([Material.objects.get(
                        id=key), xcount*xproduct.components[key], orderitemsId])
            else:
                make_list = [[xproduct, xcount, orderitemsId]]
        for i in make_list:
            material, count, orderitemsId = i
            utask = Utask.objects.create(
                number=f'{material.number}_{start_date_str}',
                material=material,
                count=count,
                start_date=start_date,
                end_date=end_date,
                create_by=user,
                update_by=user,
                belong_dept=belong_dept
            )
            OrderItem.objects.filter(id__in=orderitemIds).update(utask=utask)

    @classmethod
    def schedue_mtasks(cls, user, utask: Utask):
        """
        从大任务自动排产出小任务
        """
        # from apps.wpm.services import make_sflogs
        if utask.state != Utask.UTASK_CREATED:
            raise ParseError('任务状态异常')
        utask.state = Utask.UTASK_DECOMPOSE
        utask.save()
        number, product, count, start_date, end_date = utask.number, utask.material, utask.count, utask.start_date, utask.end_date
        # 计算相差天数
        rela_days = (end_date - start_date).days + 1
        if utask.mgroup:  # 如果存在指定的mgroup则直接排产
            if rela_days >= 1:
                task_count_day = math.ceil(count/rela_days)
                for i in range(rela_days):
                    task_date = start_date + timedelta(days=i)
                    Mtask.objects.create(**{
                        'type': utask.type,
                        'number': f'{number}_{i+1}',
                        'material_out': utask.material,
                        'material_in': utask.material_in,
                        'mgroup': utask.mgroup,
                        'count': task_count_day,
                        'start_date': task_date,
                        'end_date':  task_date,
                        'utask': utask,
                        'create_by': user,
                        'update_by': user,
                        'is_count_utask': True
                    })
                    # 先撤销有需要再加
                    # make_sflogs(mgroup=utask.mgroup,
                    #             start_date=task_date, end_date=task_date, create_by=user)
        else:
            # 获取产品的加工路线
            rqs = Route.get_routes(product)
            if not rqs.exists():
                raise ParseError('未配置工艺路线')
            # 创建小任务
            for ind, val in enumerate(rqs):
                if val.material_out:
                    halfgood = val.material_out
                else:
                    raise ParseError(f'第{ind+1}步-无输出物料')
                if val.material_in:
                    material_in = val.material_in
                elif ind > 0:
                    material_in = rqs[ind-1].material_out
                if val.is_autotask:
                    # 找到工段
                    mgroups = Mgroup.objects.filter(process=val.process)
                    mgroups_count = mgroups.count()
                    if mgroups_count == 1:
                        mgroup = mgroups.first()
                    elif mgroups_count == 0:
                        raise ParseError(f'第{ind+1}步-工段不存在!')
                    else:  # 后面可能会指定车间
                        raise ParseError(f'第{ind+1}步-工段存在多个!')
                    task_count = count
                    if val.out_rate:
                        if val.out_rate > 1:
                            task_count = math.ceil(
                                count / (val.out_rate/100))
                        else:
                            task_count = math.ceil(count / val.out_rate)
                    task_count_day = math.ceil(task_count/rela_days)
                    if rela_days >= 1:
                        for i in range(rela_days):
                            task_date = start_date + timedelta(days=i)
                            Mtask.objects.create(**{
                                'number': f'{number}_r{ind+1}_{i+1}',
                                'type': utask.type,
                                'material_out': halfgood,
                                'material_in': material_in,
                                'mgroup': mgroup,
                                'count': task_count_day,
                                'start_date': task_date,
                                'end_date':  task_date,
                                'utask': utask,
                                'create_by': user,
                                'update_by': user,
                                'is_count_utask': val.is_count_utask
                            })
                            # 先撤销，有需要再加
                            # make_sflogs(mgroup=mgroup,
                            #             start_date=task_date, end_date=task_date, create_by=user)

    @classmethod
    def check_orderitems(cls, orderitems: QuerySet[OrderItem]):
        """
        废弃
        校验orderitems并返回整合后的字典以productId为key, [product, count, end_date, orderitems] 为value
        """
        rdict = {}
        if orderitems.exclude(mtask=None).exists():
            raise ParseError('存在订单项已排任务!')
        for item in orderitems:
            productId = item.material.id
            if productId not in rdict:
                rdict[productId] = [item.material, item.count,
                                    item.order.delivery_date, [item.id]]
            else:
                order_date = item.order.delivery_date
                if rdict[productId][2] > order_date:
                    rdict[productId][2] = order_date
                rdict[productId][1] = rdict[productId][1] + item.count
                rdict[productId][3].append(item.id)
        return rdict

    @classmethod
    def schedue_from_orderitems(cls, user, orderitemIds: List[str], start_date: date, end_date: date = None):
        """
        废弃
        从多个订单明细自动排产
        """
        orderitems = OrderItem.objects.filter(pk__in=orderitemIds)
        rdict = cls.check_orderitems(orderitems)
        start_date_str = start_date.strftime('%Y%m%d')
        for k, v in rdict.items():
            xproduct, xcount, end_date_cal, orderitemId_list = v
            if end_date is None:
                end_date = end_date_cal
            if start_date >= end_date:
                raise ParseError('开始时间不可大于结束时间')
            # 计算相差天数
            rela_days = (end_date - start_date).days + 1
            make_list = []
            if xproduct.is_assemb:
                for key in xproduct.components:
                    make_list.append([Material.objects.get(
                        id=key), xcount*xproduct.components[key], end_date, orderitemId_list])
            else:
                make_list = [[xproduct, xcount, end_date, orderitemId_list]]
            for i in make_list:
                product, count, end_date, orderitemId_list = i
                # 获取每个产品的加工路线
                rqs = Route.get_routes(product)
                lasttask = None
                # 创建父任务
                for ind, val in enumerate(rqs):
                    if val.material_out:
                        halfgood = val.material_out
                    else:
                        raise ParseError(f'第{ind+1}步-无输出物料')
                    if val.material_in:
                        material_in = val.material_in
                    elif ind > 0:
                        material_in = rqs[ind-1].material_out
                    if val.is_autotask:
                        # 找到工段
                        mgroups = Mgroup.objects.filter(process=val.process)
                        mgroups_count = mgroups.count()
                        if mgroups_count == 1:
                            mgroup = mgroups.first()
                        elif mgroups_count == 0:
                            raise ParseError(f'第{ind+1}步-工段不存在!')
                        else:  # 后面可能会指定车间
                            raise ParseError(f'第{ind+1}步-工段存在多个!')
                        task_count = count
                        if val.out_rate:
                            if val.out_rate > 1:
                                task_count = math.ceil(
                                    count / (val.out_rate/100))
                            else:
                                task_count = math.ceil(count / val.out_rate)
                        fmtask = Mtask.objects.create(**{
                            'number': f'{product.number}_{start_date_str}_r{ind+1}',
                            'material_out': halfgood,
                            'material_in': material_in,
                            'mgroup': mgroup,
                            'count':  task_count,
                            'start_date': start_date,
                            'end_date': end_date,
                            'create_by': user,
                            'update_by': user,
                        })
                        task_count_day = math.ceil(task_count/rela_days)
                        if rela_days > 1:
                            for i in range(rela_days):
                                task_date = start_date + timedelta(days=i)
                                Mtask.objects.create(**{
                                    'number': f'{fmtask.number}_{i+1}',
                                    'material_out': halfgood,
                                    'material_in': material_in,
                                    'mgroup': mgroup,
                                    'count': task_count_day,
                                    'start_date': task_date,
                                    'end_date':  task_date,
                                    'parent': fmtask,
                                    'create_by': user,
                                    'update_by': user
                                })
                        lasttask = fmtask
                OrderItem.objects.filter(
                    id__in=orderitemId_list).update(mtask=lasttask)
        from apps.sam.tasks import change_order_state_when_schedue
        change_order_state_when_schedue(orderitemIds)

    @classmethod
    def mtask_submit(cls, mtask: Mtask, user: User):
        """
        锁定生产任务
        """
        from apps.wpm.models import Mlog
        from apps.wpm.services import mlog_submit, update_mtask
        now = timezone.now()
        if mtask.state == Mtask.MTASK_ASSGINED:
            mlogs = Mlog.objects.filter(mtask=mtask)
            # if ignore_nologs is False and mlogs.count() == 0:
            #     raise ParseError(f'{mtask.mgroup.name}_未填写日志')
            for mlog in mlogs:
                mlog_submit(mlog, user, now)
            update_mtask(mtask)
            mtask.state = Mtask.MTASK_SUBMIT
            mtask.submit_time = now
            mtask.submit_user = user
            mtask.save()
        else:
            raise ParseError('该任务状态不可提交')
