import datetime
import numpy as np
from ninja_extra import api_controller, ControllerBase, route
from ninja import Query
from ninja_jwt.authentication import JWTAuth
from ninja_extra.permissions import IsAuthenticated
from ninja.pagination import paginate
from apps.dict.models import DictItem
from utils.chen_pagination import MyPagination
from django.db import transaction
from typing import List, Optional
from utils.chen_response import ChenResponse
from utils.codes import HTTP_INDEX_ERROR
from django.shortcuts import get_object_or_404
from apps.project.models import Case, Problem, Project, TestDemand
from apps.project.schemas.problem import (
    DeleteSchema,
    ProblemModelOutSchema,
    ProblemFilterSchema,
    ProblemCreateOutSchema,
    ProblemCreateInputSchema,
    ProblemSingleInputSchema,
    ProblemUpdateInputSchema,
    ProblemFilterWithHangSchema
)
from utils.util import get_str_abbr
from utils.smallTools.interfaceTools import conditionNoneToBlank

@api_controller("/project", auth=JWTAuth(), permissions=[IsAuthenticated], tags=['问题单系列'])
class ProblemController(ControllerBase):
    @route.get("/getProblemList", response=List[ProblemModelOutSchema], exclude_none=True,
               url_name="problem-list")
    @transaction.atomic
    @paginate(MyPagination)
    def get_problem_list(self, data: ProblemFilterSchema = Query(...)):
        project_id = data.project_id
        conditionNoneToBlank(data)
        case_key = "".join([data.round_id, '-', data.dut_id, '-', data.design_id, '-', data.test_id, '-', data.case_id])
        # 先查询出对应的case
        case_obj = Case.objects.filter(project_id=project_id, key=case_key).first()
        # 然后进行过滤
        qs = case_obj.caseField.filter(project__id=data.project_id,
                                       ident__icontains=data.ident,
                                       name__icontains=data.name,
                                       status__icontains=data.status,
                                       type__icontains=data.type,
                                       grade__icontains=data.grade,
                                       operation__icontains=data.operation,
                                       postPerson__icontains=data.postPerson,
                                       ).order_by("id")

        # 遍历通过代码不通过ORM查询闭环方式-巧妙使用numpy中array对象的in方法来判断
        closeMethod1 = self.context.request.GET.get("closeMethod[0]")
        closeMethod2 = self.context.request.GET.get("closeMethod[1]")
        query_add_closeMethod = []
        for query in qs:
            arr = np.array(query.closeMethod)
            if closeMethod1 is None and closeMethod2 is None:
                query_add_closeMethod.append(query)
                continue
            if closeMethod1 in arr:
                query_add_closeMethod.append(query)
                continue
            if closeMethod2 in arr:
                query_add_closeMethod.append(query)
                continue
        return query_add_closeMethod

    # 搜索全部问题单/或查询轮次下的问题单
    @route.get('/problem/searchAllProblem', response=List[ProblemModelOutSchema], exclude_none=True,
               url_name="problem-allList")
    @transaction.atomic
    @paginate(MyPagination)
    def get_all_problems(self, round_key: Optional[str] = False, data: ProblemFilterWithHangSchema = Query(...)):
        project_id = data.project_id
        for attr, value in data.__dict__.items():
            if getattr(data, attr) is None:
                setattr(data, attr, '')
        # 先查询当前项目
        qs = Problem.objects.filter(project__id=data.project_id,
                                    ident__icontains=data.ident,
                                    name__icontains=data.name,
                                    status__icontains=data.status,
                                    type__icontains=data.type,
                                    grade__icontains=data.grade,
                                    operation__icontains=data.operation,
                                    postPerson__icontains=data.postPerson,
                                    ).order_by("id")
        closeMethod1 = self.context.request.GET.get("closeMethod[0]")
        closeMethod2 = self.context.request.GET.get("closeMethod[1]")
        query_final = []
        for query in qs:
            arr = np.array(query.closeMethod)
            if closeMethod1 is None and closeMethod2 is None:
                query_final.append(query)
                continue
            if closeMethod1 in arr:
                query_final.append(query)
                continue
            if closeMethod2 in arr:
                query_final.append(query)
                continue
        # 遍历所有problem，查询是有否有关联case，如果有则设置hang为True，否则False
        hang = True
        # 过滤不是该轮次的问题单对象列表
        deleted_problem_list = []
        for pro_obj in query_final:
            case_exists = pro_obj.case.exists()
            if not case_exists:
                setattr(pro_obj, "hang", hang)
            # 如果有关联用例还要看是否是查询轮次的问题单，过滤出去
            elif case_exists:
                hang = False
                setattr(pro_obj, "hang", hang)
                hang = True
                if round_key:
                    if not pro_obj.case.filter(round__key=round_key).exists():
                        deleted_problem_list.append(pro_obj)
        for dq in deleted_problem_list:
            query_final.remove(dq)
        # !!!如果是轮次查询则返回轮次，如果是关联查询则查询关联当前的case
        if round_key:
            pass
        else:
            case_obj = Case.objects.filter(project_id=project_id, key=data.key).first()
            if case_obj:
                for pro_obj in query_final:
                    # 查询关联的case
                    related = False
                    for re_case in pro_obj.case.all():
                        if case_obj.id == re_case.id:
                            related = True
                    setattr(pro_obj, "related", related)
        # 过滤查询悬挂逻辑
        query_last = []
        if data.hang == '3' or data.hang == '':  # 疑问：为什么会是空字符串
            query_last = query_final
        if data.hang == '2':
            for pp in query_final:
                if not pp.hang:
                    query_last.append(pp)
        if data.hang == '1':
            for pp in query_final:
                if pp.hang is True:
                    query_last.append(pp)
        return query_last

    @staticmethod
    def __date_solve(payload: ProblemCreateInputSchema):
        """辅助函数：
        1.设置问题单时间，而不是默认进入时间，传入schema对象，返回schema对象，只对里面时间进行处理
        """
        project_obj = get_object_or_404(Project, id=payload.project_id)
        round_obj = project_obj.pField.filter(key=payload.round_key).first()
        if round_obj:
            if payload.postDate is None:
                payload.postDate = round_obj.beginTime + datetime.timedelta(days=1)
            if payload.designDate is None:
                payload.designDate = round_obj.beginTime + datetime.timedelta(days=2)
        return payload

    # 添加问题单
    @route.post("/problem/save", response=ProblemCreateOutSchema, url_name="problem-create")
    @transaction.atomic
    def create_case_demand(self, payload: ProblemCreateInputSchema):
        payload = self.__date_solve(payload)
        asert_dict = payload.dict()
        project_id = payload.project_id
        # 查询problem的总数
        problem_count = Problem.objects.filter(project_id=project_id).count()
        # 查询当前各个前面节点的instance
        pop_keys: List[str] = ["round_key", "dut_key", "design_key", "test_key", "case_key"]
        for pkey in pop_keys:
            asert_dict.pop(pkey)
        # 处理问题单标识PT_项目ident_数目依次增加
        asert_dict["ident"] = str(problem_count + 1)
        qs = Problem.objects.create(**asert_dict)
        # 处理时间
        qs.postDate = payload.postDate
        qs.designDate = payload.designDate
        qs.save()
        # 分两个逻辑处理，无关联创建问题单/case下面创建问题单
        if payload.case_key:
            # 构造case_key
            case_key = "".join(
                [payload.round_key, "-", payload.dut_key, '-', payload.design_key, '-', payload.test_key, '-',
                 payload.case_key])
            # 查询出所属的case
            case_obj = Case.objects.filter(project_id=project_id, key=case_key).first()
            qs.case.add(case_obj)
            qs.save()
        # 对problem的ident排序
        self.reset_problem_ident(project_id)
        return qs

    # 更新问题单
    @route.put("/problem/update/{id}", response=ProblemCreateOutSchema, url_name="problem-update")
    @transaction.atomic
    def update_problem(self, id: int, payload: ProblemCreateInputSchema):
        # 查到当前
        problem_qs = Problem.objects.get(id=id)
        for attr, value in payload.dict().items():
            setattr(problem_qs, attr, value)
        problem_qs.save()
        return ChenResponse(message="问题单更新成功")

    # 弹窗的-更新问题单
    @route.put("/problem/modalupdate/{id}", response=ProblemCreateOutSchema, url_name="problem-update")
    @transaction.atomic
    def update_modal_problem(self, id: int, payload: ProblemUpdateInputSchema):
        # 查到当前
        problem_qs = Problem.objects.get(id=id)
        for attr, value in payload.dict().items():
            setattr(problem_qs, attr, value)
        problem_qs.save()
        return ChenResponse(message="问题单更新成功")

    # 删除问题单
    @route.delete("/problem/delete", url_name="problem-delete")
    @transaction.atomic
    def delete_problem(self, data: DeleteSchema):
        # 1.查询出所有被删除id
        problems = Problem.objects.filter(id__in=data.ids)
        if not problems.exists():
            return ChenResponse(status=500, code=HTTP_INDEX_ERROR, message='您未选取删除内容')
        # 4.查询出当前项目id
        project_id = None
        # 2.循环该取出problem
        for problem in problems:
            project_id = problem.project_id
            # 3. 直接删除case关联，然后删除自己
            problem.case.clear()
            problem.delete()
        # 4.找到对应项目的所有problems进行排序
        if project_id is not None:
            self.reset_problem_ident(project_id)
        return ChenResponse(message="问题单删除成功！")

    # 根据问题单id，返回关联的用例s
    @route.get('/getRelativeCases', url_name='problem-relative-case')
    @transaction.atomic
    def get_relative_cases(self, id: int):
        problem_qs = get_object_or_404(Problem, id=id)
        cases = problem_qs.case.all()
        case_list = []
        for case in cases:
            case_dict = {
                'id': case.id,
                'case': case.title,
                'round': case.round.title,
                'dut': case.dut.title,
                'design': case.design.title,
            }
            demand = case.test
            case_dict['demand'] = demand.title
            demand_testType_showtitle = get_str_abbr(demand.testType, 'testType')
            case_dict['demand_ident'] = "-".join(['XQ', demand_testType_showtitle, demand.ident])
            case_list.append(case_dict)
        return case_list

    # 单独显示问题单页面需要数据
    @route.get("/getSingleProblem", url_name="problem-single", response=ProblemCreateOutSchema)
    @transaction.atomic
    def search_single_problem(self, data: ProblemSingleInputSchema = Query(...)):
        key_string = "".join(
            [data.round_id, '-', data.dut_id, '-', data.design_id, '-', data.test_id, '-', data.case_id, '-',
             data.problem_id])
        qs = Problem.objects.get(project__id=data.project_id, key=key_string)
        return qs

    # 让测试用例关联/取消问题单
    @route.get('/problem/relateProblem', exclude_none=True, url_name="problem-allList")
    @transaction.atomic
    def relate_problem(self, case_key: str, problem_id: int, val: bool):  # val是将要变成的值
        # 先判断将要变成的值是否为True
        problem_obj: Problem = Problem.objects.filter(id=problem_id).first()
        project_id = problem_obj.project_id  # 根据问题单反推项目id
        case_obj = Case.objects.filter(project_id=project_id, key=case_key).first()
        flag = False  # 是否操作成功的标志
        if val:
            # 这分支是进行关联操作
            # 5月15日新需求：一个用例只能关联一个问题单
            if case_obj.caseField.count() >= 1:
                return ChenResponse(code=400, status=400, message='请注意：一个用例只允许关联一个问题单',
                                    data={'isOK': False})
            case_obj.caseField.add(problem_obj)
            flag = True
        else:
            case_obj.caseField.remove(problem_obj)
            flag = True
        # 排序ident
        if project_id:
            self.reset_problem_ident(project_id)
        return ChenResponse(code=200, status=200, message='关联或取消关联成功...',
                            data={'isOK': flag, 'key': case_obj.key})

    # 类方法：操作后对problem的ident排序：先基于轮次排序，然后基于测试项类型排序
    @classmethod
    def reset_problem_ident(cls, project_id: int):
        project_obj: Project = get_object_or_404(Project, id=project_id)
        # 获取所有问题单
        problem_qs = project_obj.projField.prefetch_related('case').prefetch_related('case__test')
        # 待排序列表
        not_sorted_problems = []
        # 处理为List[Dict]以便后续排序修改ident
        for problem in problem_qs:
            cases = problem.case.all()
            if len(cases):
                # 如果关联了case
                belong_demand: TestDemand = cases[0].test
                # 找到对应测试类型
                test_type = DictItem.objects.get(dict__code='testType', key=belong_demand.testType)
                # 找到测试类型的sort/找到轮次key
                not_sorted_problems.append({
                    'problem': problem,
                    'sort': test_type.sort,
                    'round_key': belong_demand.round.key,
                })
            else:
                # 如果没有关联case
                not_sorted_problems.append({
                    'problem': problem,
                    'sort': 1024,
                    'round_key': 1024,
                })
        # 排序后修改ident
        round_sorted_problems = sorted(not_sorted_problems, key=lambda x: int(x['round_key']))
        last_sorted_problems = sorted(round_sorted_problems, key=lambda x: int(x['sort']))
        # 根据排序修改problem的ident
        for index, problem_dict in enumerate(last_sorted_problems):
            problem_dict['problem'].ident = str(index + 1)
            problem_dict['problem'].save()
