from patch.include import *
from tqdm import tqdm
import os


def scaffold_all():
    # osv: это временный файл для обновления bcrm acrm идемпотентный
    from cmf.system_data import scaffold_activities
    scaffold_activities()
    from cmf.system_data import scaffold_tags
    scaffold_tags()
    from cmf.system_data import scaffold_workflow_type
    scaffold_workflow_type()


def update_project():
    for project in tqdm(models.CmfProject.list(
            fields=['workflow', 'activity']
    )):
        if project.workflow_type and 'agile' in project.workflow_type:
            workflow = models.CmfWorkflow.calc_workflow(
                'CmfProject', 'project.agile%', activity=project.activity)
        elif project.workflow_type and 'wiki' in project.workflow_type:
            workflow = models.CmfWorkflow.calc_workflow(
                'CmfProject', 'project.wiki%', activity=project.activity)
        else:
            workflow = models.CmfWorkflow.calc_workflow(
                'CmfProject', 'project.base%', activity=project.activity)
        if project.workflow == workflow:
            print(project, '- ALREADY OK')
            continue

        project.workflow = workflow
        project.workflow.is_changed = True
        project.save()
        print(project, '- UPDATED')


def wf_diff(pwf, lst):
    for sto in lst.status_opts:
        code = sto.status.code.value.replace('DEL-', '')
        name = sto.status.name.value.replace('DEL-', '')
        sto_exists = pwf.status_list(
                           filter=['OR', ['code', '==', code],
                                         ['name', '==', name]
                                  ])
        if not sto_exists:
            return sto
    return None

def create_new_wf_task(name, workflow_type, lst, cmf_model='CmfTask'):
    new_wf = models.CmfWorkflow(name=name, workflow_type=workflow_type)
    new_wf.save()
    new_wf = models.CmfWorkflow.get(id=new_wf.id, fields=['**'])
    new_wf.statuses.load()
    for sto in lst.status_opts:
        name = sto.status.name.value.replace('DEL-', '')
        code = sto.status.code.value.replace('DEL-', '')
        code = code.replace('del-', '')
        if name not in [x.name for x in new_wf.statuses] and code not in [x.code for x in new_wf.statuses]:
            add_status = sto.status.clone(code=code)
            # add_status.code = code
            add_status.name = sto.name
            add_status.workflow = new_wf
            add_status.cmf_model = cmf_model
            add_status.orderno = sto.orderno
            add_status.color = sto.color
            add_status.save()
            print(f'CREATE status {add_status}')
    new_wf = models.CmfWorkflow.get(id=new_wf.id, fields=['**'])
    return new_wf

def create_new_wf_deal(name, workflow_type, lst, cmf_model='CmfTask'):
    new_wf = models.CmfWorkflow(name=name, workflow_type=workflow_type)
    new_wf.save(skip_scaffold=True)
    created_status_names = []
    for sto in lst.status_opts:
        name = sto.name.value or sto.status.name.value or ''
        name = name.replace('DEL-', '')
        code = sto.status.code.value.replace('DEL-', '')
        code = code.replace('del-', '')
        if name and name not in created_status_names:
            created_status_names.append(name)
            add_status = sto.status.clone(code=code)
            # add_status.code = code
            add_status.name = name
            add_status.workflow = new_wf
            add_status.cmf_model = cmf_model
            add_status.orderno = sto.orderno
            add_status.color = sto.color
            add_status.save()
            print(f'CREATE status {add_status}')
    new_wf = models.CmfWorkflow.get(id=new_wf.id, fields=['**'])
    return new_wf


def update_list():
    idx = ''
    for lst in models.CmfList.list(
            fields=['default_task_workflow', 'status_opts.status', 'status_opts.orderno', 'sys_type',
                    'parent', 'workflow', 'activity'],
            filter=['parent_id', 'LIKE', 'CmfProject:%']):
        if not lst.parent or not 'CmfProject' in lst.parent.id.value:
            continue
        pwf = lst.parent.default_task_workflow.load()
        if lst.default_task_workflow:
            pwf = lst.default_task_workflow
        cur_pwf = None
        sto_diff = wf_diff(pwf, lst)
        if sto_diff is None:
            cur_pwf = pwf
            if lst.default_task_workflow == cur_pwf and lst.workflow:
                print(lst, '- ALREADY OK')
                continue
        if not cur_pwf:
            # находим по статусу wf
            name = sto_diff.status.name.value.replace('DEL-', '')
            code = sto_diff.status.code.value.replace('DEL-', '')
            try_status = models.CmfStatus.get(filter=[
                ['name', '=', name],
                ['workflow', '!=', None]], fields=['**'])
            if try_status and try_status.workflow:
                print(f'TRY try_status {try_status}')
                sto_diff = wf_diff(try_status.workflow, lst)
                if sto_diff is None:
                    cur_pwf = try_status.workflow
                    print(f'FOUND BY try_status {try_status} {cur_pwf}')
        if not cur_pwf:
            cur_pwf = create_new_wf_task(
                f'task-{lst.parent.name}{idx}', pwf.workflow_type.load(), lst)
            if idx == '':
                idx = 0
            idx += 1
            print(f"CREATE {cur_pwf} ^^^")
        lst.default_task_workflow = cur_pwf
        lst._calc_workflow()
        lst.save(only_data=True)
        print(lst, '- UPDATED')


def update_task():
    tasks = models.CmfTask.list(fields=['parent_task'])
    tasks.sort(key=lambda t: bool(t.parent_task))  # Вначале патчим родителей.
    for task in tasks:
        # Т.к. родительская задача могла быть пропатчена, загрузим задачу целиком
        task = models.CmfTask.get(task.id, fields=task.save_preload_fields()+['parent_task.workflow'])
        print(
            f'Task {task.code}, parent_task {task.parent_task and task.parent_task.code},'
            f' parent workflow {task.parent_task and task.parent_task.workflow_type}({task.parent_task and task.parent_task.workflow})')
        old_workflow_code = task.workflow and task.workflow.code.value or None
        task.workflow = None
        task._calc_workflow()
        print(
            f'Task {task.code}, parent_task {task.parent_task and task.parent_task.code},'
            f'workflow {task.workflow_type}({task.workflow})')
        if old_workflow_code is not None and old_workflow_code == task.workflow.code.value\
                and task.cache_status_opt_id.is_null:
            print(f"{task.code} - ALREADY OK")
            continue
        task.cache_status_opt_id = None
        task.save(only_data=True)
        print(f"{task.code} - UPDATED {old_workflow_code} -> {task.workflow.code}")
        assert task.workflow


def update_pipeline():
    for pipeline in models.CmfPipeline.list(
            filter=['default_deal_workflow', '==', None],
            fields=[
                'workflow.workflow_type', 'status_opts.status', 'status_opts.orderno',
                'default_deal_workflow.workflow_type']):

        pipeline.workflow.is_changed=True # force _calc_default_workflow
        pipeline.save()
        pipeline.load_fields(['workflow.workflow_type', 'status_opts.status', 'default_deal_workflow.workflow_type'])
        print()
        print(f'Pipeline {pipeline, pipeline.code}')
        wf_code = f'deal.base:{pipeline.code}'
        new_wf = create_new_wf_deal(
            wf_code, pipeline.default_deal_workflow.workflow_type, pipeline, 'CmfDeal')
        if new_wf is None:
            import pdb; pdb.set_trace()
        pipeline.default_deal_workflow = new_wf
        pipeline.save()


def update_deal():
    for deal in models.CmfDeal.list(fields=['workflow', 'activity', 'workflow_type', 'pipeline.cmf_deleted', 'pipeline.default_deal_workflow',
                                                 'cache_status_opt', 'status.status_type']):
        print(f'START DEAL {deal}')
        if not deal.pipeline:
            print(f'BAD_DEAL {deal} have no pipeline')
            continue
        deal.workflow = None
        deal._calc_workflow()
        deal.save(only_data=True)
        if not deal.workflow == deal.pipeline.default_deal_workflow \
            and deal.pipeline.cmf_deleted==False:
            assert deal.workflow == deal.pipeline.default_deal_workflow
        # Пытаемся найти новый статус
        deal.workflow.statuses.load()
        if deal.cache_status_opt and deal.cache_status_opt.name.value is not None:
            strip_status_name = deal.cache_status_opt.name.value.replace('DEL-', '')
        else:
            strip_status_name = deal.status.name.value.replace('DEL-', '')

        for status in deal.workflow.statuses:
            if status.name == strip_status_name and deal.status.id != status.id:
                deal.status = status
                deal.cache_status_opt = None
                break
        # Если подвисшая сделка
        if deal.status not in deal.workflow.statuses:
            for status in deal.workflow.statuses:
                if not deal.status.status_type:
                    deal.status.status_type = 'open'
                if status.status_type == deal.status.status_type:
                    deal.status = status
                    deal.cache_status_opt = None
        assert deal.status.id in [obj.id for obj in deal.workflow.statuses], f'Не удалось вычислить новый статус у сделки {deal.status}'
        deal.save(only_data=True)


def update_active_entity():
    # здесь ставим дефалт всем
    from common.models.cmf_active_entity import CmfActiveEntity
    for m in CmfActiveEntity.iter_subclasses():
        if m.class_name:
            if m.class_name in ['CmfProject', 'CmfList', 'CmfTask', 'CmfPipeline', 'CmfDeal']:
                continue
        for obj in m.list(filter=['workflow', '==', None], fields=['workflow','activity']):
            print(obj)
            obj._calc_workflow()
            obj.save(only_data=True)
            assert obj.workflow


@app_context(commit=True)
def patch():
    """
    Для тестирования патча: ( cd /opt/crm; python3 -m patch.202205230433_tree_parent_set )
    Здесь можно работать с моделями через models.CmfTask и т.д.
    Для прогрессбара используйте:
    for task in tqdm(models.CmfTask.list()):
        ...
    """
    os.environ["NO_CACHE"] = "1"
    scaffold_all()  # проверено и применено для bcrm, acrm
    update_project()  # проверено и применено для bcrm, acrm
    update_list()  # проверено и применено для bcrm, acrm
    update_task()  # проверено и применено для bcrm, acrm
    update_active_entity()  # проверено и применено для bcrm, acrm
    update_pipeline()  # проверено и применено для bcrm
    update_deal()  # проверено и применено для bcrm


if __name__ == "__main__":
    patch()
