from __future__ import annotations

import datetime
import json
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, TypeVar, cast

from attrs import define as _attrs_define
from attrs import field as _attrs_field
from dateutil.parser import isoparse

from .. import types
from ..types import UNSET, Unset

if TYPE_CHECKING:
    from ..models.food import Food
    from ..models.inventory_location import InventoryLocation
    from ..models.unit import Unit


T = TypeVar("T", bound="PatchedInventoryEntry")


@_attrs_define
class PatchedInventoryEntry:
    """Adds nested create feature

    Attributes:
        id (int | Unset):
        inventory_location (InventoryLocation | Unset): Moves `UniqueValidator`'s from the validation stage to the save
            stage.
            It solves the problem with nested validation for unique fields on update.

            If you want more details, you can read related issues and articles:
            https://github.com/beda-software/drf-writable-nested/issues/1
            http://www.django-rest-framework.org/api-guide/validators/#updating-nested-serializers

            Example of usage:
            ```
                class Child(models.Model):
                field = models.CharField(unique=True)


            class Parent(models.Model):
                child = models.ForeignKey('Child')


            class ChildSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
                class Meta:
                    model = Child


            class ParentSerializer(NestedUpdateMixin, serializers.ModelSerializer):
                child = ChildSerializer()

                class Meta:
                    model = Parent
            ```

            Note: `UniqueFieldsMixin` must be applied only on the serializer
            which has unique fields.

            Note: When you are using both mixins
            (`UniqueFieldsMixin` and `NestedCreateMixin` or `NestedUpdateMixin`)
            you should put `UniqueFieldsMixin` ahead.
        sub_location (None | str | Unset):
        code (None | str | Unset):
        food (Food | Unset): Moves `UniqueValidator`'s from the validation stage to the save stage.
            It solves the problem with nested validation for unique fields on update.

            If you want more details, you can read related issues and articles:
            https://github.com/beda-software/drf-writable-nested/issues/1
            http://www.django-rest-framework.org/api-guide/validators/#updating-nested-serializers

            Example of usage:
            ```
                class Child(models.Model):
                field = models.CharField(unique=True)


            class Parent(models.Model):
                child = models.ForeignKey('Child')


            class ChildSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
                class Meta:
                    model = Child


            class ParentSerializer(NestedUpdateMixin, serializers.ModelSerializer):
                child = ChildSerializer()

                class Meta:
                    model = Parent
            ```

            Note: `UniqueFieldsMixin` must be applied only on the serializer
            which has unique fields.

            Note: When you are using both mixins
            (`UniqueFieldsMixin` and `NestedCreateMixin` or `NestedUpdateMixin`)
            you should put `UniqueFieldsMixin` ahead.
        unit (Unit | Unset): Moves `UniqueValidator`'s from the validation stage to the save stage.
            It solves the problem with nested validation for unique fields on update.

            If you want more details, you can read related issues and articles:
            https://github.com/beda-software/drf-writable-nested/issues/1
            http://www.django-rest-framework.org/api-guide/validators/#updating-nested-serializers

            Example of usage:
            ```
                class Child(models.Model):
                field = models.CharField(unique=True)


            class Parent(models.Model):
                child = models.ForeignKey('Child')


            class ChildSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
                class Meta:
                    model = Child


            class ParentSerializer(NestedUpdateMixin, serializers.ModelSerializer):
                child = ChildSerializer()

                class Meta:
                    model = Parent
            ```

            Note: `UniqueFieldsMixin` must be applied only on the serializer
            which has unique fields.

            Note: When you are using both mixins
            (`UniqueFieldsMixin` and `NestedCreateMixin` or `NestedUpdateMixin`)
            you should put `UniqueFieldsMixin` ahead.
        amount (float | Unset):
        expires (datetime.date | None | Unset):
        note (None | str | Unset):
        label (str | Unset):
        created_at (datetime.datetime | Unset):
        created_by (int | Unset):
    """

    id: int | Unset = UNSET
    inventory_location: InventoryLocation | Unset = UNSET
    sub_location: None | str | Unset = UNSET
    code: None | str | Unset = UNSET
    food: Food | Unset = UNSET
    unit: Unit | Unset = UNSET
    amount: float | Unset = UNSET
    expires: datetime.date | None | Unset = UNSET
    note: None | str | Unset = UNSET
    label: str | Unset = UNSET
    created_at: datetime.datetime | Unset = UNSET
    created_by: int | Unset = UNSET
    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)

    def to_dict(self) -> dict[str, Any]:
        id = self.id

        inventory_location: dict[str, Any] | Unset = UNSET
        if not isinstance(self.inventory_location, Unset):
            inventory_location = self.inventory_location.to_dict()

        sub_location: None | str | Unset
        if isinstance(self.sub_location, Unset):
            sub_location = UNSET
        else:
            sub_location = self.sub_location

        code: None | str | Unset
        if isinstance(self.code, Unset):
            code = UNSET
        else:
            code = self.code

        food: dict[str, Any] | Unset = UNSET
        if not isinstance(self.food, Unset):
            food = self.food.to_dict()

        unit: dict[str, Any] | Unset = UNSET
        if not isinstance(self.unit, Unset):
            unit = self.unit.to_dict()

        amount = self.amount

        expires: None | str | Unset
        if isinstance(self.expires, Unset):
            expires = UNSET
        elif isinstance(self.expires, datetime.date):
            expires = self.expires.isoformat()
        else:
            expires = self.expires

        note: None | str | Unset
        if isinstance(self.note, Unset):
            note = UNSET
        else:
            note = self.note

        label = self.label

        created_at: str | Unset = UNSET
        if not isinstance(self.created_at, Unset):
            created_at = self.created_at.isoformat()

        created_by = self.created_by

        field_dict: dict[str, Any] = {}
        field_dict.update(self.additional_properties)
        field_dict.update({})
        if id is not UNSET:
            field_dict["id"] = id
        if inventory_location is not UNSET:
            field_dict["inventory_location"] = inventory_location
        if sub_location is not UNSET:
            field_dict["sub_location"] = sub_location
        if code is not UNSET:
            field_dict["code"] = code
        if food is not UNSET:
            field_dict["food"] = food
        if unit is not UNSET:
            field_dict["unit"] = unit
        if amount is not UNSET:
            field_dict["amount"] = amount
        if expires is not UNSET:
            field_dict["expires"] = expires
        if note is not UNSET:
            field_dict["note"] = note
        if label is not UNSET:
            field_dict["label"] = label
        if created_at is not UNSET:
            field_dict["created_at"] = created_at
        if created_by is not UNSET:
            field_dict["created_by"] = created_by

        return field_dict

    def to_multipart(self) -> types.RequestFiles:
        files: types.RequestFiles = []

        if not isinstance(self.id, Unset):
            files.append(("id", (None, str(self.id).encode(), "text/plain")))

        if not isinstance(self.inventory_location, Unset):
            files.append(
                (
                    "inventory_location",
                    (None, json.dumps(self.inventory_location.to_dict()).encode(), "application/json"),
                )
            )

        if not isinstance(self.sub_location, Unset):
            if isinstance(self.sub_location, str):
                files.append(("sub_location", (None, str(self.sub_location).encode(), "text/plain")))
            else:
                files.append(("sub_location", (None, str(self.sub_location).encode(), "text/plain")))

        if not isinstance(self.code, Unset):
            if isinstance(self.code, str):
                files.append(("code", (None, str(self.code).encode(), "text/plain")))
            else:
                files.append(("code", (None, str(self.code).encode(), "text/plain")))

        if not isinstance(self.food, Unset):
            files.append(("food", (None, json.dumps(self.food.to_dict()).encode(), "application/json")))

        if not isinstance(self.unit, Unset):
            files.append(("unit", (None, json.dumps(self.unit.to_dict()).encode(), "application/json")))

        if not isinstance(self.amount, Unset):
            files.append(("amount", (None, str(self.amount).encode(), "text/plain")))

        if not isinstance(self.expires, Unset):
            if isinstance(self.expires, datetime.date):
                files.append(("expires", (None, self.expires.isoformat().encode(), "text/plain")))
            else:
                files.append(("expires", (None, str(self.expires).encode(), "text/plain")))

        if not isinstance(self.note, Unset):
            if isinstance(self.note, str):
                files.append(("note", (None, str(self.note).encode(), "text/plain")))
            else:
                files.append(("note", (None, str(self.note).encode(), "text/plain")))

        if not isinstance(self.label, Unset):
            files.append(("label", (None, str(self.label).encode(), "text/plain")))

        if not isinstance(self.created_at, Unset):
            files.append(("created_at", (None, self.created_at.isoformat().encode(), "text/plain")))

        if not isinstance(self.created_by, Unset):
            files.append(("created_by", (None, str(self.created_by).encode(), "text/plain")))

        for prop_name, prop in self.additional_properties.items():
            files.append((prop_name, (None, str(prop).encode(), "text/plain")))

        return files

    @classmethod
    def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
        from ..models.food import Food
        from ..models.inventory_location import InventoryLocation
        from ..models.unit import Unit

        d = dict(src_dict)
        id = d.pop("id", UNSET)

        _inventory_location = d.pop("inventory_location", UNSET)
        inventory_location: InventoryLocation | Unset
        if isinstance(_inventory_location, Unset):
            inventory_location = UNSET
        else:
            inventory_location = InventoryLocation.from_dict(_inventory_location)

        def _parse_sub_location(data: object) -> None | str | Unset:
            if data is None:
                return data
            if isinstance(data, Unset):
                return data
            return cast(None | str | Unset, data)

        sub_location = _parse_sub_location(d.pop("sub_location", UNSET))

        def _parse_code(data: object) -> None | str | Unset:
            if data is None:
                return data
            if isinstance(data, Unset):
                return data
            return cast(None | str | Unset, data)

        code = _parse_code(d.pop("code", UNSET))

        _food = d.pop("food", UNSET)
        food: Food | Unset
        if isinstance(_food, Unset):
            food = UNSET
        else:
            food = Food.from_dict(_food)

        _unit = d.pop("unit", UNSET)
        unit: Unit | Unset
        if isinstance(_unit, Unset):
            unit = UNSET
        else:
            unit = Unit.from_dict(_unit)

        amount = d.pop("amount", UNSET)

        def _parse_expires(data: object) -> datetime.date | None | Unset:
            if data is None:
                return data
            if isinstance(data, Unset):
                return data
            try:
                if not isinstance(data, str):
                    raise TypeError()
                expires_type_0 = isoparse(data).date()

                return expires_type_0
            except (TypeError, ValueError, AttributeError, KeyError):
                pass
            return cast(datetime.date | None | Unset, data)

        expires = _parse_expires(d.pop("expires", UNSET))

        def _parse_note(data: object) -> None | str | Unset:
            if data is None:
                return data
            if isinstance(data, Unset):
                return data
            return cast(None | str | Unset, data)

        note = _parse_note(d.pop("note", UNSET))

        label = d.pop("label", UNSET)

        _created_at = d.pop("created_at", UNSET)
        created_at: datetime.datetime | Unset
        if isinstance(_created_at, Unset):
            created_at = UNSET
        else:
            created_at = isoparse(_created_at)

        created_by = d.pop("created_by", UNSET)

        patched_inventory_entry = cls(
            id=id,
            inventory_location=inventory_location,
            sub_location=sub_location,
            code=code,
            food=food,
            unit=unit,
            amount=amount,
            expires=expires,
            note=note,
            label=label,
            created_at=created_at,
            created_by=created_by,
        )

        patched_inventory_entry.additional_properties = d
        return patched_inventory_entry

    @property
    def additional_keys(self) -> list[str]:
        return list(self.additional_properties.keys())

    def __getitem__(self, key: str) -> Any:
        return self.additional_properties[key]

    def __setitem__(self, key: str, value: Any) -> None:
        self.additional_properties[key] = value

    def __delitem__(self, key: str) -> None:
        del self.additional_properties[key]

    def __contains__(self, key: str) -> bool:
        return key in self.additional_properties
