# -*- coding: utf-8 -*-

import json
import math
from itertools import groupby
from collections import defaultdict, namedtuple
from datetime import time
from multiprocessing import Pool, Manager

from django.conf import settings
from django.db.models import Q, Max
from django import db
from django.core.management import BaseCommand, CommandError

from gm_types.gaia import TAG_V3_TYPE

from tags.models.tag import TagV3, TagMapOldTag
from talos.models.diary import Diary, DiaryTag, DiaryTagV3
from talos.models.topic import Problem, ProblemTag, ProblemTagV3
from talos.models.tractate import Tractate, TractateTag, TractateTagV3
from qa.models import Question, QuestionTag, QuestionTagV3, Answer, AnswerTag, AnswerTagV3


# 普通标签、一级分类、二级分类、一级症状、二级症状、一级方式、二级方式、三级方式、一级诉求、二级诉求、一级部位、二级部位、药品、仪器、耗材、材料
tag_types = [
    TAG_V3_TYPE.NORMAL,
    TAG_V3_TYPE.FIRST_CLASSIFY,
    TAG_V3_TYPE.SECOND_CLASSIFY,
    TAG_V3_TYPE.FIRST_SYMPTOM,
    TAG_V3_TYPE.SECOND_SYMPTOM,
    TAG_V3_TYPE.MACROSCOPIC_MODE,
    TAG_V3_TYPE.FIRST_BRAND,
    TAG_V3_TYPE.SECOND_BRAND,
    TAG_V3_TYPE.FIRST_APPEAL,
    TAG_V3_TYPE.SECOND_APPEAL,
    TAG_V3_TYPE.FIRST_POSITION,
    TAG_V3_TYPE.SECOND_POSITION,
    TAG_V3_TYPE.DRUG,
    TAG_V3_TYPE.INSTRUMENT,
    TAG_V3_TYPE.CONSUMABLES,
    TAG_V3_TYPE.MATERIAL,
]
LIMIT = 200


def get_tag_ids():

    tag_v3_ids = list(TagV3.objects.using(settings.ZHENGXING_DB).filter(
        is_online=True,
        tag_type__in=tag_types
    ).values_list("id", flat=True))

    return tag_v3_ids


def oldtag_map_tag_v3():

    result = defaultdict(set)
    for item in TagMapOldTag.objects.using(settings.ZHENGXING_DB).filter(is_online=True):
        result[item.old_tag_id].add(item.tag_id)

    return result


all_tag_ids = get_tag_ids()
all_oldtag2tagv3 = oldtag_map_tag_v3()


class DealTool(object):

    @staticmethod
    def get_ids(queue, entry_model):

        start_id = queue.get()
        print(start_id)
        if start_id is None:
            return

        entries = entry_model.objects.using(settings.SLAVE_DB_NAME).filter(pk__gt=start_id)[:LIMIT]
        max_id = entries.aggregate(max_id=Max('id'))
        queue.put(max_id["max_id"])

        return list(entries.values_list("id", flat=True))

    @staticmethod
    def delete_relations(relation_model, pks, entry_id_field):

        if not pks:
            return

        relations = defaultdict(set)
        for item in relation_model.objects.using(settings.SLAVE_DB_NAME).filter(pk__in=pks).values(entry_id_field, "tag_v3_id"):
            relations[item[entry_id_field]].add(item["tag_v3_id"])

        delete_log = {entry_id: list(i) for entry_id, i in relations.items()}
        delete_log["action"] = "delete"
        print(json.dumps(delete_log))
        relation_model.objects.filter(pk__in=pks).delete()

    @staticmethod
    def get_entries_tag_relation(entry_tag_model, entry_ids, entry_id_field, entry_id_field_in):
        entry2tag = defaultdict(list)
        for item in entry_tag_model.objects.using(settings.SLAVE_DB_NAME).filter(**{entry_id_field_in: entry_ids}):
            entry2tag[getattr(item, entry_id_field)].append(item.tag_id)
        return entry2tag

    @staticmethod
    def get_delete_info(entry_tag_v3_model, entry_ids, entry_id_field, entry_id_field_in):
        delete_ids, not_delete2tags = [], defaultdict(list)
        for item in entry_tag_v3_model.objects.using(settings.SLAVE_DB_NAME).filter(**{entry_id_field_in: entry_ids}):
            if item.tag_v3_id in all_tag_ids:
                delete_ids.append(item.id)
            else:
                not_delete2tags[getattr(item, entry_id_field)].append(item.tag_v3_id)
        return delete_ids, not_delete2tags

    @staticmethod
    def add_relations(entry_tag_v3_model, entry2tag, entry_id_field, not_delete2tags):

        create_tag = []
        for entry_id, tag_ids in entry2tag.items():
            added = set()
            not_delete_tags = not_delete2tags[entry_id]
            for tag_id in tag_ids:
                tag_v3_ids = all_oldtag2tagv3.get(tag_id)
                if not tag_v3_ids:
                    continue
                create_tag.extend([
                    entry_tag_v3_model(**{entry_id_field: entry_id, "tag_v3_id": tag_v3_id})
                    for tag_v3_id in (tag_v3_ids - added)
                    if (tag_v3_id not in not_delete_tags)
                ])
                added.update(tag_v3_ids)

        if create_tag:
            create_log = defaultdict(list)
            for item in create_tag:
                create_log[getattr(item, entry_id_field)].append(getattr(item, "tag_v3_id"))
            create_log = dict(create_log)
            create_log["action"] = "add"
            print(json.dumps(create_log))
            entry_tag_v3_model.objects.bulk_create(create_tag)

    @staticmethod
    def update_diary(queue):

        diary_ids = DealTool.get_ids(queue, Diary)
        if not diary_ids:
            return

        delete_ids, not_delete2tags = DealTool.get_delete_info(
            entry_tag_v3_model=DiaryTagV3,
            entry_ids=diary_ids,
            entry_id_field="diary_id",
            entry_id_field_in="diary_id__in"
        )
        DealTool.delete_relations(DiaryTagV3, delete_ids, entry_id_field="diary_id")

        diary2tag = DealTool.get_entries_tag_relation(
            entry_tag_model=DiaryTag,
            entry_ids=diary_ids,
            entry_id_field="diary_id",
            entry_id_field_in="diary_id__in",
        )
        DealTool.add_relations(
            entry_tag_v3_model=DiaryTagV3,
            entry2tag=diary2tag,
            entry_id_field="diary_id",
            not_delete2tags=not_delete2tags
        )

    @staticmethod
    def update_problem(queue):

        problem_ids = DealTool.get_ids(queue, Problem)
        if not problem_ids:
            return

        delete_ids, not_delete2tags = DealTool.get_delete_info(
            entry_tag_v3_model=ProblemTagV3,
            entry_ids=problem_ids,
            entry_id_field="problem_id",
            entry_id_field_in="problem_id__in"
        )
        DealTool.delete_relations(ProblemTagV3, delete_ids, entry_id_field="problem_id")

        problem2tag = DealTool.get_entries_tag_relation(
            entry_tag_model=ProblemTag,
            entry_ids=problem_ids,
            entry_id_field="problem_id",
            entry_id_field_in="problem_id__in",
        )
        DealTool.add_relations(
            entry_tag_v3_model=ProblemTagV3,
            entry2tag=problem2tag,
            entry_id_field="problem_id",
            not_delete2tags=not_delete2tags
        )

    @staticmethod
    def update_question(queue):

        question_ids = DealTool.get_ids(queue, Question)
        if not question_ids:
            return

        delete_ids, not_delete2tags = DealTool.get_delete_info(
            entry_tag_v3_model=QuestionTagV3,
            entry_ids=question_ids,
            entry_id_field="question_id",
            entry_id_field_in="question_id__in"
        )
        DealTool.delete_relations(QuestionTagV3, delete_ids, entry_id_field="question_id")

        question2tag = DealTool.get_entries_tag_relation(
            entry_tag_model=QuestionTag,
            entry_ids=question_ids,
            entry_id_field="question_id",
            entry_id_field_in="question_id__in",
        )
        DealTool.add_relations(
            entry_tag_v3_model=QuestionTagV3,
            entry2tag=question2tag,
            entry_id_field="question_id",
            not_delete2tags=not_delete2tags
        )

    @staticmethod
    def update_answer(queue):

        answer_ids = DealTool.get_ids(queue, Answer)
        if not answer_ids:
            return

        delete_ids, not_delete2tags = DealTool.get_delete_info(
            entry_tag_v3_model=AnswerTagV3,
            entry_ids=answer_ids,
            entry_id_field="answer_id",
            entry_id_field_in="answer_id__in"
        )
        DealTool.delete_relations(AnswerTagV3, delete_ids, entry_id_field="answer_id")

        answer2tag = DealTool.get_entries_tag_relation(
            entry_tag_model=AnswerTag,
            entry_ids=answer_ids,
            entry_id_field="answer_id",
            entry_id_field_in="answer_id__in",
        )
        print("老标签：")
        print(json.dumps(answer2tag))
        DealTool.add_relations(
            entry_tag_v3_model=AnswerTagV3,
            entry2tag=answer2tag,
            entry_id_field="answer_id",
            not_delete2tags=not_delete2tags
        )

    @staticmethod
    def update_tractate(queue):

        tractate_ids = DealTool.get_ids(queue, Answer)
        if not tractate_ids:
            return

        delete_ids, not_delete2tags = DealTool.get_delete_info(
            entry_tag_v3_model=TractateTagV3,
            entry_ids=tractate_ids,
            entry_id_field="tractate_id",
            entry_id_field_in="tractate_id__in"
        )
        DealTool.delete_relations(TractateTagV3, delete_ids, entry_id_field="tractate_id")

        tractate2tag = DealTool.get_entries_tag_relation(
            entry_tag_model=TractateTag,
            entry_ids=tractate_ids,
            entry_id_field="tractate_id",
            entry_id_field_in="tractate_id__in",
        )
        DealTool.add_relations(
            entry_tag_v3_model=TractateTagV3,
            entry2tag=tractate2tag,
            entry_id_field="tractate_id",
            not_delete2tags=not_delete2tags
        )


class Command(BaseCommand):
    """同步内容标签"""

    processes = 1
    limit = LIMIT

    def start(self, count, processor):

        queue = Manager().Queue(maxsize=self.processes)
        queue.put(0)  # 触发程序开始

        args_list = []
        cnt = int(math.ceil(count / self.limit))
        for _ in range(cnt):
            args_list.append((queue,))

        db.connections.close_all()
        pool = Pool(processes=self.processes)
        pool.starmap(processor, args_list)
        pool.close()
        pool.join()

    def start_diary(self):

        max_id = Diary.objects.using(settings.SLAVE_DB_NAME).aggregate(max_id=Max('id'))["max_id"]
        self.start(max_id, DealTool.update_diary)

    def start_problem(self):

        max_id = Problem.objects.using(settings.SLAVE_DB_NAME).aggregate(max_id=Max('id'))["max_id"]
        self.start(max_id, DealTool.update_problem)

    def start_question(self):

        max_id = Question.objects.using(settings.SLAVE_DB_NAME).aggregate(max_id=Max('id'))["max_id"]
        self.start(max_id, DealTool.update_question)

    def start_answer(self):

        max_id = Answer.objects.using(settings.SLAVE_DB_NAME).aggregate(max_id=Max('id'))["max_id"]
        self.start(max_id, DealTool.update_answer)

    def start_tractate(self):

        max_id = Tractate.objects.using(settings.SLAVE_DB_NAME).aggregate(max_id=Max('id'))["max_id"]
        self.start(max_id, DealTool.update_tractate)

    def add_arguments(self, parser):
        parser.add_argument('-t', '--type', dest='type', type=str)

    def handle(self, *args, **options):

        print("BEGIN")

        CONTENT_TYPE = namedtuple("CONTENT_TYPE", ["DIARY", "TOPIC", "QUESTION", "ANSWER", "TRACTATE"])(
            DIARY='1', TOPIC='2', QUESTION='3', ANSWER='4', TRACTATE='5'
        )

        content_type = options['type']
        if not content_type:
            raise CommandError("请指定内容类型")

        if content_type == CONTENT_TYPE.DIARY:
            self.start_diary()

        elif content_type == CONTENT_TYPE.TOPIC:
            self.start_problem()

        elif content_type == CONTENT_TYPE.QUESTION:
            self.start_question()

        # elif content_type == CONTENT_TYPE.ANSWER:
        #     self.start_answer()

        elif content_type == CONTENT_TYPE.TRACTATE:
            self.start_tractate()

        print("END")
