from datetime import datetime
import logging
from typing import Any, Set

from django.db import models

from staff.departments.models import Geography, Department, InstanceClass
from staff.lib.db import atomic
from staff.lib.sync_tools.data_mapper import DataMapper
from staff.lib.sync_tools.rollupper import Rollupper

from staff.oebs import models as oebs_models
from staff.oebs.controllers.rolluppers.update_intranet_status import map_end_date_to_intranet_status


logger = logging.getLogger(__name__)


class GeographyDataMapper(DataMapper):
    mapping = (
        ('description', 'oebs_description'),
        ('description', 'native_lang', lambda _: 'ru'),
        ('end_date', 'intranet_status', map_end_date_to_intranet_status),
    )


class GeographyRollupper(Rollupper):
    model = oebs_models.Geography
    queryset = oebs_models.Geography.objects.all()
    data_mapper_class = GeographyDataMapper
    link_field_name = 'staff_geography'
    key_field_name = 'code'
    rollup_rel_fields = (
        ('name', {}),
        ('name_en', {}),
        ('native_lang', {}),
        ('intranet_status', {}),
    )
    create_absent = True

    @atomic
    def create_dis_instance(self, oebs_instance: oebs_models.Geography) -> Geography:
        now = datetime.now()
        department_instance = Department(
            from_staff_id=0,
            name=oebs_instance.description,
            created_at=now,
            modified_at=now,
            intranet_status=map_end_date_to_intranet_status(oebs_instance.end_date),
            url=f'geo_{oebs_instance.code.lower()}',
            instance_class=InstanceClass.GEOGRAPHY.value,
        )
        department_instance.save()

        return Geography(
            name=oebs_instance.description,
            created_at=now,
            modified_at=now,
            intranet_status=map_end_date_to_intranet_status(oebs_instance.end_date),
            oebs_description=oebs_instance.description,
            oebs_code=oebs_instance.code,
            department_instance=department_instance,
        )

    def generic_rollup(
        self,
        oebs_instance: oebs_models.Geography,
        dis_instance: Geography,
        field_name: str,
        dry_run: bool,
        **kwargs: Any,
    ) -> bool:
        dep_value = getattr(dis_instance.department_instance, field_name)
        geo_value = getattr(dis_instance, field_name)

        if dep_value == geo_value:
            return False

        setattr(dis_instance.department_instance, field_name, geo_value)
        return True

    def rollup_relation_fields(
        self,
        oebs_instance: oebs_models.Geography,
        dis_instance: Geography,
        dry_run: bool,
    ) -> Set[str]:
        relation_fields = super().rollup_relation_fields(oebs_instance, dis_instance, dry_run)

        if relation_fields and not dry_run:
            dis_instance.department_instance.save()

        return relation_fields

    def run_rollup(self, object_id=None, dry_run=False):
        super().run_rollup(object_id, dry_run)

        if not dry_run:
            with atomic():
                existing_oebs_codes = list(oebs_models.Geography.objects.all().values_list('code', flat=True))
                old_geographies = (
                    Geography.objects
                    .filter(oebs_code__isnull=False, intranet_status=1)
                    .exclude(oebs_code__in=existing_oebs_codes)
                    .select_related('department_instance')
                )

                for old_geo in old_geographies:
                    old_geo.intranet_status = 0
                    old_geo.save(update_fields=['intranet_status'])
                    old_geo.department_instance.intranet_status = 0
                    old_geo.department_instance.save(update_fields=['intranet_status'])

            with atomic():
                wrong_intranet_status = (
                    Geography.objects
                    .exclude(department_instance__intranet_status=models.F('intranet_status'))
                    .select_related('department_instance')
                )

                for geo in wrong_intranet_status:
                    geo.department_instance.intranet_status = geo.intranet_status
                    geo.department_instance.save(update_fields=['intranet_status'])
