import typing

from django.contrib.auth.hashers import make_password
from django.db.models import Q
from rest_framework import serializers

from apps.authentication.exceptions import UserExistException, OrganizationNationalUniqueIDException, \
    OrganizationTypeRepeatableException
from apps.authentication.models import (
    User,
    City,
    Province,
    Organization,
    OrganizationType,
    OrganizationStats,
    BankAccountInformation
)
from apps.authorization import models as authorize_models
from apps.authorization.api.v1.serializers import (
    UserRelationSerializer
)


class CitySerializer(serializers.ModelSerializer):
    """ Serialize city data """

    class Meta:
        model = City
        fields = [
            'id',
            'name',
            'province'
        ]


class ProvinceSerializer(serializers.ModelSerializer):
    """ Serialize province data """

    class Meta:
        model = Province
        fields = [
            'id',
            'name',
        ]


class BankAccountSerializer(serializers.ModelSerializer):
    """ Serialize bank account data """

    class Meta:
        model = BankAccountInformation
        fields = '__all__'

    def validate(self, attrs):
        account = attrs['account']
        card = attrs['card']
        sheba = attrs['sheba']

        # if not self.instance:
        #     if self.Meta.model.objects.filter(Q(account=account) | Q(card=card) | Q(sheba=sheba)).exists():
        #         raise BankAccountExistException()
        # elif self.instance:
        #     if self.instance.account != account:
        #         if self.Meta.model.objects.filter(Q(account=account)).exists():
        #             raise BankAccountNumberAccountException()
        #
        #     elif self.instance.card != card:
        #         if self.Meta.model.objects.filter(Q(card=card)).exists():
        #             raise BankAccountCardException()
        #
        #     elif self.instance.sheba != sheba:
        #         if self.Meta.model.objects.filter(Q(sheba=sheba)).exists():
        #             raise BankAccountShebaException()
        return attrs

    def update(self, instance, validated_data):
        """ update user bank account information """
        instance.name = validated_data.get('name', instance.name)
        instance.account = validated_data.get('account', instance.account)
        instance.organization = validated_data.get('organization', instance.organization)
        instance.account_type = validated_data.get('account_type', instance.account_type)
        instance.card = validated_data.get('card', instance.card)
        instance.sheba = validated_data.get('sheba', instance.sheba)
        instance.save()
        return instance


class UserSerializer(serializers.ModelSerializer):
    """ Serialize user data """

    class Meta:
        model = User
        fields = [
            'id',
            'username',
            'password',
            'first_name',
            'last_name',
            'is_active',
            'mobile',
            'phone',
            'national_code',
            'birthdate',
            'nationality',
            'ownership',
            'address',
            'unit_name',
            'unit_national_id',
            'photo',
            'province',
            'city',
            'otp_status',
            'is_herd_owner',
        ]
        extra_kwargs = {
            'password': {
                'required': False
            },
            'username': {
                'required': False
            },
            "national_code": {
                'required': False
            },
            "unit_name": {
                'required': False
            },
            "unit_national_id": {
                'required': False
            }
        }

    def validate(self, attrs):
        mobile = attrs['mobile']

        if not self.instance:
            if self.Meta.model.objects.filter(
                    Q(mobile=mobile) | Q(username=attrs['username']),
                    trash=False
            ).exists():
                raise UserExistException()

        elif self.instance:
            if self.instance.mobile != mobile:
                if self.Meta.model.objects.filter(trash=False, mobile=mobile).exists():
                    raise UserExistException()

            if 'username' in attrs.keys():
                if self.instance.username != attrs['username']:
                    if self.Meta.model.objects.filter(trash=False, username=attrs['username']).exists():
                        raise UserExistException()

        return attrs

    def to_representation(self, instance):
        """ Custom output """

        representation = super().to_representation(instance)
        if isinstance(instance, User):
            if instance.bank_information.filter().exists():
                representation['bank_account'] = BankAccountSerializer(
                    instance.bank_information.all(), many=True
                ).data

            if instance.city:
                representation['city_name'] = instance.city.name
            if instance.province:
                representation['province_name'] = instance.province.name

        return representation

    def update(self, instance, validated_data):
        """ update user instance """
        instance.username = validated_data.get('username', instance.username)

        # control password
        if validated_data.get('password'):
            instance.password = make_password(validated_data.get('password', instance.password))
        instance.first_name = validated_data.get('first_name')
        instance.last_name = validated_data.get('last_name')
        instance.is_active = validated_data.get('is_active')
        instance.mobile = validated_data.get('mobile')
        instance.phone = validated_data.get('phone')
        instance.national_code = validated_data.get('national_code')
        instance.birthdate = validated_data.get('birthdate')
        instance.nationality = validated_data.get('nationality')
        instance.ownership = validated_data.get('ownership')
        instance.address = validated_data.get('address')
        instance.unit_name = validated_data.get('unit_name')
        instance.unit_national_id = validated_data.get('unit_national_id')
        instance.photo = validated_data.get('photo')
        instance.province = validated_data.get('province', instance.province)
        instance.city = validated_data.get('city', instance.city)
        instance.otp_status = validated_data.get('otp_status')
        instance.save()

        return instance

    @staticmethod
    def update_relations(user: object, relation_data: dict, bank_data: dict) -> typing.Any:
        """
            update user relations & bank account for user
        """
        user_relation = UserRelationSerializer(data=relation_data)  # Create user relation
        if user_relation.is_valid(raise_exception=True):
            user_relation_obj = user_relation.update(
                authorize_models.UserRelations.objects.get(user=user),
                validated_data=relation_data
            )

        bank_info = BankAccountSerializer(data=bank_data)  # Create user bank information
        if bank_info.is_valid(raise_exception=True):
            bank_obj = bank_info.update(
                BankAccountInformation.objects.get(id=bank_data['id']),
                validated_data=bank_data
            )

        return user_relation_obj, bank_obj  # noqa


class OrganizationTypeSerializer(serializers.ModelSerializer):
    """ Serialize organization type data """

    class Meta:
        model = OrganizationType
        fields = [
            'id',
            'parent',
            'key',
            'name',
            'org_type_field',
            'is_repeatable'
        ]

    def to_representation(self, instance):
        representation = super().to_representation(instance)

        if instance.parent:
            representation['parent'] = {
                'name': instance.parent.name,
                'id': instance.parent.id
            }

        return representation


class OrganizationSerializer(serializers.ModelSerializer):
    """ Serialize organization data """

    class Meta:
        model = Organization
        fields = [
            'id',
            'name',
            'type',
            'province',
            'city',
            'address',
            'parent_organization',
            'national_unique_id',
            'company_code',
            'field_of_activity',
            'free_visibility_by_scope',
            'purchase_policy',
            'service_area',  # noqa
        ]
        extra_kwargs = {
            'service_area': {  # noqa
                'required': False
            },
            'purchase_policy': {
                'required': False
            }
        }

    def validate(self, attrs):
        """
        @ validate national_unique_code to be unique
        @ validate to organization type field
        """

        #  check national_unique_id
        national_unique_id = attrs.get('national_unique_id', 0)

        if not self.instance:
            if self.Meta.model.objects.filter(national_unique_id=national_unique_id).exists():
                raise OrganizationNationalUniqueIDException()
        if self.instance:
            if self.instance.national_unique_id != national_unique_id:
                if self.Meta.model.objects.filter(national_unique_id=national_unique_id).exists():
                    raise OrganizationNationalUniqueIDException()

        # check organization type field
        # if is repeatable of type, organization will not be recreating
        org_type = attrs.get('type', None)
        if not self.instance:
            if org_type is not None and not org_type.is_repeatable:
                if org_type.org_type_field == 'PR' and self.Meta.model.objects.filter(
                        type=org_type,
                        province=attrs['province']
                ).exists():
                    raise OrganizationTypeRepeatableException()

                if org_type.org_type_field == 'CI' and self.Meta.model.objects.filter(
                        type=org_type,
                        city=attrs['city']
                ).exists():
                    raise OrganizationTypeRepeatableException()

                if org_type.org_type_field == 'CO' and self.Meta.model.objects.filter(
                        type=org_type,
                        province=attrs['province'],
                ).exists():
                    raise OrganizationTypeRepeatableException()

        # check organization type field when updating
        elif self.instance:
            if org_type is not None and not org_type.is_repeatable:
                if org_type.org_type_field == 'PR' and self.instance.province != attrs[
                    'province'] and self.Meta.model.objects.filter(
                    type=org_type,
                    province=attrs['province'],
                ).exists():
                    raise OrganizationTypeRepeatableException()

                if org_type.org_type_field == 'CI' and self.instance.city != attrs[
                    'city'] and self.Meta.model.objects.filter(
                    type=org_type,
                    city=attrs['city'],
                ).exists():
                    raise OrganizationTypeRepeatableException()

                if org_type.org_type_field == 'CO' and self.instance.province != attrs[
                    'province'] and self.Meta.model.objects.filter(
                    type=org_type,
                    province=attrs['province'],
                ).exists():
                    raise OrganizationTypeRepeatableException()

        return attrs

    def to_representation(self, instance):
        """ Custom output """
        representation = super().to_representation(instance)
        if isinstance(instance, Organization):
            representation['province'] = ProvinceSerializer(instance.province).data
            representation['city'] = CitySerializer(instance.city).data
            representation['type'] = OrganizationTypeSerializer(instance.type).data
            if instance.parent_organization:
                representation['parent_organization'] = OrganizationSerializer(
                    instance.parent_organization,
                ).data

            if instance.bank_information.exists():
                representation['bank_account'] = BankAccountSerializer(
                    instance.bank_information.all(), many=True
                ).data

            if instance.service_area:
                representation['service_area'] = [
                    {
                        'id': city.id,
                        'name': city.name,
                    } for city in instance.service_area.all()
                ]
        return representation

    def update(self, instance, validated_data):
        """ update user organization information """  # noqa
        instance.name = validated_data.get('name', instance.name)
        instance.type = validated_data.get('type', instance.type)
        instance.province = validated_data.get('province', instance.province)
        instance.city = validated_data.get('city', instance.city)
        instance.address = validated_data.get('address', instance.address)
        instance.parent_organization = validated_data.get('parent_organization', instance.parent_organization)
        instance.national_unique_id = validated_data.get('national_unique_id', instance.national_unique_id)
        instance.purchase_policy = validated_data.get('purchase_policy', instance.purchase_policy)
        instance.free_visibility_by_scope = validated_data.get(
            'free_visibility_by_scope',
            instance.free_visibility_by_scope
        )
        instance.save()

        if validated_data.get('service_area'):
            instance.service_area.clear()
            instance.service_area.add(*(validated_data.get('service_area')))
        return instance


class OrganizationStatsSerializer(serializers.ModelSerializer):
    class Meta:
        model = OrganizationStats
        fields = '__all__'
