from abc import ABC, abstractmethod
from typing import Annotated, Any, Optional, TypeVar, Union, cast

from pydantic import BaseModel, BeforeValidator, ConfigDict, WrapSerializer, with_config
from pydantic_core import PydanticUndefined
from pydantic_core.core_schema import SerializationInfo, SerializerFunctionWrapHandler
from typing_extensions import Self, TypeAlias, TypedDict

T = TypeVar("T")

ResourceId = Union[int, str]
ResourceT = TypeVar("ResourceT", bound="Resource")


class Schema(BaseModel):
    model_config = ConfigDict(
        extra="forbid",
        frozen=True,
        serialize_by_alias=True,
        validate_by_alias=False,
        validate_by_name=True,
    )


class Resource(BaseModel, ABC):
    model_config = ConfigDict(
        extra="ignore",
        frozen=True,
        validate_by_alias=True,
        validate_by_name=True,
    )

    @classmethod
    def parse_api(cls, raw: Any) -> Self:
        return cls.model_validate(raw)

    @staticmethod
    @abstractmethod
    def get_schema() -> type[Schema]: ...

    @staticmethod
    @abstractmethod
    def get_filter_schema() -> type[Schema]: ...


@with_config(
    extra="forbid",
    frozen=True,
    validate_by_alias=False,
    validate_by_name=True,
)
class ResourceDict(TypedDict):
    pass


def is_external_serialization(info: SerializationInfo) -> bool:
    return isinstance(info.context, dict) and info.context.get("external", False)


def empty_str_to_none(value: Optional[str]) -> Optional[str]:
    if value is None or value == "":
        return None
    return value


def none_to_empty_str(
    value: Optional[str],
    handler: SerializerFunctionWrapHandler,
    info: SerializationInfo,
) -> Optional[str]:
    result = handler(value)
    if not is_external_serialization(info):
        return cast(Optional[str], result)

    return "" if result is None else result


def extract_nested_id(value: Any) -> Any:
    if isinstance(value, int) or isinstance(value, str):
        return value
    if isinstance(value, dict) and "id" in value:
        return value["id"]
    return PydanticUndefined


def serialize_nested_id(
    value: Any,
    handler: SerializerFunctionWrapHandler,
    info: SerializationInfo,
) -> Union[int, dict[str, Any]]:
    result = handler(value)
    if not is_external_serialization(info):
        return cast(int, result)

    return {"id": result}


OptionalStr: TypeAlias = Annotated[
    Optional[str],
    BeforeValidator(empty_str_to_none),
    WrapSerializer(none_to_empty_str),
]

NestedId: TypeAlias = Annotated[T, BeforeValidator(extract_nested_id), WrapSerializer(serialize_nested_id)]
ReadOnlyNestedId: TypeAlias = Annotated[T, BeforeValidator(extract_nested_id)]
