import logging
from typing import Dict, Set

from staff.departments.models import RelevanceDate, HeadcountPosition, HRProduct
from staff.lib.db import atomic
from staff.lib.sync_tools.rollupper import Rollupper
from staff.oebs.models import HeadcountPosition as OEBSHeadcountPosition
from staff.person.models import Staff

from staff.oebs.controllers.mappers import DataMapper
from staff.oebs.controllers.rolluppers.fix_zero_hc_cross import fix_zero_hc_cross
from staff.oebs.controllers.rolluppers.headcounts_obfuscator import obfuscate_headcounts_data
from staff.oebs.controllers.rolluppers.sync_bp_codes_for_persons import sync_bp_codes_for_persons


logger = logging.getLogger(__name__)


class OEBSHeadcountsRollupper(object):
    model = OEBSHeadcountPosition

    @classmethod
    def rollup(cls, **kwargs):
        dry_run = kwargs.get('dry_run') or False

        logger.info('OEBSHeadcountsRollupper: Rolling up positions')
        HeadcountPositionsRollupper().rollup(dry_run=dry_run, create_absent=True)

        with atomic():
            logger.info('OEBSHeadcountsRollupper: fix_zero_hc_cross')
            fix_zero_hc_cross()
            obfuscate_headcounts_data()
            logger.info('OEBSHeadcountsRollupper: sync_bp_codes_for_persons')
            sync_bp_codes_for_persons()

        logger.info('OEBSHeadcountsRollupper: Rolling up relevance date')
        RelevanceDateRollupper(dry_run).rollup_relevance_date()
        logger.info('OEBSHeadcountsRollupper: Headcounts rolled up')


class HeadcountPositionsMapper(DataMapper):
    mapping = (
        ('position_code', 'code'),
        ('position_name', 'name'),
        ('position_geo', 'geo'),
        ('position_bonus_id', 'bonus_id'),
        ('position_reward_id', 'reward_id'),
        ('position_review_id', 'review_id'),
        ('position_headcount', 'headcount'),
        ('position_is_crossing', 'is_crossing'),
        ('position_category_is_new', 'category_is_new'),
        ('position_status', 'status'),
        ('position_current_login', 'current_login'),
        ('position_previous_login', 'previous_login'),
        ('position_in_total_hc', 'in_total_hc'),
        ('position_index', 'index'),
        ('position_prev_index', 'prev_index'),
        ('position_next_index', 'next_index'),
        ('position_replacement_type', 'replacement_type'),
        ('position_main_assignment', 'main_assignment'),
    )


class HeadcountPositionsRollupper(Rollupper):
    model = OEBSHeadcountPosition
    link_field_name = 'departments_headcountposition'
    key_field_name = 'id'
    rollup_rel_fields = (
        ('org_id', {'dest_field': 'department_id', 'map_func': '_get_department_id'}),  # почему тут, а не в mapping?
        ('position_current_login', {'dest_field': 'current_person_id', 'map_func': '_get_person'}),
        ('position_previous_login', {'dest_field': 'previous_person_id', 'map_func': '_get_person'}),
        ('position_product_id', {'dest_field': 'hr_product_id', 'map_func': '_get_hr_product'}),
        ('position_product_id', {'dest_field': 'valuestream_id', 'map_func': '_get_valuestream'}),
    )
    data_mapper_class = HeadcountPositionsMapper

    def run_rollup(self, object_id=None, dry_run=False):
        super(HeadcountPositionsRollupper, self).run_rollup(object_id, dry_run)
        HeadcountPosition.objects.exclude(id__in=OEBSHeadcountPosition.objects.values('id')).delete()

    def create_dis_instance(self, oebs_instance):
        return HeadcountPosition(id=oebs_instance.id)

    def generic_rollup(self, oebs_instance, dis_instance, field_name, dry_run, **kwargs):
        map_func = getattr(self, kwargs['map_func'])
        dest_field = kwargs['dest_field']
        value = getattr(oebs_instance, field_name)
        result_value = map_func(value)
        dest_value = getattr(dis_instance, dest_field)

        if result_value == dest_value:
            return False

        setattr(dis_instance, dest_field, result_value)
        return True

    def _get_person(self, login):
        persons_cache = self.cache.get('persons')
        if persons_cache is None:
            self.cache['persons'] = persons_cache = dict(Staff.objects.values_list('login', 'id'))
        return persons_cache.get(login)

    def _get_hr_product(self, hr_product_id):
        hr_products_cache: Set[int] = self.cache.get('hr_products')
        if hr_products_cache is None:
            hr_products_cache = set(HRProduct.objects.values_list('id', flat=True))
            self.cache['hr_products'] = hr_products_cache

        if hr_product_id not in hr_products_cache:
            logger.info('Product is %s not found', hr_product_id)
            return None

        return hr_product_id

    def _get_valuestream(self, hr_product_id):
        valuestream_cache: Dict[int, int] = self.cache.get('valuestreams')
        if valuestream_cache is None:
            valuestream_cache = dict(HRProduct.objects.values_list('id', 'value_stream_id'))
            self.cache['valuestreams'] = valuestream_cache

        return valuestream_cache.get(hr_product_id)

    @staticmethod
    def _get_department_id(org_id):
        return org_id


class OEBSRollupper(object):
    def __init__(self, dry_run):
        self.dry_run = dry_run


class RelevanceDateRollupper(OEBSRollupper):
    def rollup_relevance_date(self):
        logger.info('Updating relevance date')
        position = OEBSHeadcountPosition.objects.first()
        RelevanceDate.objects.update_or_create(model_name=None, defaults={'relevance_date': position.relevance_date})
