# This file is generated by prismarine 1.5.2. Do not modify it.

from prismarine.runtime.dynamo_crud import (
    _query,
    _get_item,
    _update,
    _put_item,
    _delete,
    _scan,
    _save,
    Model,
    without,
    DbNotFound
)

from typing import List, Literal  # noqa
from types import EllipsisType

from common.dynamo_access import get_dynamo_access

from typing import Any, Optional, get_args, get_origin
from pydantic import BaseModel, Field, create_model
from pydantic.fields import PydanticUndefined


def _is_optional_type(annotation: Any):
    origin = get_origin(annotation)
    if origin:
        return type(None) in get_args(annotation)

    return annotation is type(None)


def _make_optional(annotation: Any):
    if _is_optional_type(annotation):
        return annotation

    try:
        return annotation | None  # type: ignore[operator]
    except TypeError:
        return Optional[annotation]


def _build_pydantic_update_model(model_cls):
    field_definitions = {}
    for name, field in model_cls.model_fields.items():
        annotation = _make_optional(field.annotation or Any)
        field_is_required = (
            field.is_required() if callable(field.is_required) else field.is_required
        )
        alias = field.alias if field.alias and field.alias != name else None

        if field_is_required:
            default = None
        else:
            default = field.default if field.default is not PydanticUndefined else None

        default_factory = None if field_is_required else field.default_factory
        field_kwargs = {}
        if alias:
            field_kwargs['alias'] = alias

        if default_factory is not None:
            field_definitions[name] = (
                annotation,
                Field(default_factory=default_factory, **field_kwargs)
            )
        elif field_kwargs:
            field_definitions[name] = (
                annotation,
                Field(default=default, **field_kwargs)
            )
        else:
            field_definitions[name] = (annotation, default)

    return create_model(
        f'{model_cls.__name__}UpdateDTO',
        __base__=BaseModel,
        __module__=__name__,
        **field_definitions
    )


def _dump_model(instance):
    if isinstance(instance, BaseModel):
        return instance.model_dump(mode='python', by_alias=True)

    return instance


def _dump_update_model(instance):
    if isinstance(instance, BaseModel):
        return instance.model_dump(mode='python', by_alias=True, exclude_unset=True)

    return instance


def _load_model(model_cls, value):
    if isinstance(value, model_cls):
        return value

    return model_cls.model_validate(value)


def _load_models(model_cls, items):
    return [_load_model(model_cls, item) for item in items]


# Model classes imports
from common.myobject.models import Item  # noqa: F401, E402
from common.myobject.models import NestedItem  # noqa: F401, E402

# Create a DynamoDB resource
dynamo = get_dynamo_access()


class ItemModel(Model):
    table_name = 'PrismaPydanticItem'
    PK = 'Foo'
    SK = 'Bar'

    UpdateDTO = _build_pydantic_update_model(Item)

    @staticmethod
    def list(*, foo: str, **kwargs) -> List[Item]:
        items = _query(dynamo, ItemModel.table_name, {ItemModel.PK: foo}, **kwargs)
        return _load_models(Item, items)

    @staticmethod
    def get(
        *, bar: str, foo: str, default: Item | EllipsisType = ..., **kwargs
    ) -> Item:
        item = _get_item(
            dynamo,
            ItemModel.table_name,
            {ItemModel.PK: foo, ItemModel.SK: bar},
            default,
            **kwargs
        )
        if not item:
            raise DbNotFound(
                f'Item was not found in {ItemModel.table_name} with params foo={foo}, bar={bar}'
            )
        return _load_model(Item, item)

    @staticmethod
    def put(item: Item, **kwargs) -> Item:
        payload = _dump_model(item)
        _put_item(dynamo, ItemModel.table_name, payload, **kwargs)
        return _load_model(Item, payload)

    @staticmethod
    def update(
        item: UpdateDTO, *, foo: str, bar: str, default: Item | EllipsisType = ...
    ) -> Item:
        initial = ItemModel.get(bar=bar, foo=foo, default=default)
        update_data = _dump_update_model(item)
        _update(
            dynamo,
            ItemModel.table_name,
            {ItemModel.PK: foo, ItemModel.SK: bar},
            without(update_data, [ItemModel.PK, ItemModel.SK])
        )
        return _load_model(Item, {**_dump_model(initial), **update_data})

    @staticmethod
    def save(updated: Item, *, original: Item | None = None) -> Item:
        updated_payload = _dump_model(updated)
        original_payload = _dump_model(original) if original else None
        _save(
            dynamo,
            ItemModel.table_name,
            kv={
                ItemModel.PK: updated_payload[ItemModel.PK],
                ItemModel.SK: updated_payload[ItemModel.SK]
            },
            updated=updated_payload,
            original=original_payload
        )
        return _load_model(Item, updated_payload)

    @staticmethod
    def delete(*, bar: str, foo: str, **kwargs):
        _delete(
            dynamo,
            ItemModel.table_name,
            {ItemModel.PK: foo, ItemModel.SK: bar},
            **kwargs
        )

    @staticmethod
    def scan(**kwargs) -> List[Item]:
        items = _scan(dynamo, ItemModel.table_name, **kwargs)
        return _load_models(Item, items)
