import datetime

from django.utils.timezone import now
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from greg.models import Consent, ConsentChoice, ConsentType, Identity, Person
from gregui.validation import (
    validate_phone_number,
    validate_norwegian_national_id_number,
)

# pylint: disable=W0223
class GuestConsentChoiceSerializer(serializers.Serializer):
    type = serializers.CharField(required=True)
    choice = serializers.CharField(required=True)

    def validate(self, attrs):
        """Check that the combination of consent type and choice exists."""
        choice = ConsentChoice.objects.filter(
            consent_type__identifier=attrs["type"], value=attrs["choice"]
        )
        if not choice.exists():
            raise serializers.ValidationError("invalid consent type or choice")
        return attrs


class GuestRegisterSerializer(serializers.ModelSerializer):
    first_name = serializers.CharField(required=False, min_length=1)
    last_name = serializers.CharField(required=False, min_length=1)
    # E-mail set to not required to avoid raising exception if it is not included in input. It is not given that
    # the guest should be allowed to update it
    email = serializers.CharField(required=False)
    mobile_phone = serializers.CharField(
        required=True, validators=[validate_phone_number]
    )
    fnr = serializers.CharField(
        required=False, validators=[validate_norwegian_national_id_number]
    )
    passport = serializers.CharField(required=False)
    date_of_birth = serializers.DateField(required=False)
    consents = GuestConsentChoiceSerializer(required=False, many=True, write_only=True)

    def update(self, instance, validated_data):
        if "email" in validated_data:
            email = validated_data.pop("email")
            create_identity_or_update(
                Identity.IdentityType.PRIVATE_EMAIL, email, instance
            )

        if "mobile_phone" in validated_data:
            mobile_phone = validated_data.pop("mobile_phone")
            create_identity_or_update(
                Identity.IdentityType.PRIVATE_MOBILE_NUMBER, mobile_phone, instance
            )

        if "fnr" in validated_data:
            fnr = validated_data.pop("fnr")
            create_identity_or_update(
                Identity.IdentityType.NORWEGIAN_NATIONAL_ID_NUMBER, fnr, instance
            )

        if "passport" in validated_data:
            passport = validated_data.pop("passport")
            create_identity_or_update(
                Identity.IdentityType.PASSPORT_NUMBER, passport, instance
            )

        # If the name is not allowed to be updated, the request has already been denied at an earlier stage
        if "first_name" in validated_data:
            instance.first_name = validated_data["first_name"]

        if "last_name" in validated_data:
            instance.last_name = validated_data["last_name"]

        if "date_of_birth" in validated_data:
            instance.date_of_birth = validated_data["date_of_birth"]

        consents = validated_data.get("consents", {})
        self._handle_consents(person=instance, consents=consents)

        return instance

    def _handle_consents(self, person, consents):
        consent_types = [x["type"] for x in consents]
        mandatory_consents = ConsentType.objects.filter(mandatory=True).values_list(
            "identifier", flat=True
        )
        missing_consents = list(set(mandatory_consents) - set(consent_types))
        if missing_consents:
            raise ValidationError(f"missing mandatory consents {missing_consents}")

        for consent in consents:
            consent_type = ConsentType.objects.get(identifier=consent["type"])
            choice = ConsentChoice.objects.get(
                consent_type=consent_type, value=consent["choice"]
            )
            consent_instance, created = Consent.objects.get_or_create(
                type=consent_type,
                person=person,
                choice=choice,
                defaults={"consent_given_at": now()},
            )
            if not created and consent_instance.choice != choice:
                consent_instance.choice = choice
                consent_instance.save()

    def validate_date_of_birth(self, date_of_birth):
        today = datetime.date.today()

        # Check that the date of birth is between the interval starting about 100 years ago and last year
        if (
            not today - datetime.timedelta(weeks=100 * 52)
            < date_of_birth
            < today - datetime.timedelta(weeks=52)
        ):
            raise serializers.ValidationError("Invalid date of birth")

        return date_of_birth

    class Meta:
        model = Person
        fields = (
            "id",
            "first_name",
            "last_name",
            "email",
            "mobile_phone",
            "fnr",
            "passport",
            "date_of_birth",
            "consents",
        )
        read_only_fields = ("id",)


def create_identity_or_update(
    identity_type: Identity.IdentityType, value: str, person: Person
):
    existing_identity = person.identities.filter(type=identity_type).first()
    if not existing_identity:
        Identity.objects.create(
            person=person,
            type=identity_type,
            value=value,
        )
    else:
        existing_identity.value = value
        existing_identity.save()